MK
摩柯社区 - 一个极简的技术知识社区
AI 面试
Python Django与Flask中的安全机制解析
2023-12-306.2k 阅读

Python Django 中的安全机制解析

1. 跨站脚本攻击(XSS)防护

1.1 自动转义

Django 对模板中的变量默认进行 HTML 转义,这能有效防止大多数简单的 XSS 攻击。例如,当我们在模板中输出一个用户输入的变量时:

# views.py
from django.http import HttpResponse
from django.shortcuts import render

def xss_view(request):
    user_input = "<script>alert('XSS')</script>"
    return render(request, 'xss_template.html', {'user_input': user_input})
<!-- xss_template.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>XSS Test</title>
</head>
<body>
    {{ user_input }}
</body>
</html>

在渲染模板时,Django 会将 <script> 标签转义为 &lt;script&gt;,这样在浏览器中就不会执行恶意脚本。

1.2 显式转义控制

如果我们需要在模板中输出一些信任的 HTML 内容,可以使用 safe 过滤器。但这需要我们确保内容来源的安全性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>XSS Safe Test</title>
</head>
<body>
    {{ trusted_html|safe }}
</body>
</html>
# views.py
def safe_xss_view(request):
    trusted_html = "<p>This is a trusted <b>HTML</b> content.</p>"
    return render(request,'safe_xss_template.html', {'trusted_html': trusted_html})

2. 跨站请求伪造(CSRF)防护

2.1 CSRF 中间件

Django 内置了 CSRF 中间件 django.middleware.csrf.CsrfViewMiddleware。在 settings.py 中,它默认是启用的:

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

这个中间件会为每个用户会话生成一个 CSRF 令牌,并在后续的 POST、PUT、DELETE 等不安全的 HTTP 请求中验证该令牌。

2.2 在模板中使用 CSRF 令牌

当我们在模板中创建一个表单时,需要包含 CSRF 令牌。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CSRF Form</title>
</head>
<body>
    <form method="post">
        {% csrf_token %}
        <input type="text" name="username">
        <input type="submit" value="Submit">
    </form>
</body>
</html>

Django 会在渲染模板时,将 {% csrf_token %} 替换为一个隐藏的输入字段,包含用户的 CSRF 令牌。

2.3 视图函数中的 CSRF 验证

在视图函数中,如果使用了 csrf_protect 装饰器(或者中间件默认起作用),Django 会验证请求中的 CSRF 令牌。

from django.views.decorators.csrf import csrf_protect

