ctf.show Web 82
1 |
|
这是一道漫长的题目,在解题之前,我们先了解一些东西:
条件竞争漏洞原理与实例
技术原理:在程序运行中,开发者一般希望代码按顺序一条一条执行。但在多线程或多进程的环境下,服务器会并发处理多个请求。如果没有使用合适的同步机制(像锁,它能保证同一时间只有一个线程能访问某个资源 ),这些并发的线程就可能同时操作共享资源,导致结果不可预测。比如两个线程同时读取一个变量的值,然后都对这个值进行修改再写回去,最后保存的值可能不是预期的,因为它们互相干扰了。
以文件上传为例: 有些网站允许用户上传文件,服务器会先检查文件是否符合要求(比如只允许上传图片格式文件 ),如果不符合就删除。在检查和删除之间有个时间差,这就是 “竞争窗口” 。攻击者可以利用这个时间差,快速多次发送上传恶意文件(比如包含恶意代码的 PHP 文件 )的请求。因为服务器在同一时刻可能处理不过来这么多请求,在还没来得及删除不符合要求的文件时,攻击者就可能成功访问到这个恶意文件并让它执行,进而在服务器上植入后门,获取对服务器的控制。类似于DDOS攻击,当请求发送得太多,服务器可能会漏掉一些恶意文件的上传请求,从而导致恶意文件被发送在服务器中造成后门漏洞。
(以上两段摘自www.th-dedsec.top《条件竞争漏洞》2025.04.07)
PHP_SESSION_UPLOAD_PROGRESS
是什么?
PHP_SESSION_UPLOAD_PROGRESS
是 PHP 提供的一个机制,用于实时跟踪文件上传的进度。比如上传一个大文件时,你可以用它获取当前上传的百分比、速度、剩余时间等信息。
工作原理
- 当
session.upload_progress.enabled
配置项开启时,PHP 脚本在运行过程中一旦检测到表单里存在name="PHP_SESSION_UPLOAD_PROGRESS"
的字段,就会开启文件上传进度跟踪功能。 - 此时,当用户上传文件,PHP 会在会话(Session)中生成一个数组,记录该脚本里所有文件上传操作的数据(见下方演示进度信息的数组)。
- 需要通过其他方式(如 AJAX 轮询)读取这个会话数据,实时显示进度条给用户。
配置要求
1 | session.upload_progress.enabled = On ; 启用上传进度跟踪 |
基本使用步骤
表单设置
在文件上传表单中,必须添加一个名为PHP_SESSION_UPLOAD_PROGRESS
(具体名称由上面提到的配置项session.upload_progress.name
确定)的隐藏字段:1
2
3
4
5
6<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="my_upload" />
<input type="file" name="file" />
<!-- Tips: type="file"时会自动弹出文件选择框,不需要手动设置value -->
<input type="submit" value="上传" />
</form>只有检测到表单里存在
name="PHP_SESSION_UPLOAD_PROGRESS"
的字段,才会跟踪文件上传进度并写入 Session 中。读取进度
通过 AJAX 或其他方式,从前端定期请求一个 PHP 脚本,读取会话中的进度信息(此部分仅作了解,不作演示)。
Session 中的进度信息
会话中的进度信息是一个数组,键为 'upload_progress_my_upload'
(具体由 session.upload_progress.prefix
配置项与表单隐藏字段中的 value
拼接而成)包含以下关键字段:
1 | $_SESSION['upload_progress_my_upload'] = [ |
知识了解完了,让我们把目光转向题目:
题目解析
1 | $file = str_replace("php", "???", $file); |
这里的关键点是 $file = str_replace(".", "???", $file);
,文件名中的.
会被替换,所以找文件名不包含.
的文件: Session 文件。
已知 Session 文件的名字一般是sess_xxx
的形式,那如何知道名字的后半部分呢?其实可以从响应头的 Set-Cookie
或请求头的 Cookie
字段中得到,例如:PHPSESSID=abc123
,便得到了名字的后半部分abc123
。(详见我之前写的关于 Session 的文章)
如果响应头中没有关于 Session 的内容,那么可以尝试自定义 SessionID 。部分服务器校验机制不可靠,不校验客户端发送的 SessionID 是否由服务端生成。由于服务端没有对应的 Session 文件,所以会生成与客户端自定义 SessionID 对应名字的 Session 文件,从而给攻击者可乘之机。
Burp Suite 解法
操作不方便,不建议。
Python requests 解法
此处借用大佬 Dedsec 的脚本,改了点功能,加了点注释:
1 | import requests |
(以上代码摘自www.th-dedsec.top《条件竞争漏洞》2025.04.07)
最后根据文件目录的内容,在 URL 中手动构造 Payload 给 486.php 文件传参即可获得 flag 。
为什么要植入后门?
Session 文件里的文件上传数据不会自动删除,但为了防止服务端后续有删除 session 文件里的 "PHP_SESSION_UPLOAD_PROGRESS":"<?php @eval($_POST[1]);?>"
等数据的操作,需要一个长期存在的后门完成下一步。
requests.session()
做什么用?
其实在这里没什么用,因为网站没有返回可供它管理的 Cookie ,详见我写的关于 requests 库的文章。
全过程流程
向 Session 文件中写入 Payload。
向网站传参(既通过 URL 传参给
$_GET[]
数组,也通过 POST 传参给$_POST[]
数组),包含 Session 文件并植入后门。访问后门文件,通过 URL 传参执行
ls
命令,查看目录内容。手动构造 Payload 查看 flag 文件。