Flask 作为一个 WEB 框架来说内部封装的安全性的措施很多,基本解决了常见的 web 漏洞,下面介绍 Flask 是如何来避免产生 常见的 web 漏洞。
SQL注入:
Flask 使用 ORM 对象关系映射的数据库管理方式,同时 Flask 框架内部也封装了许多安全性的函数,对于 SQL 注入拥有一定防护力,如图
Flask 中单引号会自动进行转义
XSS:
Flask 使用 Jinja2 模板引擎,Jinja 会默认将渲染变量进行 HTML 实体转义
@user_bp.route('/test2')
def test2():
code = '<h1>Flask</h1>'
return render_template('test.html', code=code)
# test.html
{{ code }}
如果需要让 HTML 标签生效 可以使用 safe 过滤器
例如:一些富文本编辑器以 HTML 格式进行文章的保存,此时需要加 safe 过滤器保证文章正常保存显示,此时 xss 潜在发生处就是富文本编辑器
Flask 配置 Jinja2 自动转义所有值,除非显式地指明不转义。这就排除了模板导致的所有 XSS 问题,但是你仍需要在其它的地方小心
- 生成 HTML 而不使用 Jinja2
- 在用户提交的数据上调用了
Markup
- 发送上传的 HTML 文件,永远不要这么做,使用 Content-Disposition: attachment 标头来避免这个问题
- 发送上传的文本文件。一些浏览器使用基于开头几个字节的 content-type 猜测,所以用户可能欺骗浏览器执行 HTML
虽然 Jinja2 可以通过转义 HTML 来保护你免受 XSS 问题,仍有一种情况,它不能保护你: 属性注入的 XSS 。为了应对这种攻击媒介,确保当在属性中使用 Jinja 表达式时,始终用单引号或双引号包裹属性
正确:<a href="{{ href }}">the text</a>
错误:<a href={{ href }}>the text</a>
# 官方文档所指明的,在实际测试中不论加没加引号,浏览器解析时都会有引号
# 注意尽量要让用户去控制 href 属性
@article_bp1.route('/test')
def article_test():
html = '''
<h1>111111</h1>
'''
href ='javascript:alert(document.body);'
return render_template('article/test.html', html=html, href=href)
http://127.0.0.1:5000/article/test?href=javascript:alert(document.cookie);
SSTI:
当用户输入被串联到模板中而不是作为数据传递时,服务器端模板注入漏洞就会出现,简单来说也就是不正确的使用 flask 中的render_template_string 方法会引发SSTI
确定模板引擎
常用的方法是使用来自不同模板引擎的语法注入任意数学运算
存在漏洞代码示例
from flask import Flask,render_template_string,request
app = Flask(__name__)
@app.route('/test/')
def test():
code = request.args.get('id') //get方式获取id
html = '''
<h3>%s</h3>
'''%(code)
return render_template_string(html)
#产生原因就是先进行了拼接,在渲染
app.run()
这个问题也并非是Jinja2的问题,而且在编写的过程中出现的人为问题
Flask安全函数
flask 中有许多提高安全性的函数,比如以下几个
generate_password_hash(password)
# 将 password 变量的值进行加盐 hash 加密
check_password_hash(user.password, password)
# 与上个函数配合使用,用来将 加密数据与为加密数据做对比,如果未加密数据加密后为已加密数据则返回 true,否则返回 false
secure_filename(icon_filename)
# 将传入文件的文件名进行安全处理,如将空格替换为下划线等
CSRF:
Flask 中并不存在表单验证,很容易造成 CSRF
基本上,对于每个修改服务器上内容的请求,应该使用一次性令牌,并存储在 cookie 里, 并且在发送表单数据的同时附上它,在服务器再次接收数据之后,你要比较两个令牌,并确保它们相等
Flask 使用 Flask-WTF 插件来避免出现 CSRF
使用方法
# 后端
class UserForm(FlaskForm):
username = StringField(label='用户名', validators=[DataRequired(), Length(min=6, max=20, message='长度必须在6~20位之间')])
password = PasswordField(label='密码', validators=[DataRequired(), Length(min=6, max=20, message='长度必须在6~20位之间')])
confirm_password = PasswordField(label='确认密码',
validators=[DataRequired(), Length(min=6, max=20, message='长度必须在6~20位之间'),
EqualTo('password', '密码不一致')])
phone = StringField(label='手机号', validators=[DataRequired(), Length(min=11, max=11, message='长度必须是11位')])
email = EmailField(label='邮箱', validators=[DataRequired()])
recaptcha = StringField(label='验证码')
def validate_recaptcha(self, data):
input_code = data.data
code = session.get('valid')
if input_code.lower() != code.lower():
raise ValidationError('验证码错误!')
......
# 前端
<form class="form-horizontal" id="container" method="post" action="{{ url_for('user.register') }}">
{{ userform.csrf_token }}
{# 防止csrf,必须设置secret_key #}
<table id="register">
<tr>
<td>{{ userform.username.label }}:</td>
<td>{{ userform.username }}{% if userform.username.errors %}
{{ userform.username.errors.0 }}{% endif %}<br>
{# 如果有报错则输出报错的message, .0表示只输出内容 #}</td>
</tr>
......
JSON安全:
Flask 使用 jsonify 函数将数据转换为 json 数据返回
# 此段代码为后台验证码校验代码
@user_bp.route('/check_phone', methods=['GET', 'POST'], endpoint='check_phone')
def check_phone():
phone = request.args.get('phone')
user = User.query.filter(User.phone == phone).all()
# code:200可用 400不可用
if len(user) > 0:
code = 400
else:
code = 200
return jsonify(code=code)
开发文档:http://docs.jinkan.org/docs/flask/security.html