HackThisSite - Realistic Mission 13
Challenge
Elbonia's Elections are coming! Help delay these elections by taking down the main competitor's site! Be careful though, you get caught, you'll be wishing you had your soap on a rope...
埃尔博尼亚大选将至,任务是拿下主要竞争对手的站点来拖住选举。入口
https://www.hackthissite.org/missions/realistic/13/是竞争对手 ENRP(Elbonian National Republican Party)的官网——一个 2004 年的静态站被原样复刻成 PHP,导航里有news.php/debates.php/members.php/newsletter.php/mailinglist.php/speeches.php/press.php/economy.php。
Solution
Recon:
- 首页
index.php是站点门面:竞选日程(Debates 2004-09-20 起、Voting 2004-11-13 @ Monotim Squares)加一段新闻摘要。members.php/mailinglist.php/debates.php/economy.php/news.php都是静态内容,没有可利用的参数。 newsletter.php给了一句关键提示:make sure you have the hidden login url and your password handy.—— 目标是一个"隐藏登录 URL",需要口令。- 真正有参数处理的只有两个页面:
speeches.php(POSTspeech→speeches2.php)和press.php(POSTrelease→readpress.php)。两个表单的下拉都只有少量合法值,这类手写拼接的页面在拿到非法参数时通常会打 PHP 报错,而报错会带出源码和路径。
Step 1: 两处报错把源码和目录结构吐出来
先看讲稿分支。speeches.php 的下拉只有
value="1",speech=1 返回
This speech is still being edited, as it had many errors because of our ex-typist;把值换成一个不存在的讲稿名,include()
失败,warning 里带着服务端绝对路径:
1 | $ curl -s -m 15 -b "HackThisSite=<mission-cookie>" \ |
错误页除了站点外框(导航、页脚)之外,正文是下面这些内容(原文照录):
1 | The following speeches have been given already: |
观察到的信息:站点根在
C:\Program Files\Apache Group\Apache2\ENRP\,脚本运行在
/www/hackthissite.org/www/missions/realistic/13/;存在
ENRP\oldsite\(旧站备份)、ENRP\admin\,以及一个
32 位十六进制命名的目录
21232f297a57a5a743894a0e4a801fc3。三条 include 串里的
speches.php 拼错了(少一个
e),说明这些是硬编码模板串。
再看新闻稿分支,它泄露得更彻底。press.php 是 POST
release 到 readpress.php,直接请求
readpress.php(不给参数)会踩到同一条报错路径,把
readpress.php 的源码片段和数据库错误一起打出来:
1 | $ curl -s -m 15 -b "HackThisSite=<mission-cookie>" \ |
正文段落(原文照录):
1 | MySQL Error: "" row does not exist in table "press_table"; |
这一段直接给出了通关路线:站点自己在服务端用 socket 去
GET /speeches/passwords/<某个目录>,目录名是
md5('Speeches'),并且请求时必须带上
REFERER: http://ENRP/get_speeches_passwords_referer。也就是说,/speeches/passwords/
下面有个用 md5('Speeches') 命名的"受保护"目录。
Step 2: 算出受保护目录名,读出口令文件
按报错里泄露的表达式直接算 md5(注意不是 md5('speeches')
小写,原文是大写 S):
1 | $ printf '%s' Speeches | md5sum |
拼成 URL 访问,得到的是 Apache 自动目录索引:
1 | $ curl -s -m 15 -b "HackThisSite=<mission-cookie>" \ |
1 |
|
目录里只有一个 passwords.fip(66 字节,扩展名写成
.fip,和前面 speches.php
一样是这个站的笔误风格)。把它拉下来:
1 | $ curl -s -m 15 -b "HackThisSite=<mission-cookie>" \ |
两个 32 位十六进制值,用冒号分隔——这就是 newsletter 里说的"你的口令"。
Step 3: 爆破两个 md5,得到用户名和口令
两个值都是弱口令,字典一跑就出(hashcat -m 0 hash.txt rockyou.txt
之类)。这里直接用已知明文回算验证:
1 | $ printf '%s' moni1 | md5sum |
passwords.fip
的内容(7bc35830abab8fced52657d38ea048df 和
21232f297a57a5a743894a0e4a801fc3,冒号分隔)对应的是
moni1:admin,即用户名 moni1、口令
admin。注意左边那个
7bc35830abab8fced52657d38ea048df
是用户名的哈希(md5('moni1')),它是口令文件里的字段,不是目录名——后面会看到把它当目录名用会
404。
Step 4: 诱饵后台
/13/admin/ 与真正的登录目录
手上有 moni1:admin
之后,直觉会去找后台。/missions/realistic/13/admin/
确实存在一个登录页,但用这组凭据会被拒——HTS 官方关卡文章与公开 writeup
都记录了错误文案
"admin" does not match password for "moni1"。它是个诱饵:用户名口令都对,但页面不是真的。
线索在 Step 1 的第一处报错里:include 路径中出现过
ENRP\admin\passes.php,还出现过一个 32 位 hex 目录
21232f297a57a5a743894a0e4a801fc3——刚刚算过,它就是
md5('admin')。作者用的是同一套把戏:把 admin
这个目录名换成它的 md5 当"隐藏"。用状态码验证目录真伪:
1 | $ curl -s -m 15 -o /dev/null -w '%{http_code}\n' -b "HackThisSite=<mission-cookie>" \ |
md5('admin') 那个目录返回 200,md5('moni1')
返回 404——真正的登录目录名是
md5('admin'),不是
md5('moni1')、也不是
md5('Speeches')(后者只用来保护
speeches/passwords/ 那层)。
关键陷阱:诱饵目录下面还有一个
/13/admin/passes.php,专门把想法往"Referer"上引:
1 | $ curl -s -m 15 -b "HackThisSite=<mission-cookie>" \ |
它不给口令,只提示 HTTP_REFERER——和 Step 1 源码里
REFERER: http://ENRP/get_speeches_passwords_referer
是同一种套路:这一关反复用 Referer 当"门禁"。
Step 5: 哈希目录里的登录表单
哈希目录本身不设防,直接返回一份 234 字节的纯表单页:
1 | $ curl -s -m 15 -b "HackThisSite=<mission-cookie>" \ |
1 | <body bgcolor="black" text="White"> |
字段名确认是 user 和 pass(不是
username/password),method=POST,action=login2.php。不带
POST 数据直接 GET 登录脚本,返回的是 HTS
站点外框,关卡内容只有一行拒绝文案:
1 | $ curl -s -m 15 -b "HackThisSite=<mission-cookie>" \ |
1 | <center><b>Incorrect Username/Password</b></center> |
Step 6: 登录与 completion oracle
把 moni1:admin POST 到
login2.php,同时带上哈希目录自身作为
Referer(这一关的来源检查就是照着 Step 1 源码里那个
REFERER: 头的模式设计的):
1 | $ curl -s -m 15 -b "HackThisSite=<mission-cookie>" \ |
凭据正确时服务端返回完成页
Vulnerabilities
- PHP warning /
报错回显泄露路径与源码:
speeches2.php的include()失败把 Windows 绝对路径、oldsite/admin内部目录和脚本自身路径全部打印;readpress.php更进一步,把自身源码(含服务端 socket 请求的构造过程)连同数据库错误一起输出。生产环境必须display_errors=Off,warning 只进日志。 - 把 md5 当访问控制:受保护目录名是
md5('Speeches')、登录目录名前缀是md5('admin'),等于把安全性押在"攻击者猜不到原文"上;32 位 hex 目录名本身就是提示,离线字典一击即破。 - 隐藏目录可枚举且无鉴权:
speeches/passwords/<hash>/开了 Apache 目录索引,直接列出并下载passwords.fip;<md5('admin')>/目录不设防地返回登录表单,把爆破面缩到一个已知路径上。 - 口令文件本身不设防:
passwords.fip里就是两个未加盐、无迭代的 md5,等于明文。 - 诱饵式安全:真正的门禁只有
login2.php的凭据校验和一个可伪造的 Referer 检查(admin/passes.php甚至把"send correct HTTP_REFERER"写在了页面上),攻击者只要看穿诱饵就能直达。
修复方向:错误信息统一走日志、不回显任何路径或源码;受保护资源用会话鉴权而不是 md5 目录名;目录索引关闭,口令文件移出 Web 根目录并用加盐哈希存储;删除诱饵页,把所有入口收敛到同一套鉴权逻辑;Referer 只能作为 CSRF 的辅助信号,且必须配合服务端 token。