【実務・中級編】 OWASP Top 10:2021 A04:2021-Insecure Designの脅威モデリング – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

エンジニア諸君、セキュリティは「後付けのパッチ」ではない。設計図を書く段階で勝負は決まっている。

OWASP Top 10:2021の『A04:2021-Insecure Design(設計上の欠陥)』は、正直言って、コードのバグを直すよりも遥かにタチが悪い。なぜなら、仕様そのものが「穴」を開けているからだ。認証や暗号の実装で、どれほど最新のライブラリを使おうが、設計が間違っていれば、それは強固な金庫にガバガバの穴が開いた壁を取り付けているようなものだ。

今日は、現場のエンジニアが陥りやすい罠と、それをSTRIDEモデルでどう潰していくか、そして「正しく実装する」ための具体的な武器を授けよう。

—

1. STRIDEモデルで「設計の穴」を可視化せよ

脅威モデリングと聞くと難しく感じるかもしれないが、要は「こいつが攻撃者だったら、どこを突いてくるか?」を妄想する遊びだ。STRIDEはそれを体系化したものに過ぎない。

  • Spoofing(なりすまし): 認証をどう突破するか?
  • Tampering(改ざん): リクエストやデータをどう細工するか?
  • Repudiation(否認): 「自分じゃない」とどう言い逃れるか?
  • Information Disclosure(情報漏洩): どこに機密が落ちているか?
  • Denial of Service(サービス拒否): どうやってサーバーをパンクさせるか?
  • Elevation of Privilege(権限昇格): 一般ユーザーがどうやって管理者に化けるか?

特に、認証基盤と暗号の実装において、この「なりすまし」と「情報漏洩」は致命傷になる。

—

2. 暗号理論の使い分け:AESとECCの「現場の鉄則」

暗号化の基本原則はシンプルだ。「重い処理は公開鍵で、速い処理は共通鍵で」。これが鉄板だ。

共通鍵暗号(AES-256-GCM)

データの保存(ストレージ)や、通信の高速化に使う。絶対に「ECBモード」や「CBCモード(初期化ベクトルを使い回すケース)」を使ってはいけない。GCMモード一択だ。これには認証付き暗号機能が含まれており、データが改ざんされたら即座にエラーを吐いてくれる。

# Python: AES-256-GCMによるセキュアな暗号化
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

# 鍵は環境変数から読み込む(絶対にソースコードに直書きしない)
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)

# ノンス(Nonce)は毎回ランダムに生成せよ!再利用は即死を意味する
nonce = os.urandom(12) 
data = b"confidential_data"

# 暗号化
ciphertext = aesgcm.encrypt(nonce, data, None)

# 復号時は nonce + ciphertext をセットで扱う
print(f"Encrypted: {ciphertext.hex()}")

公開鍵暗号(ECC – 楕円曲線暗号)

RSAはもう古い。鍵長が長くなりすぎて効率が悪い。今の主流は圧倒的にECC(Curve25519など)だ。短い鍵長でRSA-3072ビットと同等のセキュリティを確保できる。

—

3. 実践:設計上の致命傷を防ぐ「セキュアな認証実装」

よくある設計の失敗は、「ユーザーIDだけで権限を判定する」ことだ。これをやると、URLのパラメータをいじるだけで他人のデータが見える(IDOR)攻撃を食らう。

悪い設計の例

GET /api/user?id=100 でデータを返す。これだと id を 101 に変えるだけで他人の個人情報が丸見えだ。

正しい設計の例

セッションまたはJWTから「現在のログインユーザー」を特定し、データベース側で WHERE user_id = CURRENT_USER_ID を強制する。

// Node.js (Express) でのセキュアなリクエスト処理例
app.get('/api/profile', authenticateToken, (req, res) => {
    // req.user は JWTからデコードされた信頼できるユーザー情報
    const userId = req.user.id; 

    // クエリパラメータを一切信用せず、セッション情報からのみ検索する
    db.query('SELECT * FROM profiles WHERE user_id = ?', [userId], (err, results) => {
        if (err) return res.status(500).send('Internal Server Error');
        res.json(results);
    });
});

—

4. 最後に:インフラ層での防御(Nginxの設定)

アプリケーションがどれほど堅牢でも、サーバー設定が甘ければ意味がない。最低限、以下のヘッダーは強制しておくべきだ。

# /etc/nginx/conf.d/security.conf
# クリックジャッキング防止
add_header X-Frame-Options "DENY";

# XSS対策
add_header X-XSS-Protection "1; mode=block";

# MIMEタイプスニッフィング防止
add_header X-Content-Type-Options "nosniff";

# HSTS(HTTPSを強制)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

結論

セキュリティを「ルール」で片付けるな。「攻撃者がどうやって自分を出し抜こうとしているか」を想像し、その道を物理的に塞ぐのが、我々エンジニアの仕事だ。

コードを書く前に、ホワイトボードに向かってSTRIDEモデルで自問自答せよ。その設計は、悪意あるユーザーに対して十分すぎるほど冷酷か?もし迷いがあるなら、それは実装するべきではない。

現場からは以上だ。次は具体的な脅威分析のワークショップで会おう。健闘を祈る。

コメント

タイトルとURLをコピーしました