反序列化
PHP
原理
对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致RCE,SQL注入,目录遍历等不可控后果。在反序列化的过程中会自动触发某些魔术方法。当反序列化的时候就有可能触发对象中的一些魔术方法。
序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字
测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class test {
public $var1 = true;
public $var2 = "hello,world!";
public $var3 = 1;
public $var4 = 0.1;
public $var5 = null;
function myfunc ($arg1, $arg2) {
echo $arg1;
echo '</br>';
echo $arg2;
}
}
$test = new test();
//$test->myfunc($test->var1, $test->var2);
echo $ser = serialize($test).PHP_EOL;//PHP_EOL为php换行符,可以提高代码的源代码级可移植性
var_dump($unser = unserialize($ser));输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 O:4:"test":5:{s:4:"var1";b:1;s:4:"var2";s:12:"hello,world!";s:4:"var3";i:1;s:4:"var4";d:0.1;s:4:"var5";N;}
object(test)#2 (5) {
["var1"]=>
bool(true)
["var2"]=>
string(12) "hello,world!"
["var3"]=>
int(1)
["var4"]=>
float(0.1)
["var5"]=>
NULL
}
//结果中并没有记录myfunc()方法
PHP序列化字符串结构
public protected private下序列化对象的区别
测试:
1 |
|
结果:
public变量
直接变量名反序列化出来protected变量
\00 + * + \00 + 变量名
可以用S:5:"\00*\00op"
来代替s:5:"?*?op"
//此处的?
是\00字符
,因为不可显示所以用?
代替疑惑:在在线平台测试的时候S和s没什么区别,本地测试必须用S
测试代码如下:
1
2$te = "O:5:\"test1\":1:{s:6:\"\00*\00var\";s:12:\"hello,world!\";}";//S和s都可以
var_dump(unserialize($te));结果:
1
2
3
4
5
6object(__PHP_Incomplete_Class)#3 (2) {
["__PHP_Incomplete_Class_Name"]=>
string(5) "test1"
["var":protected]=>
string(12) "hello,world!"
}private变量
\x00 + 类名 + \x00 + 变量名
补充:
\x:十六进制
\d:十进制
\o:八进制
\x00不可见字符
php v7.x反序列化的时候对访问类别不敏感
反序列化中S与s的区别
如果类型是S
,会调用以下函数,简单来说就是将\
解释成十六进制,来转成字符
1 | static zend_string *unserialize_str(const unsigned char **p, size_t len, size_t maxlen) |
魔术方法
以下内容来自于PHP官方文档中关于魔术方法的部分
构造方法和析构方法
- __construct()
具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。 - __destruct()
析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
new
出一个新的对象时就会调用__construct(),而对象被销毁时,例如程序退出时,就会调用__destruct()
__sleep()和__wakeup()
1 | //示例 |
__toString()
echo
或者拼接字符串或者其他隐式调用该方法的操作都会触发
1 |
|
__set() __get() __isset() __unset()
__invoke() __call()
当尝试以调用函数的方式调用一个对象时,__invoke()
方法会被自动调用。
在对象中调用一个不可访问方法时,__call()
会被调用。
其他
__callStatic(), __set_state(), __clone(), __debugInfo()等和序列化没有多大关系,详情参考官网
反序列化的利用
__wakeup失效
php版本< 5.6.25 | < 7.0.10
当序列化字符串中,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()的执行
例:1
2O:4:"Demo":1:{s:10:"Demofile";s:16:"f15g_1s_here.php";}
O:4:"Demo":2:{s:10:"Demofile";s:16:"f15g_1s_here.php";}使用
+
绕过正则
例:1
2
3preg_match('/[oc]:\d+:/i', $var)
O:4:"Demo":1:{s:10:"Demofile";s:16:"f15g_1s_here.php";}
O:+4:"Demo":1:{s:10:"Demofile";s:16:"f15g_1s_here.php";}
靶场
无类执行
wp
右键查看源码,有个css文件
点进去访问如下,发现提示?19190
加上?19190
访问
访问结果
发现php源码,分析代码逻辑,包含了flag.php文件,满足条件才能输出$flag
抓包构造$cookie
值,得到flag
有类魔术方法触发
地址:CTFHub中的AreUSerialz
题目
1 | // 题目 |
wp
1.include("flag.php")
;看出flag应该就在这个文件中
2.定义了一个FileHandler
类,有三个protected属性
,并且还有魔术方法
和自定义方法
3.类结束了有个if语句,这是关键。里面使用到了unserialize()
,考点大概率就是反序列化
4.执行顺序,由GET传参给$str
,然后进行反序列化传给$obj
,相当于$obj
现在是一个对象,当程序结束,会自动调用$obj
对象中的析构方法__destruct()
。__destruct()
中会调用$obj
的process()
方法,该方法中的if语句有三条路,当protected属性op=="1"
时会执行write()
方法,当protected属性op=="2"
时会执行read()
方法,最后若都不满足上述两个便会执行output()
方法输出Bad Hacker!
5.选择执行write()
还是read()
,有第1步分析可得,应该走read()
因为包含了flag大概率会在flag.php
文件中,然后去看read()
方法我们发现我们还需要用到一个protected属性filename
,因为我们要去读flag.php
得把这个值传进去
6.明确目标任务,GET传入一段序列化字符串满足FileHandler
类的定义,并且让$op == "2"
和$filename == "flag.php"
注意,这里还有两个考点
7.一是在最开始执行__destruct()
方法时,若$op==="2"
则会重新赋值令$op
的值为”1”,这使得我们没办法直接去调用read()
方法,这里也印证了我们之前的猜测,要获取flag应当执行read()
方法而非write()
方法。
其实也很好绕过,在__destruct()
方法中使用的是===
强等于,而read()
方法中使用的是==
弱等于,我们只需要令$op = " 2" 或 $op = 2
,就能绕过限制。
8.另一个考点是自定义方法is_valid()
的绕过,该方法规定字符的ASCII码必须是32-125,而FileHandler
类中的属性是protected
在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求,会返回false。
绕过方法:
①PHP7.1以上(不包含7.1)版本对属性类型不敏感,public
属性序列化不会出现不可见字符,可以用public
属性来绕过。
1 |
|
②protected
属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。
用在线工具得到payload,用S替换s,用\00替换*
两侧不可见字符
1 |
|
9.最后访问http://192.168.101.155/PHPseri.php/?str=payload
,再右键查看源码得到flag
其他:
做这个题,在本地kali用phpstudy搭环境时遇到了个小问题,在使用file_get_contents()
去读文件时,发现报错file_get_contents(flag.php): failed to open stream: No such file or directory in /www/admin/localhost_80/wwwroot/PHPseri.php on line 48
,百度后,需要修改php.ini
1 | allow_url_fopen = Off |
在线工具
php代码在线测试,php在线执行 (dooccn.com)
参考链接
CTF PHP反序列化 - MustaphaMond - 博客园 (cnblogs.com)
网鼎杯2020php反序列化,网鼎杯2020[青龙组]–AreUSerialz
Java(挖坑,以后填)
靶场
Releases · WebGoat/WebGoat (github.com)
CTFHub中的think_java
题目
工具
反序列化工具ysoserial
:Releases · frohoff/ysoserial (github.com)