mssql-django 的安全性最佳做法

本文將介紹透過後端連接 SQL Server mssql-django 的 Django 應用程式的安全實務。 這些做法補充了 Django 內建的安全功能及 SQL Server 的安全模型。

請使用 Microsoft Entra 認證取代密碼

Microsoft Entra 認證消除了儲存的資料庫密碼。 所有 Azure SQL 連線都用它。

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>.database.windows.net",
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "Authentication=ActiveDirectoryMsi",
        },
    },
}

如需完整的驗證方法清單、目前的注意事項,以及包含 DefaultAzureCredentialManagedIdentityCredentialTOKEN 範例,請參閱 使用 mssql-django 的 Microsoft Entra 驗證

安全地管理憑證

當你需要 SQL 認證時,請避免憑證出現在原始碼中。

環境變數

import os

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": os.environ["DB_NAME"],
        "USER": os.environ["DB_USER"],
        "PASSWORD": os.environ["DB_PASSWORD"],
        "HOST": os.environ["DB_HOST"],
        "PORT": os.environ.get("DB_PORT", "1433"),
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
    },
}

Azure Key Vault

在生產環境部署中,請從 Azure Key Vault 擷取祕密:

from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient

credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://<your-vault>.vault.azure.net/", credential=credential)

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": client.get_secret("db-name").value,
        "USER": client.get_secret("db-user").value,
        "PASSWORD": client.get_secret("db-password").value,
        "HOST": client.get_secret("db-host").value,
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
    },
}

謹慎

千萬不要將憑證提交給原始碼控制。 將.env檔案加入.gitignore。 使用 git-secrets 或 pre-commit 掛鉤,掃描是否有意外提交的憑證。

強制執行 TLS 加密

SQL Server 連線應該永遠加密。 ODBC 驅動程式預設從 ODBC 驅動程式 18 開始加密連線:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>.database.windows.net",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            # Encryption is on by default with Driver 18
        },
    },
}

若使用 ODBC 驅動程式 17,請明確啟用加密:

"extra_params": "Encrypt=yes"

謹慎

僅用於 TrustServerCertificate=yes 本地開發,並使用自簽憑證。 不要在生產環境中使用它。 它會禁用憑證鏈驗證,並增加中間攻擊者的風險。 在伺服器上安裝受信任憑證並連接到 TrustServerCertificate=no

套用最低權限原則

建立專用的 SQL Server 登入,只擁有應用程式所需的權限:

-- Create a login and user for the application
CREATE LOGIN [django_app]
WITH PASSWORD = '<strong-password>';

USE [<your-database>];

CREATE USER [django_app] FOR LOGIN [django_app];

-- Grant minimum required permissions
-- Read and write data
ALTER ROLE db_datareader ADD MEMBER [django_app];
ALTER ROLE db_datawriter ADD MEMBER [django_app];

-- Allow Django to create and alter tables during migrations
GRANT ALTER ON SCHEMA::dbo TO [django_app];
GRANT CREATE TABLE TO [django_app];
GRANT REFERENCES ON SCHEMA::dbo TO [django_app];

對於未在生產環境中執行遷移的應用程式,請省略 ALTERCREATE TABLE 權限:

-- Production application user (read/write only)
ALTER ROLE db_datareader ADD MEMBER [django_app];
ALTER ROLE db_datawriter ADD MEMBER [django_app];

GRANT EXECUTE ON SCHEMA::dbo TO [django_app]; -- If using stored procedures

從一個獨立且更具特權的部署步驟執行遷移:

-- Migration user (used only during deployments)
ALTER ROLE db_ddladmin ADD MEMBER [django_migrations];

選擇正確的角色

SQL Server 固定資料庫角色依照權限最低至高排序。 選擇最不被重視且能覆蓋你工作量的職位,並只在需要時升級:

Role Grants 何時使用
db_datareader SELECT 在所有使用者資料表和檢視中 唯讀舉報使用者
db_datawriter INSERTUPDATEDELETE(在所有使用者資料表上) 執行時應用程式使用者(與 db_datareader 合併)
db_ddladmin 建立、修改及丟棄結構物件 僅限遷移或部署使用者
db_owner 所有資料庫權限,包括安全性 應避免用於應用程式;僅限 DBA 使用

