ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

CTF任意文件读取漏洞从入门到精通:路径穿越、伪协议与绕过实战

CTF任意文件读取漏洞从入门到精通:路径穿越、伪协议与绕过实战 1. 为什么任意文件读取是CTF入门最值得啃的第一块硬骨头刚接触CTF那会儿我一度觉得Web方向的题目类型太杂SQL注入、XSS、文件上传、命令执行……每个都像一座独立的山头。后来带我的前辈说了一句话点醒了我如果你只想先精通一类漏洞那就选任意文件读取。原因很实在——它的原理足够简单但利用手法极其丰富从最基础的路径拼接到编码绕过、伪协议、日志包含几乎把Web安全里输入信任这个核心命题演绎了个遍。啃透它你对用户输入到底能有多危险这件事会有肌肉记忆般的理解。任意文件读取漏洞英文常写作Arbitrary File Read本质是服务端把用户可控的输入直接拼进了文件操作函数导致攻击者能读取到预期之外的服务器文件。它和文件包含File Inclusion经常被混为一谈但两者有明确边界文件读取偏向读内容文件包含偏向执行代码。很多新手一上来就背Payload结果遇到稍微变形一点的题目就懵了根本原因是没搞清楚数据从请求到文件系统之间到底经过了哪些环节。这篇笔记我打算按自己当初从零摸索的顺序来写不搞那种先讲定义再讲分类的教科书套路。我会先带你把漏洞的成因拆开看再讲实战里最常遇到的几种绕过姿势然后是伪协议这个重头戏最后聊聊怎么把读到的文件变成下一步的突破口。中间会穿插我自己踩过的坑比如为什么明明Payload没错却读不到文件、为什么有的题目读/etc/passwd能成读flag却不行。适合刚入门CTF Web方向、想系统搞懂一类漏洞的朋友也适合已经会背Payload但想弄明白底层逻辑的人。提醒一句下面所有技术内容仅用于CTF竞赛和授权测试环境的学习研究任何未授权环境下的测试都是违规的这条线必须守住。2. 把漏洞成因拆到函数级别输入是怎么一步步走到文件系统的2.1 从一次看起来没问题的代码说起先看一段非常典型的PHP代码这类写法在早期的CMS和练手项目里遍地都是?php $file $_GET[file]; include($file); ?很多人第一反应是这不就是文件包含吗。没错include确实是包含但当你传入的路径指向一个纯文本文件时它的效果就是把文件内容读出来并输出这时候它和文件读取在表现上没区别。真正纯粹的读取漏洞长这样?php $filename $_GET[filename]; echo file_get_contents($filename); ?或者用readfile、fopenfread、file、show_source等等。函数名不同但病根是同一个$_GET[filename]这个完全由用户控制的字符串没有经过任何白名单校验就直接被当成了文件路径。这里有个关键认知漏洞不在于用了哪个函数而在于信任边界被打破了。服务端默认用户传过来的路径是安全的但用户完全可以传../../../../etc/passwd。这就是任意文件读取的起点。2.2 路径穿越../到底做了什么../在文件系统里表示上一级目录。假设网站根目录是/var/www/html/代码里写的是file_get_contents(data/ . $filename)那么拼接后的路径是/var/www/html/data/加上你的输入。如果你传../../../../etc/passwd最终路径就变成了/var/www/html/data/../../../../etc/passwd操作系统在解析路径时会把data/..抵消成html再..抵消成www一路往上退到根目录最后指向/etc/passwd。这就是路径穿越Path Traversal也叫目录遍历。我当初犯过一个低级错误数不清要退几级。后来总结了个笨办法但很管用——先确定当前脚本所在目录再数它到根目录有几层多退几级也无所谓因为退到根目录后再..还是根目录。所以实战里我习惯直接甩一长串../比如../../../../../../../../etc/passwd省得算。2.3 绝对路径与相对路径的取舍有时候你不需要穿越因为代码可能直接接受绝对路径?php $filename $_GET[filename]; readfile($filename); ?这种情况下直接传/etc/passwd就行不用../。但更多时候代码会做拼接比如$dir . $filename这时候就必须用相对路径穿越。判断该用哪种有个小技巧先传一个绝对路径试试如果返回报错或者空再换相对路径穿越。报错信息本身也是情报比如failed to open stream: No such file or directory会告诉你拼接后的完整路径长什么样这对接下来的绕过极有帮助。2.4 为什么有的题目读不到/etc/passwd这是新手最常问的问题。原因通常有三类第一权限问题。Web服务进程比如www-data用户可能没有读取目标文件的权限。/etc/passwd通常是全局可读的所以它成了探路石但/etc/shadow一般只有root能读读不到很正常。第二路径被过滤或改写。代码可能把../替换成了空字符串或者做了realpath规范化后限制了目录范围。第三目标文件根本不在那个位置。不同系统、不同发行版的文件路径有差异比如flag文件可能被放在/flag、/tmp/flag、/var/www/html/flag.php等任意位置。我的经验是读不到/etc/passwd不代表漏洞不存在可能只是路径或权限的问题。这时候要换思路比如读当前目录下的文件、读配置文件、读源码。3. 绕过过滤的实战手法从替换到编码的完整链路3.1 过滤../的几种常见写法与对应绕过出题人也不傻直接让你传../太简单了所以会加过滤。我整理了几种最常见的过滤逻辑和对应的绕过思路过滤方式代码示例绕过思路删除../str_replace(../, , $input)用....//删除中间的../后剩下../删除..str_replace(.., , $input)用.../或....//删除后仍能拼出穿越转义../str_replace(../, #46;#46;/, $input)尝试双重编码或换用绝对路径只允许特定后缀if(!preg_match(/\.jpg$/, $input)) die()用%00截断PHP5.3.4或路径参数绕过重点说....//这个经典绕过。假设代码是str_replace(../, , $input)你传....//....//....//etc/passwd替换过程是这样的字符串里找../....//中第3、4个字符是..第5个是/但../要求连续三个字符是.、.、/而....//的前三个字符是.、.、.不匹配。等等这里要仔细....//的字符序列是....//。str_replace从左往右扫描找到第一个../需要连续三个字符。在....//里位置1-3是...不是../位置2-4是..加.也不是位置3-5是../即../所以替换掉位置3-5后剩下../也就是../。完美绕过了。这个逻辑我当初推了好几遍才想通建议你也拿张纸画一下字符位置比死记硬背强。3.2 URL编码与双重编码的适用场景当过滤发生在解码之前时URL编码就能派上用场。比如代码先检查原始输入里有没有../检查完再解码使用那你传%2e%2e%2f就能绕过检查因为解码后才是../。双重编码则是应对解码一次后再检查的情况传%252e%252e%252f第一次解码变成%2e%2e%2f如果检查在这一步它看到的是%2e不是.放行第二次解码才变成../。这里有个坑我踩过不是所有场景都支持双重编码取决于代码解码了几次。盲目套用双重编码可能因为多解码一次导致路径错误。判断方法是先传单次编码如果被拦再试双重。3.3 后缀限制与截断的那些事有的题目要求文件名必须以.jpg结尾比如?php $file $_GET[file]; if (strpos($file, .jpg) false) { die(only jpg allowed); } include($file); ?这种用strpos判断包含而非结尾的直接传../../../etc/passwd.jpg就行因为路径里确实有.jpg但实际文件不存在系统会忽略不存在的部分吗不会它会报错。所以更靠谱的是利用空字节截断仅PHP5.3.4传../../../etc/passwd%00.jpg%00是空字节在C语言层面表示字符串结束后面的.jpg被忽略。不过现在PHP版本普遍较高空字节截断基本失效了。更通用的是路径参数绕过比如传../../../etc/passwd/.././x.jpg这种利用系统对路径的规范化处理。或者如果代码用的是file_get_contents且允许远程可以配合伪协议。3.4 一个完整的绕过推演实例假设题目代码如下?php $file $_GET[file]; $file str_replace(../, , $file); $file str_replace(.., , $file); if (preg_match(/etc|passwd|flag/i, $file)) { die(hacker!); } include(pages/ . $file); ?分析一下先删../再删..然后过滤了etc、passwd、flag这些关键词。第一步绕过删除用....//经过两次替换后....//会先被str_replace(../,)处理。前面分析过....//里位置3-5是../删掉后剩../。然后第二个str_replace(..,)会把这个../里的..删掉只剩/。糟糕这样穿越就没了。所以这里要调整用....//....//第一次替换后变成../../第二次替换..后变成//还是不行。看来双重删除..的情况下....//这招失效了。那换思路既然..被删能不能用别的方式表示上级目录在Linux里..是唯一的上级表示法没有替代。但我们可以利用绝对路径如果include能接受绝对路径的话。不过代码前面拼了pages/绝对路径会被拼成pages//etc/passwd不行。这时候可以考虑伪协议比如php://filter它不依赖路径穿越直接读文件内容。传php://filter/convert.base64-encode/resourceindex.php经过替换后..和../都不存在关键词过滤也绕过了没有etc/passwd/flag完美。这个推演过程说明没有万能Payload必须根据过滤逻辑动态调整。我习惯先把过滤规则一条条列出来然后逐个击破最后组合验证。4. 伪协议任意文件读取的降维打击武器4.1php://filter为什么是读源码的首选php://filter是PHP特有的流包装器它允许在读取文件时应用过滤器。最常用的就是convert.base64-encode把文件内容Base64编码后输出。为什么要编码因为直接读PHP源码源码里的?php ?会被当成PHP代码执行你看到的可能是执行结果而不是源码。Base64编码后内容变成纯文本就能完整看到源码了。标准Payloadphp://filter/convert.base64-encode/resourceindex.php读出来的是一串Base64解码后就是源码。这个手法在CTF里出现频率极高几乎每套Web题都会考。我当初的困惑是为什么resource后面不能跟路径穿越其实可以resource../../index.php也是合法的只是很多时候目标文件就在当前目录不需要穿越。4.2php://filter的进阶过滤器链除了Base64还有几个过滤器组合很实用convert.base64-encode最常用读源码必备。convert.iconv.UTF-8.UTF-16编码转换有时能绕过关键词检测。string.strip_tags去除HTML标签读某些文件时有用。zlib.deflate压缩输出适合读大文件。更高级的玩法是过滤器链构造通过多个convert.iconv组合可以在不依赖Base64的情况下实现任意文件读取甚至绕过某些WAF。这个技术门槛较高新手可以先掌握Base64这一种遇到瓶颈再研究链式构造。4.3data://与php://input的配合data://协议允许把数据直接当作文件内容data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8解码后是?php phpinfo();?。当include配合data://时就能执行任意代码。但data://需要allow_url_includeOn默认是关闭的所以实战中不如php://filter通用。php://input则是读取POST请求体配合include可以执行POST过去的数据。这两个协议在文件包含题里更常见但和文件读取也有交集——比如你可以用php://filter读源码发现里面有include($_GET[x])再用data://或php://input拿shell。4.4 伪协议被禁用时的退路有些环境会禁用伪协议比如allow_url_fopenOff。这时候php://filter还能用吗能因为php://filter属于本地流不受allow_url_fopen限制。但data://、http://这些就废了。如果连php://filter都被禁极少见那就只能回到最原始的路径穿越老老实实读文件。这时候要更注重路径的准确性以及利用报错信息定位文件位置。我的习惯是拿到题目先试php://filter读当前文件能读出来就说明伪协议可用后续思路会宽很多读不出来再考虑路径穿越。5. 从读取到利用把文件内容变成下一步的钥匙5.1 读配置文件找数据库凭证任意文件读取最直接的价值是信息收集。读config.php、database.php、.env这类文件往往能拿到数据库账号密码、API密钥、后台路径等敏感信息。比如读到一个config.php?php $db_host localhost; $db_user root; $db_pass Pssw0rd123; $db_name ctf; ?拿到这些如果数据库端口对外开放就能直接连上去如果不开放可以结合SQL注入或其他漏洞进一步利用。5.2 读源码找隐藏漏洞读源码是CTF里最关键的突破口之一。很多题目表面上看不出漏洞但源码里藏着过滤逻辑、隐藏参数、后门文件路径。比如读到?php if ($_GET[debug] true) { show_source(__FILE__); } ?这就告诉你有个debug参数可以读源码。或者读到?php // TODO: remove this before deploy if ($_POST[cmd]) { system($_POST[cmd]); } ?直接就是一个命令执行后门。我印象很深的一道题前台怎么测都没反应后来用php://filter读了index.php源码发现它include了一个check.php再读check.php里面有个extract($_GET)直接变量覆盖绕过了所有验证。读源码的价值就在于此——它把黑盒变成了白盒。5.3 读系统文件辅助提权或横向在授权测试环境里读/etc/passwd能枚举用户读/etc/hosts能看内网拓扑读/proc/self/environ能看环境变量有时含密钥读/root/.ssh/id_rsa能拿私钥如果有权限。这些在CTF里可能用不上但在真实授权测试中价值巨大。CTF里更常见的是读/flag、/flag.txt、/tmp/flag这些位置。如果直接读不到可以读/proc/self/cwd/flag当前工作目录下的flag或者读/proc/self/cmdline看启动命令推断flag位置。5.4 日志包含从读取到执行的跨越当日志文件可读且可写时任意文件读取可以升级为日志包含攻击。思路是先把PHP代码写入日志比如通过User-Agent头再用文件包含漏洞包含日志文件从而执行代码。步骤大致是用Burp Suite或curl发送请求User-Agent设为?php system($_GET[cmd]);?。日志文件如/var/log/apache2/access.log会记录这个User-Agent。用文件包含漏洞包含日志文件?file/var/log/apache2/access.logcmdid。日志里的PHP代码被执行命令执行成功。这个手法要求日志路径已知且可读且包含漏洞存在。CTF里经常考但真实环境里日志权限通常较严不一定能读。6. 我踩过的那些坑与排查思路6.1 Payload没错却读不到文件的排查清单遇到读不到文件的情况我一般按这个顺序排查确认漏洞点是否真的存在传一个肯定存在的文件比如当前目录的index.php看有没有回显。如果连这个都读不到可能漏洞点找错了。确认路径是否正确用报错信息反推。比如报错说failed to open stream: No such file or directory in /var/www/html/index.php on line 5说明拼接后的路径不对调整../数量。确认权限换一个全局可读的文件试试比如/etc/hostname、/proc/version。确认过滤规则如果传../被拦试试编码或....//。确认伪协议是否可用换php://filter试试。这个清单我贴在显示器边上每次卡住就过一遍能解决八成问题。6.2 编码后路径反而错误的坑有一次我传%2e%2e%2f结果报错说找不到文件。后来发现代码在解码前做了urldecode等于解码了两次%2e%2e%2f变成../后又被解码一次..里的.没问题但/变成%2f不对/解码后还是/。实际问题是代码先urldecode再拼接我传的%2e%2e%2f被解码成../然后拼接路径是对的。但报错说找不到说明目标文件不在那个位置。所以编码本身没错是路径算错了。这个坑让我明白报错信息要仔细读别一看到失败就怀疑编码。6.3 读源码时被?php截断的问题用php://filter读源码时如果没用Base64编码直接读源码里的?php会被PHP解析器执行导致你看到的不是源码。我当初不知道读index.php读出来一片空白还以为文件是空的。后来加了convert.base64-encode才看到完整源码。记住读PHP源码必须Base64编码这是铁律。6.4 不同系统路径差异导致的误判Windows和Linux的文件路径完全不同。Windows下读C:\Windows\win.iniLinux下读/etc/passwd。CTF里Linux居多但偶尔有Windows题。判断方法看报错信息里的路径分隔符/是Linux\是Windows。或者读/proc/versionLinux特有和C:\Windows\win.iniWindows特有来探测。7. 把任意文件读取练成条件反射的训练方法学漏洞最怕一看就会一测就废。我的训练方法是自己搭环境反复练。用Docker起一个PHPApache环境写几个不同过滤逻辑的页面然后逐个绕过。比如页面1无过滤直接读。页面2过滤../用....//绕过。页面3过滤..和etc用php://filter绕过。页面4只允许.jpg后缀用空字节或路径参数绕过。每个页面自己先想Payload想不出来再看答案然后把绕过过程写下来包括为什么这么绕、字符是怎么被替换的。写一遍比看十遍都管用。另外CTF比赛后一定要复盘。把没做出来的文件读取题拿出来看Writeup重点看别人的Payload是怎么构造的、为什么自己没想到。我当初就是靠复盘积累了....//、双重编码、伪协议链这些技巧。最后说个心态问题任意文件读取看起来简单但真正精通需要大量实践。别指望看一篇笔记就成高手动手测、动手绕、动手写才是从0到1的唯一路径。我在这个过程中最大的体会是——漏洞利用的本质是理解程序的逻辑而不是背Payload。当你能够看着一段代码就推断出它可能有什么读取漏洞、该怎么绕的时候你才算真正入门了。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表