TryHackMe Contrabando 漏洞挖掘全解析:从 LFI 到 Root 权限的完整攻击链
Contrabando writeup — TryHackMe
这台机器呈现了一个复杂而迷人的攻击路径,需要跨不同系统层链接多个漏洞。初始入口点是本地文件包含(LFI)漏洞,利用该漏洞发现了一个关键的 HTTP 请求走私漏洞(CVE-2023–25690)。此漏洞是绕过反向代理并在后端 Docker 容器上实现远程代码执行(RCE)的关键。在逃离容器的网络后,我在内部服务上发现了服务器端模板注入(SSTI)漏洞,从而获得了用户 shell。最后的权限提升涉及两个步骤:首先,通过 GLOB 模式匹配利用一个实现不当的 Bash 脚本来窃取密码;其次,利用 Python2 脚本中的一个危险代码构造最终获得 root 访问权限。
侦察阶段
我使用 nmap 进行积极的全面端口扫描,以快速识别目标机器上的所有可用服务。使用的标志是-sS、-p-和—min-rate 5000,以覆盖所有端口并快速完成。
(nmap1)
发现:
端口 22:开放(SSH)
端口 80:开放(HTTP)
对这些特定端口进行的后续服务版本和脚本扫描提供了更多细节。
(nmap)
结果:
端口 80:Apache httpd 2.4.55
枚举阶段
导航到端口 80 显示了一个简单的"即将推出"页面,带有一个指向测试版站点的链接,重定向到/page/home.html。
(soon)
/page/端点结构立即暗示了本地文件包含(LFI)的潜在向量。
(url)
我开始通过模糊测试来查找目录。对 Web 根目录(/)的初始扫描显示信息很少,但模糊测试/page/目录本身(/page/FUZZ)返回了大量的 200 OK 响应。停止扫描后,我意识到这是预期的功能。
(gobuster)
我决定尝试其中一个端点/page/,它显示 readfile()函数正在运行,还有一个名为 index.php 的文件。
(about)
我访问了/page/index.php 并查看了页面源代码。这揭示了驱动/page/端点的后端逻辑。
(index.php)
为了测试 LFI,我决定再次使用 Gobuster,但使用其模糊测试模式。我使用的命令如下(我过滤了长度 0–300,因为请求的平均响应是这个大小,这阻止了我看到好的请求)。
(fuzz)
在模糊测试过程中,一些格式错误的请求返回了 400 Bad Request 错误。这些错误包含了一个揭示性的头部:服务器将自己标识为 Apache 2.4.54,并且在 172.18.0.3:8080 上。
(apache/2.4.54)
这可能证明了系统的构建方式:主机上的 Apache 反向代理(端口 80)将请求转发到 Docker 容器内的 Apache 后端服务(端口 8080)。
通过测试一个导致状态码 200 的有效载荷,我有效地验证了服务器易受 LFI 攻击。
(lfi)
由于有效载荷的结构,我了解到像../../../../etc/passwd 这样的标准 LFI 有效载荷被代理阻止,但可以通过双重 URL 编码绕过。这使我能够使用更短的有效载荷。
(short)
为了验证是否是 Docker,我决定使用 Burp Suite 并拦截对/etc/apache2/sites-available/000-default.conf 的请求。
拥有 VirtualHost *:8080 证实了我的怀疑。
这是关键的洞察:前端服务器(在端口 80 上)充当反向代理。当用户请求/page/something 时,代理会将请求作为 index.php?page=something 转发给后端应用程序(在 Docker 容器的端口 8080 上)。后端的 index.php 脚本然后获取 page 参数并将其直接传递给 readfile()函数,使其易受 LFI 攻击。
作为下一步,我决定再次列出/page/中的目录,但就像我对 FUZZ 所做的那样,按长度过滤。
(gobuster)
扫描显示了一个名为/gen.php 的文件。通过使用 LFI,我能够读取其源代码。
(post)
我看到如果提供了 length POST 参数,它以未清理的方式使用了 exec()函数。出现的第一个问题是我无法直接访问该文件,因此我决定测试/page/端点是否允许包装器。
(wrapper)
它确实允许它们,因此我决定通过向我的 python3 服务器-m http.server 发送请求来测试 SSRF。
(ssrf)
拥有 SSRF 使我能够通过向 localhost 发送请求来访问 gen.php 文件。
(localhost)
挑战在于反向代理将我的 POST 请求转换为 GET 请求,阻止我直接发送必要的参数。
(index.php?page=http://localhost:8080/gen.php)
解决方案是后端上的 Apache 版本(2.4.54),它易受通过 CVE-2023–25690 的 HTTP 请求走私攻击。此漏洞允许攻击者发送带有嵌入的 CRLF(\r\n 或 %0d%0a)序列的特制 HTTP 请求。前端和后端服务器对单个请求的解释不同,允许攻击者"走私"第二个隐藏的请求直接到后端应用程序,绕过代理的规则。
利用阶段
为了验证服务器是否易受攻击,我决定使用在 dhmosfunk 的 GitHub 上找到的 PoC。
%20HTTP/1.1%0d%0aFoo:%20baarr
(poc)
有效载荷所做的是在 HTTP 版本之后发送一个额外的请求。如果发送请求时没有发生错误,则意味着服务器易受攻击,并且请求已成功"走私"通过。
现在我需要一个功能脚本来帮助我,我在 GitHub 上找到了 thanhlam-attt 的一个(我修改了脚本以使其适用于这台机器,你必须安装 pwntools 才能使此脚本工作)。
运行脚本的步骤如下:
创建一个 pre.txt 文件,其中包含您想要走私到后端/gen.php 的完整原始 HTTP POST 请求。为此,复制下面的有效载荷,将其粘贴到 burp suite 中,然后使用您的 IP 和 PORT 重新计算内容长度,然后右键单击 -> 复制到文件。
修改脚本中的 IP 变量并输入受害者机器的 IP 地址。
创建一个 shell.sh 文件,其中包含一个 bash 反向 shell,并在您为 pre.txt 中 curl 请求指定的相同端口上使用 Python HTTP 服务器托管它。
在您在 shell.sh 文件中设置的相同端口上设置一个 netcat 监听器并运行脚本。
该脚本制作了一个利用 CRLF 漏洞的请求,将我的恶意 POST 请求走私过代理。
(request)
后端服务器执行了它,授予我 Docker 容器内 www-data 用户的反向 shell。
(www-data)
从容器中,我需要探索主机的内部网络。因此,我需要将一个静态的 nmap 二进制文件上传到目标,为此,我首先移动到/tmp 目录并创建一个文件夹,然后使用 curl 向我的 python 服务器发出请求以下载该文件。
(curl)
下载后,我解压它并使用 chmod +x 赋予执行权限。
(extraer)
为了运行它,我只使用了—min-rate=5000 标志,但它扫描了主机 172.18.0.1 到 172.18.0.3 上的所有端口。
(5000)
扫描显示 Docker 主机(172.18.0.1)上的端口 5000 开放。我使用 curl 与此新服务交互(我已经利用了端口 80 和 8080)。
(fetch)
该服务获取一个 URL。我决定向我的 Python 服务器发送一个请求,但当我没有收到任何有趣的内容时,我决定改为将其发送到 netcat。
(nc)
通过使用 nc 检查请求,我发现它使用了 PycURL 库,该库支持 file://协议。这使我能够从主机系统读取本地文件。
(file:///etc/passwd)
这揭示了一个名为 hansolo 的用户。知道他的名字后,我决定检查是哪个用户正在运行此服务进程。
(file:///proc/self/environ)
这揭示了该进程由 hansolo 运行。读取/proc/self/cmdline 显示了应用程序路径:/home/hansolo/app/app.py。
(app.py)
app.py 的源代码是关键。它使用了高度危险的 render_template_string()函数来渲染用户控制的输入。
(template)
将 website_content 请求直接发送到 render_template_string 使应用程序易受 SSTI 攻击。当应用程序将用户输入嵌入到模板中,然后在服务器上执行时,就会发生服务器端模板注入。这允许攻击者注入可能导致 RCE 的模板指令。在这种情况下,应用程序使用的是 Jinja2 模板。
我通过托管一个名为 test.html 的文件来确认该漏洞,该文件包含有效载荷{{ 7*7 }}。当应用程序获取并渲染它时,输出是 49,证明输入正在被执行。
(test.html)
然后我托管了一个更高级的 Jinja2 SSTI 反向 shell 有效载荷并触发它。
这给了我主机上 hansolo 用户的反向 shell。
(hansolo)
权限提升
我做的第一件事是稳定 shell,为此我采取了以下步骤:
(reset)
然后我将终端类型设置为 xterm 并导出变量 TERM 和 SHELL。
(export)
现在我决定开始枚举以提升权限。我运行了 sudo -l,发现我可以在没有密码的情况下以 root 身份执行一个命令。
(sudo -l)
专注于第一个(因为它没有要求密码),我决定查看其内容。
(/root/password)
vault 脚本将用户输入与/root/password 的内容进行比较。关键缺陷在于其比较逻辑。变量没有加引号。在 Bash 中,这会导致==运算符执行模式匹配(GLOB 扩展)而不是字符串匹配。通过输入单个星号*,我匹配了任何非空字符串,欺骗脚本授予访问权限。
(*)
这并没有太大帮助,所以看到脚本分析了/root/password,我决定使用我在 voltatach 博客上找到的一个脚本来发现这个密码是什么。
该脚本使用 GLOB 模式(例如,a*, b*, [a-z], ?)执行暴力攻击。这依次确定了密码的长度和每个字符。运行脚本后,我获得了一个密码。
(password)
第二个 sudo 命令很有趣:它允许我使用任何 python*解释器运行一个脚本。我检查了该脚本,发现它使用 input()来获取用户数据。
(input())
关键区别在于 Python 2 和 Python 3 之间:
在 Python 3 中,input()始终将用户的输入作为字符串返回。
在 Python 2 中,input()将用户的输入作为 Python 代码进行评估。这是极其危险的。
我使用 Python2 执行了该脚本,提供了窃取的密码。对于最后的提示("您想为密码添加的任何单词"),我提供了一个生成 bash shell 的 Python 命令:import("os").system("/bin/bash")
(root)
因为脚本是用 Python2 运行的,input()函数将__import__("os").system("/bin/bash")作为 Python 代码进行评估,以 root 权限执行它,并授予我 root shell。现在我可以读取 root 标志的内容。
(root.txt)
所有问题的答案是:
第一个标志是什么?THM{Th3_BeST_SmuGGl3R_In_Da_GalaXy}
第二个标志是什么?THM{All_AbouT_PassW0rds}
就这些了!感谢阅读,下次再见!,我希望这篇 Writeup 对您有所帮助。更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)
公众号二维码

公众号二维码

评论