Pythonセキュリティ実装パターン10選 ── 安全なWebツールを作る実務コード付きガイド

PythonでWebツールやAPIを開発する際、多くの開発者は機能実装に集中します。しかし実務ではセキュリティ設計の質=サービス品質です。ユーザーデータの保護、攻撃耐性、運用安全性——これらが欠けたサービスは、どれだけ機能が優れていても信頼されません。

本記事では、Python開発者が最低限実装すべきセキュリティパターン10個を、NG例とOK例のコード付きで紹介します。Django・FastAPIを使う人はもちろん、Pythonでツールやスクリプトを書くすべての開発者が対象です。

なお、パスワード・ハッシュ・トークンといったセキュリティ用文字列の基礎知識については「パスワード・UUID・ハッシュ・トークンの違いとは?」で詳しく解説しています。

Pythonセキュリティ実装10項目一覧

まず全体像を把握しましょう。10項目を一覧にまとめました。各項目名から詳細セクションにジャンプできます。

項目目的重要度OWASP対応
① 安全な乱数生成予測不能なトークン生成必須A02 暗号化の失敗
② パスワード保存認証情報の保護必須A02 暗号化の失敗
③ JWTの安全設計認証・認可の制御必須A07 認証の不備
④ SQLインジェクション対策データベースの保護必須A03 インジェクション
⑤ XSS対策画面表示の安全性必須A03 インジェクション
⑥ CSRF対策フォーム送信の保護必須A01 アクセス制御の不備
⑦ レート制限攻撃・乱用の防止重要
⑧ ログ設計事故防止・追跡性重要A09 ログと監視の不備
⑨ シークレット管理情報漏洩の防止必須A02 暗号化の失敗
⑩ 環境変数による設定分離運用の安全性必須A05 セキュリティ設定ミス

OWASP Top 10(Webアプリケーション脆弱性ランキング)との対応も記載しました。この10項目をカバーするだけで、OWASPが指摘する主要リスクの大半に対応できます。

① 安全な乱数生成 — random は使うな

トークン、認証コード、パスワードリセットURLなどのセキュリティ用途で乱数が必要な場合、Pythonの random モジュールは絶対に使ってはいけませんrandom はメルセンヌ・ツイスタという予測可能なアルゴリズムを使っており、出力を数百個観測すれば次の出力が予測できてしまいます。

NG例:

NG
import random
token = random.randint(100000, 999999)  # 予測可能!

OK例:

OK
import secrets

token = secrets.token_hex(16)
print(token)
# 例: 9f3c0c6d61c3c2e7b2c5a2b41a6f9d88

secrets モジュールはOSの暗号論的擬似乱数生成器(CSPRNG)を利用しており、出力の予測は事実上不可能です。トークン生成、CSRF対策、パスワードリセットなど、セキュリティに関わるすべての乱数は secrets を使いましょう。

💡 Tip

secrets.token_urlsafe(32) を使えば、URLに安全に埋め込める形式のトークンが生成できます。パスワードリセットリンクの生成に最適です。詳しくは「Pythonでパスワード生成」も参考にしてください。

② パスワード保存 — 平文は論外

パスワードを平文でデータベースに保存するのは、最も致命的なセキュリティ事故のひとつです。DBが漏洩した瞬間、全ユーザーのパスワードが攻撃者の手に渡ります。

NG例:

NG
password = "user_password"
db.save(password)  # 平文保存 → 漏洩即終了

OK例:

OK
import bcrypt

password = b"mypassword"
hashed = bcrypt.hashpw(password, bcrypt.gensalt())

# 検証
is_valid = bcrypt.checkpw(password, hashed)
print(is_valid)  # True
Bash
pip install bcrypt

SHA-256などの汎用ハッシュ関数を単体で使うのも危険です。SHA-256は高速すぎるため、攻撃者が1秒に数十億回のハッシュ計算を試行できてしまいます。bcryptやArgon2は意図的に計算を遅くする設計になっており、ブルートフォース攻撃のコストを桁違いに引き上げます。

