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