天天看點

Flask(python web架構)安全

Flask 作為一個 WEB 架構來說内部封裝的安全性的措施很多,基本解決了常見的 web 漏洞,下面介紹 Flask 是如何來避免産生 常見的 web 漏洞。

SQL注入:

Flask 使用 ORM 對象關系映射的資料庫管理方式,同時 Flask 架構内部也封裝了許多安全性的函數,對于 SQL 注入擁有一定防護力,如圖

Flask(python web架構)安全

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 }}
           
Flask(python web架構)安全

如果需要讓 HTML 标簽生效 可以使用 safe 過濾器

例如:一些富文本編輯器以 HTML 格式進行文章的儲存,此時需要加 safe 過濾器保證文章正常儲存顯示,此時 xss 潛在發生處就是富文本編輯器

Flask(python web架構)安全

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

确定模闆引擎

常用的方法是使用來自不同模闆引擎的文法注入任意數學運算

Flask(python web架構)安全

存在漏洞代碼示例

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