@csrf_protect
def csrf_protected_view(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        # 处理表单数据
        return HttpResponse('Form submitted successfully')
    return render(request, 'csrf_form.html')

如果请求中没有正确的 CSRF 令牌,Django 会返回一个 403 Forbidden 响应。

3. SQL 注入防护

3.1 使用 ORM

Django 的对象关系映射(ORM)能有效防止 SQL 注入。例如,当我们查询数据库获取用户信息时:

from django.contrib.auth.models import User

def user_query(request):
    username = request.GET.get('username')
    try:
        user = User.objects.get(username=username)
        return HttpResponse(f"User {user.username} found.")
    except User.DoesNotExist:
        return HttpResponse("User not found.")

Django 的 ORM 会对查询参数进行适当的转义和处理,确保恶意的 SQL 语句不会被执行。

3.2 原生 SQL 时的防护

如果我们不得不使用原生 SQL,Django 提供了参数化查询的方式。

from django.db import connection

def raw_sql_query(request):
    username = request.GET.get('username')
    with connection.cursor() as cursor:
        cursor.execute("SELECT * FROM auth_user WHERE username = %s", [username])
        row = cursor.fetchone()
        if row:
            return HttpResponse(f"User {row[1]} found.")
        else:
            return HttpResponse("User not found.")

通过使用参数化查询,%s 会被替换为实际的参数值,从而防止 SQL 注入。

4. 点击劫持防护

4.1 X - Frame - Options 中间件

Django 提供了 django.middleware.clickjacking.XFrameOptionsMiddleware 中间件来防止点击劫持。在 settings.py 中默认启用:

# settings.py
MIDDLEWARE = [
    #...
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

这个中间件会在响应头中添加 X - Frame - Options 字段。默认值是 DENY,表示页面不允许在 <frame><iframe><object> 中显示。

4.2 自定义 X - Frame - Options 策略

我们可以在视图函数中通过 xframe_options_exempt 装饰器来覆盖默认策略,或者在 settings.py 中设置 X_FRAME_OPTIONS 的值为 SAMEORIGIN,表示页面只能在同源的 <frame> 中显示。

from django.views.decorators.clickjacking import xframe_options_exempt

@xframe_options_exempt
def exempt_view(request):
    return HttpResponse("This view is exempt from X - Frame - Options.")

5. 密码管理

5.1 密码哈希

Django 使用强大的密码哈希算法来存储用户密码。默认使用 pbkdf2_sha256 算法。当我们创建用户时:

from django.contrib.auth.models import User

user = User.objects.create_user(
    username='testuser',
    password='testpassword'
)

Django 会将密码进行哈希处理后存储在数据库中,而不是明文存储。

5.2 密码验证

在用户登录时,Django 会使用相同的哈希算法对用户输入的密码进行哈希,并与数据库中存储的哈希值进行比较。

from django.contrib.auth import authenticate, login

def user_login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = authenticate(request, username=username, password=password)
        if user is not None:
            login(request, user)
            return HttpResponse("Login successful")
        else:
            return HttpResponse("Invalid credentials")
    return render(request, 'login.html')

Python Flask 中的安全机制解析

1. 跨站脚本攻击(XSS)防护

1.1 自动转义

Flask 对 Jinja2 模板中的变量默认进行 HTML 转义。例如:

from flask import Flask, render_template_string

app = Flask(__name__)

@app.route('/xss')
def xss_route():
    user_input = "<script>alert('XSS')</script>"
    return render_template_string('{{ user_input }}', user_input=user_input)

在渲染模板时,Jinja2 会将 <script> 标签转义,防止恶意脚本执行。

1.2 显式转义控制

如果需要输出信任的 HTML 内容,可以使用 |safe 过滤器。

@app.route('/safe_xss')
def safe_xss_route():
    trusted_html = "<p>This is a trusted <b>HTML</b> content.</p>"
    return render_template_string('{{ trusted_html|safe }}', trusted_html=trusted_html)

2. 跨站请求伪造(CSRF)防护

2.1 CSRF 扩展

Flask 本身没有内置 CSRF 防护,但可以使用 Flask - WTF 扩展来实现。首先安装扩展:

pip install Flask - WTF

然后在应用中使用:

from flask import Flask, render_template_string
from flask_wtf.csrf import CSRFProtect, CSRFError

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'
csrf = CSRFProtect(app)

@app.route('/csrf', methods=['GET', 'POST'])
def csrf_route():
    if request.method == 'POST':
        try:
            csrf.protect()
            # 处理表单数据
            return "Form submitted successfully"
        except CSRFError:
            return "CSRF verification failed", 403
    return render_template_string('''
        <form method="post">
            {{ csrf_token() }}
            <input type="text" name="username">
            <input type="submit" value="Submit">
        </form>
    ''')

Flask - WTF 会为每个用户会话生成 CSRF 令牌,并在表单中添加 csrf_token() 来包含令牌。

3. SQL 注入防护

3.1 使用 ORM(如 SQLAlchemy)

Flask 通常与 SQLAlchemy 一起使用来进行数据库操作,从而防止 SQL 注入。例如:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///test.db'
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)

@app.route('/user_query')
def user_query():
    username = request.args.get('username')
    user = User.query.filter_by(username=username).first()
    if user:
        return f"User {user.username} found."
    else:
        return "User not found."

SQLAlchemy 会对查询参数进行处理,避免 SQL 注入。

3.2 原生 SQL 时的防护

如果使用原生 SQL,需要进行参数化查询。

@app.route('/raw_sql_query')
def raw_sql_query():
    username = request.args.get('username')
    conn = db.engine.connect()
    result = conn.execute("SELECT * FROM user WHERE username = :username", username=username)
    row = result.fetchone()
    if row:
        return f"User {row[1]} found."
    else:
        return "User not found."

4. 点击劫持防护

4.1 设置 X - Frame - Options 头

Flask 可以通过设置响应头来防止点击劫持。

@app.after_request
def add_xframe_options_header(response):
    response.headers['X - Frame - Options'] = 'DENY'
    return response

这里设置 X - Frame - OptionsDENY,表示页面不允许在 <frame><iframe><object> 中显示。也可以设置为 SAMEORIGIN 来允许同源的框架嵌入。

5. 密码管理

5.1 密码哈希

Flask 可以使用 bcrypt 等库来进行密码哈希。首先安装 bcrypt

pip install bcrypt

然后在应用中使用:

import bcrypt

password = "testpassword".encode('utf - 8')
hashed = bcrypt.hashpw(password, bcrypt.gensalt())

def check_password(plain_password, hashed_password):
    return bcrypt.checkpw(plain_password.encode('utf - 8'), hashed_password)

5.2 密码验证

在用户登录时,使用 check_password 函数来验证密码。

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        user = User.query.filter_by(username=username).first()
        if user and check_password(password, user.password_hash):
            return "Login successful"
        else:
            return "Invalid credentials"
    return render_template_string('''
        <form method="post">
            <input type="text" name="username">
            <input type="password" name="password">
            <input type="submit" value="Login">
        </form>
    ''')

Django 与 Flask 安全机制对比

  1. 内置防护程度:Django 内置了丰富的安全机制,如 CSRF 防护、密码管理等,开箱即用。而 Flask 相对更轻量级,很多安全机制需要借助扩展来实现,如 CSRF 防护需要 Flask - WTF 扩展。
  2. 模板转义:两者都对模板变量默认进行 XSS 转义,但 Django 的模板系统在这方面的文档和使用方式更为成熟和详细。
  3. 数据库安全:在使用 ORM 防止 SQL 注入方面,Django 的 ORM 和 Flask 搭配的 SQLAlchemy 都能很好地完成任务。但如果使用原生 SQL,Django 的参数化查询语法相对更简洁明了。
  4. 密码管理:Django 有一套完整的用户认证和密码管理体系,而 Flask 需要开发者手动集成第三方库如 bcrypt 来实现类似功能。
  5. 部署安全:Django 有一些内置的中间件来处理如点击劫持等安全问题,配置相对简单。Flask 则需要开发者手动在应用中添加相应的响应头设置来实现相同的功能。

在实际开发中,根据项目的规模、需求和团队的技术栈来选择合适的框架,并合理运用其安全机制,以构建安全可靠的 Web 应用。同时,无论是使用 Django 还是 Flask,都需要持续关注安全问题,及时更新框架版本,修复可能出现的安全漏洞。