⚠️ よくある落とし穴

「SHA-256でハッシュ化したから安全」は誤りです。パスワード保存には必ずbcrypt・Argon2・scryptのような意図的に遅いアルゴリズムを使ってください。ハッシュの基礎知識については「ランダム文字列8種の正体と使い分けガイド」で詳しく解説しています。

③ JWTの安全設計

JWT(JSON Web Token)はステートレスな認証に広く使われていますが、設定を誤ると深刻な脆弱性になります。

NG例:

NG
import jwt
token = jwt.encode({"user_id": 1}, "secret")  # 期限なし・弱いsecret

OK例:

OK
import jwt
import datetime

payload = {
    "user_id": 1,
    "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
token = jwt.encode(payload, "STRONG_SECRET_KEY_HERE", algorithm="HS256")

# 検証
data = jwt.decode(token, "STRONG_SECRET_KEY_HERE", algorithms=["HS256"])
Bash
pip install PyJWT

JWT設計で守るべきルールは3つです。

  • 必ず有効期限(exp)を設定する — 期限なしのトークンは漏洩時に永久に悪用される
  • algorithm を明示的に指定する — 指定しないと “none” アルゴリズム攻撃のリスクがある
  • 十分に強い秘密鍵を使うsecrets.token_hex(32) で生成した値を環境変数に格納
💡 Tip

アクセストークンの有効期限は15分〜1時間が目安です。長期間のログイン維持にはリフレッシュトークンを併用し、アクセストークンは短命に保ちましょう。

④ SQLインジェクション対策

SQLインジェクションは、最も古典的かつ今でも最も多い脆弱性のひとつです。ユーザー入力をSQL文に直接結合すると、攻撃者がデータベースを自在に操作できてしまいます。

NG例:

NG
# 絶対にやってはいけない
query = "SELECT * FROM users WHERE name='" + name + "'"
cursor.execute(query)

この場合、nameadmin' OR '1'='1 と入力されると、全ユーザーのデータが漏洩します。

OK例:

OK
# パラメータバインディングを使う
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))

パラメータバインディングを使えば、ユーザー入力は「SQL文の一部」ではなく「データ」として安全に処理されます。Django ORMやSQLAlchemyを使う場合はデフォルトでこの方式ですが、raw SQL を書く場面では必ずパラメータバインディングを使うことを徹底してください。

⚠️ よくある落とし穴

f-string(f"SELECT ... WHERE name='{name}'")も文字列結合と同じく危険です。見た目がきれいでも、SQLインジェクションのリスクはまったく変わりません。

⑤ XSS対策

XSS(クロスサイトスクリプティング)は、ユーザーの入力をそのまま画面に表示することで、悪意のあるスクリプトが実行される攻撃です。

NG例:

NG
# ユーザー入力をそのまま返す
return user_input
# 攻撃者が <script>alert(1)</script> を入力すると実行される

OK例:

OK
import html

safe_output = html.escape(user_input)
return safe_output
# <script> → <script> に変換され無害化

XSS対策の原則は「入力ではなく出力を信用しない」です。データベースに保存する段階ではなく、HTMLに出力する段階でエスケープするのが正しいアプローチです。DjangoのテンプレートエンジンやJinja2はデフォルトでHTMLエスケープが有効ですが、|safe フィルタや mark_safe() を使う際は特に注意が必要です。

💡 Tip

Djangoテンプレートで {{ variable|safe }} を使う場合は、必ず信頼できるデータのみに限定してください。ユーザー入力に |safe を適用することは、XSSの入口を自分で開けるのと同じです。

⑥ CSRF対策

CSRF(クロスサイトリクエストフォージェリ)は、ログイン済みのユーザーに意図しないリクエストを送信させる攻撃です。対策の基本は、フォームにサーバーが発行したトークンを埋め込み、送信時に検証することです。

