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例:
import random
token = random.randint(100000, 999999) # 予測可能!
OK例:
import secrets
token = secrets.token_hex(16)
print(token)
# 例: 9f3c0c6d61c3c2e7b2c5a2b41a6f9d88
secrets モジュールはOSの暗号論的擬似乱数生成器(CSPRNG)を利用しており、出力の予測は事実上不可能です。トークン生成、CSRF対策、パスワードリセットなど、セキュリティに関わるすべての乱数は secrets を使いましょう。
secrets.token_urlsafe(32) を使えば、URLに安全に埋め込める形式のトークンが生成できます。パスワードリセットリンクの生成に最適です。詳しくは「Pythonでパスワード生成」も参考にしてください。
② パスワード保存 — 平文は論外
パスワードを平文でデータベースに保存するのは、最も致命的なセキュリティ事故のひとつです。DBが漏洩した瞬間、全ユーザーのパスワードが攻撃者の手に渡ります。
NG例:
password = "user_password"
db.save(password) # 平文保存 → 漏洩即終了
OK例:
import bcrypt
password = b"mypassword"
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
# 検証
is_valid = bcrypt.checkpw(password, hashed)
print(is_valid) # True
pip install bcrypt
SHA-256などの汎用ハッシュ関数を単体で使うのも危険です。SHA-256は高速すぎるため、攻撃者が1秒に数十億回のハッシュ計算を試行できてしまいます。bcryptやArgon2は意図的に計算を遅くする設計になっており、ブルートフォース攻撃のコストを桁違いに引き上げます。
「SHA-256でハッシュ化したから安全」は誤りです。パスワード保存には必ずbcrypt・Argon2・scryptのような意図的に遅いアルゴリズムを使ってください。ハッシュの基礎知識については「ランダム文字列8種の正体と使い分けガイド」で詳しく解説しています。
③ JWTの安全設計
JWT(JSON Web Token)はステートレスな認証に広く使われていますが、設定を誤ると深刻な脆弱性になります。
NG例:
import jwt
token = jwt.encode({"user_id": 1}, "secret") # 期限なし・弱いsecret
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"])
pip install PyJWT
JWT設計で守るべきルールは3つです。
- 必ず有効期限(exp)を設定する — 期限なしのトークンは漏洩時に永久に悪用される
- algorithm を明示的に指定する — 指定しないと “none” アルゴリズム攻撃のリスクがある
- 十分に強い秘密鍵を使う —
secrets.token_hex(32)で生成した値を環境変数に格納
アクセストークンの有効期限は15分〜1時間が目安です。長期間のログイン維持にはリフレッシュトークンを併用し、アクセストークンは短命に保ちましょう。
④ SQLインジェクション対策
SQLインジェクションは、最も古典的かつ今でも最も多い脆弱性のひとつです。ユーザー入力をSQL文に直接結合すると、攻撃者がデータベースを自在に操作できてしまいます。
NG例:
# 絶対にやってはいけない
query = "SELECT * FROM users WHERE name='" + name + "'"
cursor.execute(query)
この場合、name に admin' OR '1'='1 と入力されると、全ユーザーのデータが漏洩します。
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例:
# ユーザー入力をそのまま返す
return user_input
# 攻撃者が <script>alert(1)</script> を入力すると実行される
OK例:
import html
safe_output = html.escape(user_input)
return safe_output
# <script> → <script> に変換され無害化
XSS対策の原則は「入力ではなく出力を信用しない」です。データベースに保存する段階ではなく、HTMLに出力する段階でエスケープするのが正しいアプローチです。DjangoのテンプレートエンジンやJinja2はデフォルトでHTMLエスケープが有効ですが、|safe フィルタや mark_safe() を使う際は特に注意が必要です。
Djangoテンプレートで {{ variable|safe }} を使う場合は、必ず信頼できるデータのみに限定してください。ユーザー入力に |safe を適用することは、XSSの入口を自分で開けるのと同じです。
⑥ CSRF対策
CSRF(クロスサイトリクエストフォージェリ)は、ログイン済みのユーザーに意図しないリクエストを送信させる攻撃です。対策の基本は、フォームにサーバーが発行したトークンを埋め込み、送信時に検証することです。
Djangoの場合(組み込み機能):
<form method="POST">
{% csrf_token %}
<input type="text" name="data">
<button type="submit">送信</button>
</form>
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)
pip install itsdangerous
DjangoはCSRF対策がデフォルトで有効なため、{% csrf_token %} を入れ忘れない限り安全です。FastAPIなどフレームワーク側にCSRF保護がない場合は、自前でトークンの発行・検証を実装する必要があります。
⑦ レート制限(Rate Limiting)
レート制限は、一定時間内のリクエスト数を制限する仕組みです。ログインのブルートフォース攻撃、APIの乱用、BOTによる大量リクエストを防ぎます。
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"}
pip install slowapi
Djangoの場合は django-ratelimit が定番です。どちらのフレームワークでも、特に以下のエンドポイントにはレート制限を必ず設定しましょう。
- ログイン — ブルートフォース攻撃の直接的な標的
- パスワードリセット — メール爆撃の防止
- API エンドポイント — 乱用とコスト増加の防止
- 登録フォーム — スパムアカウント作成の防止
レート制限は「攻撃を完全に防ぐ」ものではなく「攻撃コストを大幅に引き上げる」ものです。Nginx側でもIPベースのレート制限を併用すると、アプリケーション層に到達する前にブロックできてさらに効果的です。
⑧ ログ設計 — 出してはいけない情報
ログは障害調査やセキュリティ監視に不可欠ですが、ログに出してはいけない情報があります。パスワード、トークン、APIキー、セッションIDをログに書き出すと、ログファイルが攻撃の突破口になります。
NG例:
# 絶対にやってはいけない
print(f"Login: user={username}, password={password}")
logging.info(f"Token: {api_token}")
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例:
# コードに直接書く → Git公開で即漏洩
SECRET_KEY = "abc123superSecret"
DB_PASSWORD = "production_password"
OK例:
import os
from dotenv import load_dotenv
load_dotenv()
SECRET_KEY = os.getenv("SECRET_KEY")
DB_PASSWORD = os.getenv("DB_PASSWORD")
pip install python-dotenv
.env ファイルの例:
SECRET_KEY=9f3c0c6d61c3c2e7b2c5a2b41a6f9d88
DB_PASSWORD=strong_random_password_here
そして .gitignore に .env を必ず追加してください。
GitHubには「Secret Scanning」機能があり、主要サービスのAPIキーがコミットされると自動検知・無効化されます。しかし、検知される前に攻撃者がスキャンしている可能性もあるため、そもそもコミットしないことが最善の防御です。
⑩ 環境変数による設定分離
開発環境(dev)、ステージング(staging)、本番環境(production)で同じ設定を使うと、意図しない事故が起きます。たとえば開発用のDEBUGモードが本番で有効になったり、テスト用のDB接続先で本番データを操作してしまったりします。
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つを再確認します。
secretsで安全な乱数を生成する- bcrypt でパスワードをハッシュ化する
- 環境変数でシークレットを管理する
- パラメータバインディングでSQLを安全にする
html.escapeでXSSを防ぐ
セキュリティは後付けではなく、最初に設計するもの。この原則を守ることが、プロフェッショナルなPython開発の基本です。

コメントを残す