【実務・中級編】暗号学的署名を用いたAPIリクエストの改ざん検知 – アプリケーションセキュリティ & 安全な開発防御ガイド

XSSの「その先」へ。API改ざんを許さない署名検証という最後の砦

やあ。現場でコードを書いていると、「XSS対策はサニタイズ(エスケープ)さえすれば完璧」と安心しきっているエンジニアによく出会う。確かに、DOM型や反射型のXSSを防ぐことは基本中の基本だ。だが、今の攻撃者はそんな「入り口」だけで満足はしない。

彼らが狙うのは、ブラウザ上のスクリプトを超えた「APIの背後にあるロジック」だ。

たとえフロントエンドが堅牢でも、APIリクエストのパラメータが途中で書き換えられたらどうなる? 例えば、決済金額やユーザーIDが改ざんされれば、セキュリティホールは即座にビジネスの破滅へと直結する。今回は、XSSのその先を見据えた、「APIリクエストの改ざん検知」という防壁について、泥臭い実務の視点から解説する。

—

なぜ「SSL/TLS」だけでは不十分なのか

「HTTPSを使っているから通信は安全だ」という意見は、半分正解で半分は甘い。HTTPSは「通信経路の暗号化」であって、「データの完全性(改ざん検知)」をAPIレベルで保証するものではない。

攻撃者がプロキシツール(Burp Suite等)を使ってリクエストを傍受し、パラメータを書き換えて再送する「リプレイ攻撃」や「改ざん攻撃」に対しては、HTTP層の保護だけでは無力だ。ここで登場するのが、HMAC(Hash-based Message Authentication Code)を用いた署名検証である。

—

実践:HMACによるAPI署名検証の実装

やり方はシンプルだ。「リクエストの中身」と「秘密鍵」を組み合わせ、ハッシュ値を生成してヘッダーに含める。サーバー側は同じ計算を行って値が一致するかを確認するだけだ。

1. Python (サーバー側) の検証ロジック

まずは、FlaskやFastAPIで使える検証ロジックの例だ。

import hmac
import hashlib
import time

本来は環境変数から読み込むこと。絶対にソースコードに直書きしない。
SECRET_KEY = b’super-secret-key-that-no-one-knows’

def verify_signature(request_body, received_signature, timestamp):
# 1. リプレイ攻撃対策:タイムスタンプが古すぎないかチェック(5分以内など)
if abs(time.time() – float(timestamp)) > 300:
return False

# 2. 署名の再計算
message = f”{timestamp}:{request_body}”.encode(‘utf-8’)
expected_signature = hmac.new(SECRET_KEY, message, hashlib.sha256).hexdigest()

# 3. 定数時間比較(Timing Attackを防ぐため、必ずhmac.compare_digestを使う)
return hmac.compare_digest(expected_signature, received_signature)

2. JavaScript (クライアント側) の署名生成

クライアント(ブラウザやモバイルアプリ)側で署名を生成する処理だ。

async function signRequest(body, secretKey) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const encoder = new TextEncoder();
const message = ${timestamp}:${JSON.stringify(body)};

// Web Crypto APIを使用して署名生成
const key = await crypto.subtle.importKey(
“raw”, encoder.encode(secretKey), { name: “HMAC”, hash: “SHA-256” }, false, [“sign”]
);
const signature = await crypto.subtle.sign(“HMAC”, key, encoder.encode(message));

// ArrayBufferを16進数文字列に変換
return {
signature: Array.from(new Uint8Array(signature)).map(b => b.toString(16).padStart(2, ‘0’)).join(”),
timestamp: timestamp
};
}

—

現場で陥りやすい「落とし穴」

コードを書いて終わり、ではないのがセキュリティの辛いところだ。現場でよく見る「あちゃー」なミスを共有しておく。

  • タイミング攻撃(Timing Attack)への無自覚:

文字列比較に == を使っていないか? 処理時間の差から秘密鍵を特定されるリスクがある。必ず hash_equals (PHP) や hmac.compare_digest (Python) のような定数時間比較関数を使うこと。

  • 秘密鍵の使い回し:

開発・ステージング・本番で同じ鍵を使うのはNGだ。また、クライアントに秘密鍵をハードコードするのも論外。モバイルアプリの場合は、鍵の難読化やセキュアエレメント(Tee/KeyChain)の利用を検討してくれ。

  • 「とりあえずHTTPS」で満足:

WAF(AWS WAFなど)の設定で、特定のヘッダーを厳格にチェックするポリシーを組み合わせよう。署名がないリクエストは、アプリケーションに到達する前に破棄するのが鉄則だ。

—

まとめ:防御の多層化という思想

XSS対策は「Webアプリの玄関の鍵」をかける行為だ。そして今回紹介した署名検証は、「金庫に二重のロックをかける」行為に相当する。

もしXSSでトークンが盗まれても、APIレベルでリクエストが署名されていなければ、攻撃者は自由なリクエストを送ることができない。これが、僕たちが目指すべき「多層防御」の姿だ。

セキュリティは、ツールを入れたら終わりではない。攻撃者の思考をトレースし、彼らが次にどこを壊そうとするかを想像し続けること。その泥臭い想像力こそが、君たちのシステムを最強の要塞へと変えるはずだ。

困ったときは、またいつでも相談してくれ。手を動かすことを止めるなよ。

コメント

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