Djangoの場合(組み込み機能):

Django テンプレート
<form method="POST">
    {% csrf_token %}
    <input type="text" name="data">
    <button type="submit">送信</button>
</form>

FastAPIの場合:

FastAPI 例
from itsdangerous import URLSafeSerializer

s = URLSafeSerializer("secret-key")
csrf_token = s.dumps("session_id")

# 検証
try:
    data = s.loads(received_token)
except Exception:
    raise HTTPException(status_code=403)
Bash
pip install itsdangerous

DjangoはCSRF対策がデフォルトで有効なため、{% csrf_token %} を入れ忘れない限り安全です。FastAPIなどフレームワーク側にCSRF保護がない場合は、自前でトークンの発行・検証を実装する必要があります。

⑦ レート制限(Rate Limiting)

レート制限は、一定時間内のリクエスト数を制限する仕組みです。ログインのブルートフォース攻撃、APIの乱用、BOTによる大量リクエストを防ぎます。

FastAPI + slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@app.get("/api/data")
@limiter.limit("10/minute")
def get_data():
    return {"status": "ok"}
Bash
pip install slowapi

Djangoの場合は django-ratelimit が定番です。どちらのフレームワークでも、特に以下のエンドポイントにはレート制限を必ず設定しましょう。

  • ログイン — ブルートフォース攻撃の直接的な標的
  • パスワードリセット — メール爆撃の防止
  • API エンドポイント — 乱用とコスト増加の防止
  • 登録フォーム — スパムアカウント作成の防止
💡 Tip

レート制限は「攻撃を完全に防ぐ」ものではなく「攻撃コストを大幅に引き上げる」ものです。Nginx側でもIPベースのレート制限を併用すると、アプリケーション層に到達する前にブロックできてさらに効果的です。

⑧ ログ設計 — 出してはいけない情報

ログは障害調査やセキュリティ監視に不可欠ですが、ログに出してはいけない情報があります。パスワード、トークン、APIキー、セッションIDをログに書き出すと、ログファイルが攻撃の突破口になります。

NG例:

NG
# 絶対にやってはいけない
print(f"Login: user={username}, password={password}")
logging.info(f"Token: {api_token}")

OK例:

OK
import logging

logging.info("Login attempt: user=%s, ip=%s", username, request_ip)
logging.warning("Failed login: user=%s, ip=%s", username, request_ip)

ログ設計のルールはシンプルです。

  • ログ禁止:パスワード、トークン、APIキー、Cookie、個人情報
  • ログ推奨:ユーザーID、IPアドレス、アクション名、タイムスタンプ、結果(成功/失敗)
⚠️ よくある落とし穴

本番環境で DEBUG=True のままデプロイすると、エラー画面にスタックトレース・環境変数・DB接続情報が表示されます。Djangoの場合、本番では必ず DEBUG=False に設定してください。

⑨ シークレット管理

APIキー、データベースパスワード、JWT秘密鍵などのシークレットは、ソースコードに直接書いてはいけません。コードがGitHubに公開された瞬間にすべてが漏洩します。

NG例:

NG
# コードに直接書く → Git公開で即漏洩
SECRET_KEY = "abc123superSecret"
DB_PASSWORD = "production_password"

OK例:

OK
import os
from dotenv import load_dotenv

load_dotenv()

SECRET_KEY = os.getenv("SECRET_KEY")
DB_PASSWORD = os.getenv("DB_PASSWORD")
Bash
pip install python-dotenv

.env ファイルの例:

.env
SECRET_KEY=9f3c0c6d61c3c2e7b2c5a2b41a6f9d88
DB_PASSWORD=strong_random_password_here

そして .gitignore.env を必ず追加してください。

💡 Tip

GitHubには「Secret Scanning」機能があり、主要サービスのAPIキーがコミットされると自動検知・無効化されます。しかし、検知される前に攻撃者がスキャンしている可能性もあるため、そもそもコミットしないことが最善の防御です。

