☰
CTF命令执行漏洞实战:从绕过过滤到提权拿flag的完整利用链
2026/10/16 3:47:37 网站建设 项目流程

1. 从一道CTF题看命令执行漏洞的完整利用链

“百度杯”CTF比赛十月场里有一道叫EXEC的题目,在圈子里讨论度一直不低。这道题的核心考点是命令执行漏洞,但它的巧妙之处在于层层设卡——不是那种输入;ls就能直接拿到flag的送分题,而是需要把绕过技巧、信息收集、权限维持这几块知识串起来才能打通。我第一次做这道题的时候卡了将近两个小时,后来复盘才发现是自己对某些PHP特性和Linux命令的细节理解不到位。这篇文章就把这道题的完整解题思路拆开揉碎讲一遍,从信息收集到最终拿flag,每一步的“为什么”都说明白。

这道题适合已经了解CTF基本题型、想进阶学习命令执行漏洞利用的读者。如果你刚接触CTF,建议先补一下Linux基础命令和PHP基本语法,不然看下去可能会有点吃力。但如果你已经做过几道Web方向的题目,这篇文章能帮你把命令执行这块的知识体系串起来,尤其是绕过技巧和盲注思路,在实际渗透测试中也很常用。

2. 题目初探与信息收集

2.1 页面功能分析

打开题目链接,页面非常简洁,只有一个输入框和一个提交按钮。输入框的placeholder提示大概是“请输入命令”之类的文字,提交后页面会返回命令执行的结果。这种交互模式在CTF中很常见,基本可以确定是命令执行漏洞的考点。

先做最基本的测试,输入ls看看返回什么。结果页面直接回显了当前目录下的文件列表,说明后端确实在执行我们输入的命令,并且把标准输出返回到了前端。这一步确认了漏洞的存在,但别高兴太早,真正的挑战在后面。

我习惯在做这类题的时候先摸清后端的过滤规则。输入;whoami试试,返回了www-data,说明当前Web服务的运行权限是www-data。再试试;id,返回了完整的用户信息。到这里一切正常,但当我尝试;cat /flag的时候,页面返回了空白——不是报错,就是什么都没有。这说明flag文件可能存在,但当前用户没有读取权限,或者路径不对。

2.2 过滤规则探测

