GHCTF-WEB-EZ ReadFile实战:从XXE到敏感文件读取的完整路径解析
最近在复盘一些CTF的Web题目,发现很多初学者在面对文件读取类漏洞时,思路容易卡在“读到了/etc/passwd,然后呢?”这一步。GHCTF中的这道“EZ ReadFile”就是一个非常典型的案例,它表面上是一个简单的XXE(XML外部实体注入)漏洞,但真正的挑战在于如何利用这个初始的“读文件”能力,像剥洋葱一样层层深入,最终在复杂的容器化环境中定位到那个被精心隐藏的flag。这过程远比单纯执行一个payload要曲折,也更贴近真实渗透测试中“信息收集-线索关联-路径突破”的实战逻辑。今天,我们就抛开官方预期解,聚焦于这条更通用、更锻炼基本功的“敏感信息读取”路径,完整拆解每一步的思考与操作。
1. 漏洞入口与初步信息收集
题目通常会给一个简单的Web界面,可能是一个文件上传点、一个数据提交表单,或者像本题一样,直接给出部分源代码。第一步永远是代码审计。即使代码很短,也要逐行分析其逻辑。
假设我们审计发现,后端处理逻辑大致如下:
<?php
if (isset($_POST['a']) && isset($_POST['b'])) {
if ($_POST['a'] !== $_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
$file = $_GET['file'];
include($file);
} else {
echo "条件不满足";
}
}
?>
这段代码揭示了两个关键点:
- 漏洞触发条件:需要传递两个不同的参数
a和b,但它们的MD5值必须严格相等(===)。这是一个典型的MD5强碰撞挑战。 - 漏洞本身:在条件满足后,程序会通过
include()函数包含file参数指定的文件。这里存在**本地文件包含(LFI)**漏洞,如果配合特定配置(如allow_url_include=On),也可能演变为远程文件包含(RFI)。
提示:MD5强碰撞的绕过通常不是考察密码学知识,而是利用已知的“魔术”字符串。在CTF中,直接搜索现成的payload是最高效的做法。
构造请求并成功触发漏洞后,我们获得了第一个关键能力:任意文件读取。此时,不要急于去寻找flag,而应系统性地收集服务器环境信息。一个结构化的信息收集清单能帮你避免遗漏。
初期关键文件读取清单:
| 文件路径 | 预期获取的信息 | 目的与分析 |
|---|---|---|
/etc/passwd | 系统用户列表 | 确认操作系统类型(Linux),了解现有用户。 |
/proc/self/environ | 当前进程的环境变量 | 可能包含Web路径、数据库密码等关键配置。 |
/proc/self/cmdline | 启动当前进程的命令 | 确定Web服务器软件(如apache2, nginx)。 |
/proc/version | 内核版本信息 | 确定系统具体版本,为后续寻找系统漏洞做准备。 |
/etc/hostname, /etc/issue | 主机名、系统发行版信息 | 进一步确认系统环境。 |
Web应用配置文件(如config.php, .env) | 数据库连接字符串、API密钥 | 尝试通过路径穿越(../../../)读取Web目录下的配置文件。 |
通过读取 /proc/self/cmdline,我们很可能看到 apache2,从而确定服务器类型。读取 /proc/version 则能确认是Ubuntu还是CentOS等具体发行版。这些信息构成了我们对目标环境的基础画像。
2. 常规路径受阻与思维转换
在信息收集之后,很自然地会尝试直接读取常见的flag位置,例如 /flag、/root/flag.txt、/home/*/flag 等。但在这道题目中,这些尝试大概率会失败。
http://target.com/?file=../../../flag
返回的可能是“文件不存在”或空白。这时,新手容易陷入迷茫。关键在于转换思维:当直接目标不可达时,就去寻找关于目标的“线索”。在CTF和真实渗透中,flag或关键数据很少赤裸裸地放在那里,它们通常被重命名、隐藏,或者其位置被记录在某个配置或日志文件中。
我们需要从已读到的敏感文件中寻找“线索”。例如,/etc/passwd 显示了用户,那么flag是否在某个用户的家目录中?/proc/self/environ 中是否包含了项目的路径?如果都没有,那么一个更重要的方向出现了:应用日志。
对于Apache服务器,其访问日志和错误日志是信息宝库:
/var/log/apache2/access.log:记录所有HTTP请求,可能包含管理员的访问记录、测试参数等。/var/log/apache2/error.log:记录错误信息,可能暴露文件路径、数据库错误,甚至在某些配置下,我们可以通过User-Agent等方式污染日志,再包含日志文件来实现代码执行。
尝试读取这些日志文件:
# 尝试读取Apache日志
../../../var/log/apache2/access.log
../../../var/log/apache2/error.log
然而,我们可能会发现这些文件不存在。这引出了下一个关键发现:目标可能运行在容器(Docker)内。
3. 容器环境下的路径探索
在 /proc 文件系统下,有几个文件能帮助我们确认容器环境并发现突破点。
-
/proc/self/mounts或/proc/1/mounts:这个文件列出了系统所有的挂载点。在Docker容器中,你会看到很多来自宿主机(host)的卷挂载,或者典型的OverlayFS挂载信息。仔细浏览,你可能会发现宿主机的路径被挂载到了容器内的某个位置。overlay / overlay rw,relatime,lowerdir=... 0 0 /dev/sda1 /var/lib/docker/overlay2/xxxx/merged ext4 rw,relatime ...类似这样的行暗示了容器文件系统的结构。
/var/lib/docker/overlay2/是Docker存储镜像层和容器可写层(diff)的典型位置。 -
/proc/self/cwd:这是一个符号链接,指向进程的当前工作目录。通过readlink命令或直接读取其链接目标(在某些读取方式下可能显示),可以知道应用在容器内的根路径。 -
/proc/self/root:指向进程的根目录。在容器中,这通常是容器内的/。
当意识到环境是容器后,我们的文件读取策略需要调整。容器内的文件系统是独立的,许多宿主机上的绝对路径(如 /var/log/apache2)在容器内可能不存在,或者路径完全不同。此时,我们需要寻找容器构建或启动时留下的“痕迹”。
在Docker生态中,有两个文件至关重要:
Dockerfile:构建镜像的蓝图,但通常不会留在运行中的容器里。docker-compose.yml:定义服务、网络、卷的编排文件。它有时会被复制到容器中,特别是当开发图方便时。docker-entrypoint.sh或entrypoint.sh:容器的启动脚本。这个文件在容器内存在的概率极高,它包含了容器启动后要执行的命令,例如初始化数据库、创建用户、设置权限——也包括可能创建flag文件的命令。
那么,如何找到这些文件?它们可能位于:
- 容器根目录
/ /app或/var/www/html的上级目录/usr/local/bin/等常用脚本目录
由于路径不确定,我们需要结合“路径穿越”和“字典爆破”的方法。我们可以编写一个简单的Python脚本进行尝试。
import requests
base_url = "http://target.com/"
base_payload = "?file="
# 可能的文件名列表
file_list = [
"docker-compose.yml",
"docker-entrypoint.sh",
"entrypoint.sh",
"Dockerfile",
"start.sh",
"init.sh"
]
# 可能的路径深度(从当前目录向上穿越)
for depth in range(0, 6): # 尝试0到5层 ../
path_prefix = "../" * depth
for filename in file_list:
test_path = path_prefix + filename
# 构造完整的漏洞利用请求,这里需要包含满足MD5碰撞条件的a和b参数
# 为简洁,省略具体请求构造,假设我们已经有了一个可以执行文件读取的函数 `do_read`
content = do_read(test_path)
if content and "error" not in content.lower() and len(content) > 10:
print(f"[+] Found: {test_path}")
print(content[:500]) # 打印前500字符预览
break
这个脚本会尝试在不同层级目录下寻找关键的Docker相关文件。运气好的话,我们会在根目录(depth=0)或上一层(depth=1)找到 docker-entrypoint.sh。
4. 从启动脚本中定位最终flag
当我们成功读取到 docker-entrypoint.sh 时,就打开了最终的大门。仔细审查这个脚本的每一行。
一个典型的 docker-entrypoint.sh 可能长这样:
#!/bin/bash
set -e
# 初始化数据库等操作
# ...
# 创建flag文件!关键行!
echo $FLAG > /some/very/obscure/path/flag_name
# 启动主进程
exec "$@"
或者,flag可能来自环境变量:
if [ -n "$INSERT_FLAG" ]; then
echo $INSERT_FLAG > /f1wlxekj1lwjek1lkejzs1lwje1lwesjk1wldejlk1wcejl1kwjelk1wjcle1jklwecj1lkwcjel1kwjel1cwjl1jwlkew1jclkej1wlkcj1lkwej1lkcwjellag
fi
看!这就是我们梦寐以求的线索! 脚本明确指出了flag被写入的完整路径和文件名。在这个例子中,文件名是一串毫无规律的随机字符串,位于根目录。如果没有读到这个脚本,靠爆破猜到这个名字几乎是不可能的。
现在,我们只需要用已经掌握的文件读取漏洞,去读取这个指定路径的文件即可:
http://target.com/?file=../../../f1wlxekj1lwjek1lkejzs1lwje1lwesjk1wldejlk1wcejl1kwjelk1wjcle1jklwecj1lkwcjel1kwjel1cwjl1jwlkew1jclkej1wlkcj1lkwej1lkcwjellag
至此,flag便唾手可得。这条路径的核心逻辑是:利用初始漏洞建立信息读取能力 -> 系统化收集环境信息 -> 通过环境特征(容器)调整搜索方向 -> 定位关键元文件(启动脚本) -> 从元文件中发现最终目标位置。
整个流程就像侦探破案,每一步的发现都为下一步提供了方向。它不依赖于某个特定的、高难度的RCE漏洞,而是扎实地运用了渗透测试中最基础也最重要的能力:信息收集与逻辑推理。掌握这种思路,即使在未来遇到更复杂的题目或真实环境,你也能有一条清晰的路径可循,而不是在尝试几个常见payload失败后就束手无策。
99



被折叠的 条评论
为什么被折叠?