⑩ 環境変数による設定分離

開発環境(dev)、ステージング(staging)、本番環境(production)で同じ設定を使うと、意図しない事故が起きます。たとえば開発用のDEBUGモードが本番で有効になったり、テスト用のDB接続先で本番データを操作してしまったりします。

settings.py
import os

ENV = os.getenv("ENV", "development")

if ENV == "production":
    DEBUG = False
    ALLOWED_HOSTS = ["example.com"]
else:
    DEBUG = True
    ALLOWED_HOSTS = ["*"]

環境変数で設定を分離することで、コードを変更せずに環境ごとの挙動を切り替えられます。Djangoの場合は django-environ、FastAPIの場合は pydantic-settings を使うとさらに型安全に管理できます。

セキュリティチェックリストとよくある事故

プロジェクトのリリース前に、以下のチェックリストを確認してください。

  • □ 乱数生成に secrets を使っているか
  • □ パスワードはbcrypt/Argon2でハッシュ化しているか
  • □ JWTに有効期限(exp)を設定しているか
  • □ SQL文でパラメータバインディングを使っているか
  • □ HTML出力時にエスケープしているか
  • □ フォームにCSRFトークンがあるか
  • □ ログイン・APIにレート制限を設定しているか
  • □ ログにパスワード・トークンを出力していないか
  • □ シークレットを環境変数で管理しているか
  • □ 本番環境でDEBUG=Falseになっているか

実務で最も多いセキュリティ事故も把握しておきましょう。

順位事故内容対策
1位シークレットのGit公開.envと.gitignoreの徹底
2位本番でDEBUG有効環境変数での設定分離
3位SQL文字列結合パラメータバインディング
4位JWT有効期限なしexpの必須設定
5位レート制限なしslowapi/django-ratelimit

この5つを防ぐだけで、実務で遭遇するセキュリティ事故の大半をカバーできます。

よくある質問(FAQ)

Q:Pythonセキュリティの最低限は?

secrets(安全な乱数)、bcrypt(パスワード保存)、環境変数(シークレット管理)、パラメータバインディング(SQL安全)、html.escape(XSS防止)の5つです。この5つだけでも、セキュリティ未対策のアプリケーションとは大きな差が生まれます。

Q:Djangoを使っていれば安全ですか?

Djangoのデフォルト設定はセキュリティが強固です。CSRFトークン、XSSエスケープ、SQLインジェクション対策がすべて組み込まれています。ただし DEBUG=True の本番放置、|safe フィルタの乱用、raw SQLの不注意な使用など、設定ミスによる脆弱性は多発しています。

Q:JWTは必ず必要ですか?

いいえ。小規模なWebアプリではセッションベースの認証で十分です。JWTが有効なのは、マイクロサービス構成やSPA(Single Page Application)など、ステートレスな認証が必要な場合です。

Q:レート制限は必要ですか?

外部に公開するAPIやログインフォームには必須です。内部専用のスクリプトやバッチ処理には不要な場合もありますが、公開エンドポイントにレート制限がないと、BOTやブルートフォース攻撃の格好の標的になります。

Q:実務で一番多い事故は何ですか?

シークレット(APIキー・パスワード)のGitHub公開です。GitHubにはAPIキーを自動スキャンするBOTが常時巡回しており、公開から数分以内に悪用されるケースもあります。

まとめ

Pythonで安全なWebツールを開発するために必要なのは、高度な暗号技術ではなく基本設計の徹底です。本記事で紹介した10パターンの中でも、特に重要な5つを再確認します。

  1. secrets で安全な乱数を生成する
  2. bcrypt でパスワードをハッシュ化する
  3. 環境変数でシークレットを管理する
  4. パラメータバインディングでSQLを安全にする
  5. html.escape でXSSを防ぐ

セキュリティは後付けではなく、最初に設計するもの。この原則を守ることが、プロフェッショナルなPython開発の基本です。

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です