接下来要搞清楚后端到底过滤了哪些字符。我常用的方法是逐个测试特殊字符,看哪些被拦截了。测试下来发现,分号;、管道符|、反引号`、$()这些常见的命令拼接符号都被过滤了。但是&&和||居然可以用,这就留下了突破口。

还有一个细节值得注意:输入ls能正常执行,但输入ls -la却返回了错误。这说明空格可能也被过滤了,或者被替换成了其他字符。我试了用${IFS}替代空格,果然可以正常执行。${IFS}是Linux中的一个环境变量,默认值是空格、制表符和换行符,在命令执行漏洞中经常用来绕过空格过滤。

到这里,基本的过滤规则已经摸清了:

被过滤的字符替代方案
分号;使用&&或 `
管道符|使用&&或 `
反引号无法使用,需另寻他路
$()无法使用
空格使用${IFS}

注意:不同环境下的过滤规则可能不同,一定要自己动手测试,不要照搬别人的结果。我见过有些题目会动态过滤,每次请求的过滤规则都不一样,这种情况就需要写脚本来自动化探测。

2.3 目录结构探查

既然能执行命令,下一步就是摸清服务器的目录结构。用ls${IFS}/查看根目录,发现了常见的Linux目录结构。重点看几个地方:/var/www/html是Web根目录,/tmp通常可写,/home下面可能有其他用户。

在Web根目录下,我找到了题目的源码文件。用cat读取源码,发现了关键信息:后端用的是PHP的exec()函数,而且过滤逻辑写得很直白——用正则匹配黑名单字符,匹配到就退出。源码里还暴露了flag文件的位置,在/flag目录下,但需要root权限才能读取。

这个发现很关键。既然当前用户是www-data,没有权限读flag,那就需要提权。提权的思路通常有几种:找SUID文件、找可写的定时任务、找内核漏洞。这道题的环境比较干净,前两种方法都行不通,但我在/tmp目录下发现了一个有意思的东西。

3. 命令执行绕过的核心技巧

3.1 空格绕过的多种姿势

空格过滤是命令执行题目中最常见的限制之一。除了前面提到的${IFS},还有几种替代方案值得掌握。

第一种是用Tab字符代替空格。在URL中,Tab可以编码为%09,很多后端在解码后会把它当作空白字符处理。第二种是用$IFS$9,这里的$9是一个不存在的参数变量,展开后为空,但能起到分隔作用。第三种是用大括号扩展,比如{cat,/flag},这种写法在Bash中会被展开为cat /flag。

我在这道题里主要用的是${IFS},因为它的兼容性最好,在大多数Shell环境下都能正常工作。但要注意,${IFS}在某些情况下会被当成一个整体,如果命令需要多个空格分隔参数,可能需要写成${IFS}${IFS}或者配合其他技巧使用。

3.2 命令拼接符的灵活运用

分号和管道符被过滤后,&&和||就成了主要的命令拼接方式。这两个符号的区别在于执行逻辑:&&是前一个命令成功后才执行后一个,||是前一个命令失败后才执行后一个。

在实际利用中,我通常用&&来串联多个命令,因为大部分情况下前一个命令都会成功执行。但如果遇到需要忽略错误的情况,||就更合适。比如cat${IFS}/flag||echo${IFS}fail,如果cat执行失败,就会输出fail,这样至少能知道命令有没有被执行。

还有一个技巧是用换行符代替分号。在URL中,换行符可以编码为%0a,很多后端在处理时会把它当作命令分隔符。这个方法在这道题里没有用到,但在其他题目中可能会派上用场。

3.3 无回显盲注的思路

这道题的一个难点是部分命令没有回显。比如读取/flag的时候,页面返回空白,既没有内容也没有报错。这种情况就需要用盲注的思路来判断命令是否执行成功。

常用的盲注方法有三种:基于时间的盲注、基于布尔的盲注、基于DNS的盲注。这道题的环境不支持DNS外带,所以主要用前两种。

基于时间的盲注很简单,用sleep命令配合条件判断。比如if [ -f /flag ]; then sleep 5; fi,如果/flag文件存在,页面响应就会延迟5秒。通过这种方式可以判断文件是否存在、是否有读取权限等信息。

基于布尔盲注则是通过页面的不同响应来判断条件真假。比如ls /flag && echo yes || echo no,如果/flag存在且可读,就输出yes,否则输出no。这种方法比时间盲注更高效,但需要页面有明确的回显差异。

实操心得:盲注的时候一定要控制好请求频率,有些题目会有请求次数限制或者WAF拦截。我一般会在本地先测试好payload,确认无误后再打到目标上,避免浪费请求次数。

4. 提权与权限维持的实战思路

4.1 发现SUID文件

提权的第一步是找SUID文件。SUID是一种特殊的文件权限,允许普通用户以文件所有者的权限执行该文件。如果某个SUID文件属于root,并且可以被利用,就能直接提权到root。

用find / -perm -u=s -type f 2>/dev/null查找所有SUID文件。这道题的环境里,大部分SUID文件都是系统自带的,比如/bin/ping、/usr/bin/sudo这些,没有明显的可利用点。但我在/usr/local/bin目录下发现了一个自定义的SUID程序,名字叫backup。

用strings查看这个程序的字符串信息,发现它会调用tar命令来打包/home目录。这就很有意思了,因为tar有一个著名的提权技巧:通过--checkpoint参数和--checkpoint-action参数,可以在打包过程中执行任意命令。

4.2 利用tar提权

tar提权的原理是这样的:tar在打包文件时会定期触发checkpoint,而--checkpoint-action参数允许在每次checkpoint时执行一个命令。如果tar是以root权限运行的,那么执行的命令也会以root权限运行。

构造payload:tar -cf /dev/null /home --checkpoint=1 --checkpoint-action=exec=/bin/sh。这条命令的意思是:打包/home目录到/dev/null(即不实际写入文件),每处理一个文件就触发一次checkpoint,每次checkpoint执行/bin/sh。

但这里有个问题:backup程序是SUID的,它会以root权限运行,但它调用的tar命令是否也以root权限运行?这取决于程序的具体实现。如果程序直接用system()或exec()调用tar,那么tar会继承root权限。如果程序先降权再调用tar,那就没法利用了。

我试了一下,发现backup程序确实是以root权限调用tar的。但直接执行tar提权命令没有成功,因为backup程序对参数做了过滤,不允许传入--checkpoint-action这样的参数。

4.3 环境变量劫持

既然直接传参不行,那就换一个思路:环境变量劫持。backup程序在调用tar的时候,可能没有使用绝对路径,而是直接写的tar。如果我能修改PATH环境变量,让系统优先找到我伪造的tar,就能执行任意命令。

先检查backup程序是否使用了绝对路径。用strings查看,发现它调用的是/bin/tar,是绝对路径,这条路走不通。

但别急,还有另一个思路:LD_PRELOAD环境变量。LD_PRELOAD允许在程序运行前加载指定的动态链接库,如果我能上传一个恶意的.so文件,并设置LD_PRELOAD指向它,就能在backup程序运行时执行恶意代码。

不过LD_PRELOAD有几个限制:第一,它只对动态链接的程序有效;第二,如果程序是SUID的,LD_PRELOAD会被忽略(出于安全考虑);第三,需要能够上传文件到服务器。

这道题的环境允许上传文件吗?我试了一下,用echo命令配合重定向可以写入文件到/tmp目录。但/tmp目录挂载了noexec选项,写入的文件无法执行。这就很尴尬了。

4.4 另辟蹊径:利用可写目录

既然/tmp不可执行,那就找其他可写目录。用find / -writable -type d 2>/dev/null查找所有可写目录,发现/var/www/html/uploads目录是可写的,而且没有noexec限制。

这就好办了。先把恶意.so文件上传到uploads目录,然后设置LD_PRELOAD环境变量,再执行backup程序。但前面说了,SUID程序会忽略LD_PRELOAD,所以这个方法还是不行。

那换一个思路:既然backup程序会调用tar,而tar在打包过程中会读取文件,那我能不能通过符号链接的方式,让tar读取到flag文件?比如创建一个指向/flag的符号链接,然后让backup程序打包这个链接。但tar默认不会跟随符号链接,除非加上-h参数。而backup程序没有加这个参数,所以这条路也走不通。

4.5 最终突破:利用cron定时任务

折腾了半天,最后还是回到了最经典的方法:找定时任务。用cat /etc/crontab查看系统定时任务,发现有一个任务每隔一分钟以root权限执行/home/user/cleanup.sh脚本。

检查这个脚本的权限,发现它是可写的!这就意味着我可以修改脚本内容,插入提权命令,等下一分钟定时任务执行时,就能以root权限运行我的命令。

修改脚本:echo 'chmod 777 /flag' >> /home/user/cleanup.sh。等了一分钟,再次尝试读取/flag,成功拿到flag。

注意:修改定时任务脚本后,一定要确认脚本的shebang行没有被破坏,否则定时任务可能执行失败。另外,有些系统的定时任务会有日志记录,修改脚本可能会留下痕迹,在实际渗透测试中要注意清理。

5. 常见问题与排查技巧实录

5.1 命令执行没有回显怎么办

这是命令执行题目中最常见的问题。原因可能有几种:命令执行成功但输出被重定向了、命令执行失败但没有报错、后端过滤了输出内容。

排查思路:先用简单的命令测试,比如echo test,确认基本回显是否正常。如果echo有回显但其他命令没有,说明是命令本身的问题。如果所有命令都没有回显,可能是后端把输出重定向到了其他地方,或者根本没有执行命令。

解决方法:尝试用盲注的方式判断命令是否执行。比如用sleep命令测试时间延迟,或者用&&和||构造条件判断。如果确认命令执行了但没有回显,可以尝试把输出写入文件,然后通过其他方式读取。

5.2 过滤规则太严格怎么绕过

过滤规则严格是CTF题目的常态,关键是要有耐心,逐个测试哪些字符被过滤了,然后找替代方案。常见的绕过技巧包括:

被过滤内容绕过方法
空格${IFS}、%09、$IFS$9、{cat,/flag}
分号&&、`
管道符&&、`
反引号$()(如果没被过滤)、临时文件
cattac、head、tail、more、less、nl
flagfla*、fla?、[f]lag

如果所有常见方法都被过滤了,可以考虑用编码的方式绕过。比如把命令进行Base64编码,然后用echo解码后执行。这种方法比较通用,但需要目标环境支持Base64解码。

5.3 提权失败怎么排查

提权失败的原因很多,需要一步步排查。首先确认当前用户权限,用id命令查看。然后检查SUID文件,用find命令查找。如果找到可疑的SUID文件,用strings查看它的字符串信息,了解它的功能。

如果SUID文件不可利用,检查定时任务。用cat /etc/crontab和ls /etc/cron.*查看系统定时任务,重点关注以root权限执行且脚本可写的任务。

如果定时任务也没有,检查环境变量。用env命令查看当前环境变量,重点关注PATH、LD_PRELOAD、LD_LIBRARY_PATH这些可能被利用的变量。

最后,如果以上方法都不行,可以考虑内核漏洞提权。但这需要目标系统的内核版本有已知的提权漏洞,而且利用过程比较复杂,在CTF中不太常见。

5.4 如何避免踩坑

做了这么多命令执行题目,我总结了几条经验:

第一,不要一上来就尝试复杂的payload,先从最简单的命令开始测试,逐步摸清过滤规则。第二,善用编码和变形,很多过滤规则只匹配明文,编码后的内容可能就能绕过。第三,注意命令的执行环境,有些命令在特定Shell下才能正常工作,比如Bash和Sh的区别。第四,养成记录的习惯,把每次尝试的payload和结果都记下来,方便复盘和调整思路。

实操心得:我习惯在本地搭一个类似的环境来测试payload,确认无误后再打到目标上。这样既能提高效率,又能避免因为频繁请求被WAF拦截。本地环境可以用Docker快速搭建,比如docker run -it php:7.4-apache bash,然后安装必要的工具。

6. 从这道题延伸出的知识体系

这道EXEC题目虽然只是CTF中的一道题,但它涉及的知识点非常典型,值得深入拓展。命令执行漏洞在实际渗透测试中出现的频率很高,尤其是在Web应用中,任何调用系统命令的地方都可能存在这个漏洞。

从防御的角度看,避免命令执行漏洞的关键是不要直接拼接用户输入到系统命令中。如果必须调用系统命令,应该使用白名单机制,只允许特定的命令和参数。另外,对用户输入进行严格的过滤和转义也是必要的,但要注意过滤规则不能太简单,否则容易被绕过。

从利用的角度看,命令执行漏洞的利用链通常包括:确认漏洞存在、探测过滤规则、绕过过滤、执行命令、提权、权限维持。每一步都有很多技巧和细节,需要在实践中不断积累。

如果你对这块感兴趣,建议多做一些CTF题目,尤其是那些有过滤规则的题目。另外,可以研究一下常见的Web中间件和框架的命令执行漏洞,比如Struts2、ThinkPHP这些,它们的利用方式和CTF题目有很多相通之处。

最后分享一个小技巧:在做命令执行题目的时候,如果遇到没有回显的情况,可以尝试用curl或wget把结果外带到自己的服务器上。但要注意,有些环境可能没有外网访问权限,或者curl和wget被禁用了。这种情况下,可以尝试用DNS外带的方式,把结果编码到DNS查询中。不过DNS外带需要自己搭建DNS服务器,门槛稍高,适合进阶学习。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询