ソフトウェア開発
PHP におけるタイミング攻撃:実例 - なぜ `hash_equals()` が重要なのか?
Wordpress/Laravel/PHPのAPIトークンに対するタイミング攻撃の現実的な例を知りたいですか?タイミング攻撃に対するヒントやベストプラクティス対策を含めて、ここで取り上げます。
多くの開発者は、APIキーやトークン、その他の秘密の文字列をチェックする際、===や ==を使った単純な比較に頼っている。一見すると、これは合理的に見えますが、攻撃者によって比較のタイミングの違いが悪用される可能性があります。
この記事では、タイミング攻撃が概念的にどのように機能するかの現実的な例を説明します。
実際のシナリオ(単純化した例)
ある内部マイクロサービスが、クエリ・パラメータ・トークンを持つリクエストを受け付け、環境に保存されている GitHub API トークンと比較するとします。
安全でないコード (単純化):
$expected = getenv('GITHUB_API_TOKEN');$provided = $_GET['token'] ??'';if($provided === $expected) {} PHP
なぜこれが問題なのかは、次のセクションで説明します。
タイミング攻撃はどのように行われるのでしょうか?
もしトークン比較の実装が一文字ずつ比較し、最初のエラーですぐに中止するのであれば、 最初の文字が正しいリクエストは平均して少し時間がかかります。
したがって、正しい最初の文字は、正しくない最初の文字よりも平均して 少し長い処理時間をもたらす。同じことが2文字目以降にも適用される。
攻撃者は、各ポジションで可能なすべての文字をテストし、応答時間を頻繁に測定し、統計的に分析することで、これを利用します。平均時間が最も長く、比例して長い文字がおそらく正しい。このようにして、トークンは段階的に再構築される。
hash_equals()が正しい選択である理由
PHP は、hash_equals()による定数時間文字列比較関数を提供しています。これは、比較の実行時間が共通の接頭辞に依存しないことを保証します - 両方の文字列が同じ長さである限り。
安全な代替(Laravel、PHP、Wordpress用):
$expected = getenv('GITHUB_API_TOKEN'); $provided = $_GET['token'] ??'';if(hash_equals($expected, $provided)) {} PHP
注意: hash_equals()は、長さが等しい場合にのみ "時間非依存" となります。トークンの長さが一定になるように設計するのがよい習慣です (たとえば HMAC や固定長の UUID、ランダムな固定長の Base64 文字列など)。
ご存知ですか?UUID-V4だけが完全にランダムに生成されるため、セキュリティ・トークンによく使われます。UUID-V4は時間やデバイスに関連する情報を含まないため、予測不可能性が高く、パターンや予測に基づく攻撃から保護されます。UUID-V4が安全で推測が難しい識別子として好まれる理由はまさにここにある。
追加の防御策 -hash_equals()以上のもの
- トークンの長さを一定にする
UUID-V4など、固定長のトークンを使用する。攻撃者が長さを推測しても、文字そのものが安全である限り、あまり役に立ちません。
- レート制限
IPまたはアカウントごとにリクエストを制限する。タイミング攻撃には多くの測定が必要であるため、レート制限は攻撃者の労力とコストを大幅に増加させる。
- ロギングとモニタリング
通常とは異なるパターン(トークンの試行回数が多い、特定のパラメータが系統的に変化している)を認識し、自動的に対応します。
- ソルト/HMAC/署名
生の静的な鍵をオープンに比較する代わりに、署名されたトークン(秘密鍵付きHMAC)またはOAuthトークンを使用します。
- TLS/HTTPS
暗号化によって通信チャネルを保護する。サーバー内部へのタイミング攻撃は防げないが、基本的な要件である。
- トークンのローテーション
トークンの寿命を短くすることで、漏洩したトークンの利益を減らす。
結論
タイミング攻撃は純粋に理論的な問題ではなく、実際に調査されており、特に一般にアクセス可能なエンドポイントでは深刻な結果をもたらす可能性があります。良いニュース:PHP/Laravel/Wordpressの開発者にとって、最初の対策は非常に簡単です:
- 機密文字列の直接比較を
hash_equals()で置き換える。
- トークンの長さを一定にし、レートを制限し、ロギングを行い、トークンを安全に管理する。