サーバーレス・アーキテクチャの死角:Lambdaタイムアウトが招く「経済的DoS」と防御の真髄
クラウドネイティブな環境におけるペネトレーションテストにおいて、我々攻撃側が最も好む「設定の綻び」の一つが、AWS Lambda等のサーバーレス関数におけるタイムアウト設定の放置だ。
多くのアーキテクトは「コードのバグ」には過敏だが、「インフラの制約」が攻撃の武器になることには鈍感すぎる。今回は、単なるコスト増大に留まらない、サーバーレス環境における「経済的DoS(Economic Denial of Service)」の深淵と、それを防ぐための防衛層アーキテクチャについて語る。
1. タイムアウト設定は、単なる「守り」ではない
Lambdaのデフォルトタイムアウト(3秒)を深く考えずに引き延ばすことは、攻撃者に対して「サンドボックスを占有する権利」を無償で提供しているに等しい。
例えば、外部APIを呼び出す関数でタイムアウトを15分に設定しているとする。攻撃者は、遅延応答を返す不正なエンドポイントを仕込み、そこへリクエストを誘発させる。関数は15分間「待機」し続け、その間、同時実行数の枠を一つ占有し続ける。これを数千回繰り返せば、正規のユーザーのリクエストは全て429 Too Many Requestsで弾かれる。これが「経済的DoS」のメカニズムだ。
低レイヤからの警告:なぜ「待機」が致命的なのか
この背後には、実行環境のメモリ枯渇リスクも潜んでいる。Lambdaの実行環境は、呼び出しごとにコンテナが再利用(Warm Start)される。タイムアウトによるハングアップ状態が重なると、ガベージコレクションが追いつかず、メモリリークの温床となる。これは単なるタイムアウトの問題ではなく、OSレベルのプロセス管理の脆弱性を突いているのと同じだ。
2. 実践的防御:同時実行数制限による「バルクヘッド」設計
リソース保護の鉄則は、船舶の隔壁(Bulkhead)のように損害を局所化することだ。
AWSにおいて、最も強力な防衛手段は関数レベルでのReserved Concurrency(予約済み同時実行数)の設定である。これを適切に設定することで、特定の関数が攻撃を受けてハングアップしても、他の関数やサービスの稼働を完全に保護できる。
以下に、Infrastructure as Code(Terraform)を用いた、堅牢な防御設定のサンプルを示す。
# Lambdaの同時実行数を制御し、万が一の攻撃時にもシステム全体への影響を最小化する
resource "aws_lambda_function" "secure_processor" {
function_name = "data-processor"
timeout = 30 # デフォルトの3秒を過信せず、処理時間に合わせた厳格な値を設定
memory_size = 512 # メモリ設定も最小限に留め、メモリ消費攻撃を抑制
# 予約済み同時実行数の設定:これ以上リクエストを受け付けないように制限する
# 全体の同時実行数から切り離すことで、システム全体の枯渇を防ぐ
reserved_concurrent_executions = 50
}
3. 生成AI時代の新たな脅威:プロンプトインジェクションとタイムアウト
現在、我々が注視しているのは、LLMをバックエンドに持つLambda関数の脆弱性だ。
プロンプトインジェクションにより、LLMが無限ループや極めて複雑な推論を強制されると、処理時間は指数関数的に増大する。
この場合、タイムアウト設定だけでは不十分だ。アプリケーション層で「ガードレイル」を設ける必要がある。
Node.jsでのタイムアウト実装例
単なる設定値だけでなく、コード内でも明示的にタイムアウトを制御し、例外処理を徹底することが、高度なペネトレーションテストに対する唯一の対抗策となる。
// プロンプトインジェクションによる処理時間増大を防ぐためのタイムアウトハンドリング
const axios = require('axios');
exports.handler = async (event) => {
// Promise.raceを使用して、ネットワークレベルでのタイムアウトを強制する
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Process Timeout: Guardrail Triggered')), 5000)
);
try {
const response = await Promise.race([
callExternalLLM(event.prompt),
timeoutPromise
]);
return response;
} catch (err) {
console.error(`[Security Alert] Request aborted: ${err.message}`);
// ここでCloudWatchへのカスタムメトリクスを送信し、検知をトリガーする
return { statusCode: 504, body: 'Service Unavailable' };
}
};
4. チーフホワイトハッカーとしての提言
脆弱性診断においては、CVE番号を追うことも重要だが、アーキテクチャの「設計上の意図」が、現実の攻撃シナリオとどう衝突するかをシミュレーションすることこそが、真の防衛である。
1. 厳格なタイムアウトの徹底: プロセスは常に「失敗すること」を前提に設計せよ。
2. バルクヘッドの導入: サービスごとに同時実行数を物理的に隔離し、攻撃の波及を防げ。
3. オブザーバビリティの拡張: タイムアウト発生数をカスタムメトリクスとして監視し、異常なスパイクを検知した瞬間に、自動で特定のAPIキーをブロックするガードレイルを構築せよ。
サーバーレスは魔法の箱ではない。それは、あなたが書いたコードと設定値が、インフラの境界線を定義する「契約」そのものなのだ。その契約が甘ければ、攻撃者はそこを突き、あなたの財布とサービスを食いつぶすだろう。
次のリリースで、あなたのLambdaのタイムアウト設定が「なんとなく」決まっていないか、今一度確認してほしい。攻撃者は、まさにその「なんとなく」の隙間を狙っている。
コメント