如果想要比固定角色更細緻的控制,可以建立一個自訂資料庫角色,並且 GRANT 只設定應用程式所用結構上的特定權限。 將所有應用程式物件保留在專用的結構描述中(例如 app),可讓你使用 GRANT ... ON SCHEMA::app 來限定授權範圍,而不必依賴全資料庫層級的 db_datareaderdb_datawriter 角色。

Note

不要使用 sa 帳戶或 db_owner 固定資料庫角色來進行應用程式連線。 若應用程式遭入侵,攻擊者將獲得完整的資料庫控制權。

防止 SQL 注入

Django 的 ORM 會自動參數化所有查詢。 SQL 注入只有在使用原始 SQL 時才有風險:

安全:ORM 查詢

# Django parameterizes these automatically
users = User.objects.filter(email=user_input)
products = Product.objects.filter(price__lte=max_price)

安全:參數化原始 SQL

from django.db import connection

with connection.cursor() as cursor:
    cursor.execute(
        "SELECT * FROM products WHERE category = %s AND price < %s",
        [category, max_price],
    )

不安全:原始 SQL 中的字串格式化

# NEVER do this - vulnerable to SQL injection
cursor.execute(f"SELECT * FROM products WHERE category = '{category}'")
cursor.execute("SELECT * FROM products WHERE category = '%s'" % category)

Extra 與 RawSQL

Django extra()RawSQL() 接受原始的 SQL 片段。 一定要使用參數:

# Safe - parameterized
Product.objects.extra(where=["category = %s"], params=[category])

from django.db.models.expressions import RawSQL
Product.objects.annotate(
    discount=RawSQL("price * %s", [discount_rate])
)

Important

絕對不要在字串格式化中使用 extra()RawSQL()。 這些系統繞過了 ORM 的自動參數化。

配置 Django 安全中介軟體

啟用 Django 內建的安全中介軟體來保護網頁層。 雖然這些不是針對資料庫的,但它們保護連接到你資料庫的應用程式:

# settings.py

# HTTPS enforcement
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000  # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

# Cookie security
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True

# Content security
SECURE_CONTENT_TYPE_NOSNIFF = True

審計資料庫存取

啟用 SQL Server 稽核,追蹤 Django 應用程式的資料庫操作:

-- Create a server audit (Azure SQL uses Azure SQL Auditing instead)
CREATE SERVER AUDIT [DjangoAudit]
TO FILE (FILEPATH = 'C:\Audits\')
WITH (ON_FAILURE = CONTINUE);

ALTER SERVER AUDIT [DjangoAudit] WITH (STATE = ON);

-- Create a database audit specification
USE [<your-database>];

CREATE DATABASE AUDIT SPECIFICATION [DjangoDbAudit]
FOR SERVER AUDIT [DjangoAudit]
ADD (SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo BY [django_app])
WITH (STATE = ON);

對於 Azure SQL Database,請透過 Azure portal 或 Azure CLI 啟用審計:

az sql db audit-policy update --resource-group <rg> --server <server> \
    --name <database> --state Enabled \
    --storage-account <storage-account>

以 Always Encrypted 保護敏感欄位

對於敏感資料如社會安全號碼、信用卡號碼或薪資資料的欄位層級加密,請使用 Always Encrypted。 ODBC 驅動程式透明地處理加密與解密:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "ColumnEncryption=Enabled",
        },
    },
}

關於詳細設定,包括使用 Azure Key Vault 管理金鑰,請參見 Always Encrypted with mssql-django

安全檢查清單

類別 練習 Priority
Authentication Use Microsoft Entra authentication for Azure SQL.
Credentials 將秘密儲存在環境變數或 Azure Key Vault.
加密 使用 ODBC 驅動程式 18(預設為加密)或 Encrypt=yes
注入 使用 ORM 查詢或參數化的原始 SQL。 絕不要用字串格式的 SQL。
最低權限 建立專用的登入帳號,並僅授予最低必要權限。
TLS 不要在生產環境中使用 TrustServerCertificate=yes
Django 啟用 SECURE_SSL_REDIRECT、安全性 Cookie、HSTS。 中等
Auditing 啟用 SQL Server 審計或 Azure SQL 審計。 中等
欄位加密 對於高度敏感的欄位,請使用 Always Encrypted。
連線 設定 CONN_MAX_AGECONN_HEALTH_CHECKS 防止連線過時。