Перейти к содержимому

Почему Sha-256(секрет + данные) уязвима к атаке расширения сообщения

Почему подпись `SHA-256(секрет + данные)` можно подделать без знания секретного ключа

Самодельная схема авторизации часто выглядит убедительно: сервер передаёт клиенту данные и вычисленную рядом подпись. Например:

```text
user=guest&role=reader
sig=...
```

Предполагается, что пользователь может увидеть и изменить параметры, но не знает секретный ключ сервера. Значит, после замены `role=reader` на `role=admin` он якобы не сможет получить правильную подпись.

На практике такая защита может оказаться полностью ненадёжной. Проблема не в том, что SHA-256 удалось взломать, и не в подборе ключа. Уязвимость появляется из-за неправильного способа применения хеш-функции:

```text
SHA-256(secret + message)
```

Для некоторых хеш-функций, включая SHA-256, SHA-1 и MD5, такая конструкция допускает атаку расширения сообщения - length extension attack. Она позволяет добавить данные в конец исходного сообщения и вычислить новую корректную подпись, не зная секрета.

Как устроена уязвимая схема

Пусть сервер использует ключ:

```text
super-secret-key-42
```

А подписываемое сообщение выглядит так:

```text
user=guest&role=reader
```

Сервер рассчитывает подпись:

```text
sig = SHA-256(secret + message)
```

Клиент получает и исходные данные, и значение `sig`. Секрет ему неизвестен. На первый взгляд изменить роль невозможно: новая строка потребует новой подписи.

Однако SHA-256 работает не как "чёрный ящик", полностью скрывающий промежуточные данные. Алгоритм обрабатывает сообщение блоками по 64 байта. После каждого блока он обновляет внутреннее состояние из восьми 32-битных слов. Состояние после последнего блока и становится итоговым хешем.

Иными словами, 256-битный результат SHA-256 - это не отдельная криптографическая печать, а финальное состояние вычисления, представленное в виде 64 шестнадцатеричных символов.

Что именно узнаёт атакующий

Если у злоумышленника есть:

```text
SHA-256(secret + message)
```

то он фактически располагает внутренним состоянием SHA-256 после обработки исходной последовательности. Восстановить эти значения не требуется: они уже записаны в подписи.

Дайджест длиной 64 hex-символа разбивается на восемь частей по 8 символов. Каждая часть соответствует одному 32-битному регистру состояния:

```text
H0 H1 H2 H3 H4 H5 H6 H7
```

Получив эти значения, специальная реализация SHA-256 может продолжить вычисление так, будто первые блоки уже были обработаны. Злоумышленник указывает длину ранее обработанных данных, а затем добавляет собственный суффикс.

Например, к сообщению можно попытаться присоединить:

```text
&role=admin
```

Новая строка будет иметь вид:

```text
user=guest&role=reader
[служебное дополнение SHA-256]
&role=admin
```

При этом секрет в начало строки добавляет уже сервер. Первые байты его версии и версии атакующего совпадут, поэтому сервер получит тот же промежуточный результат, после чего обработает добавленный суффикс.

Как работает дополнение SHA-256

Перед обработкой SHA-256 дополняет сообщение до размера, кратного 64 байтам. Формат дополнения фиксирован:

1. добавляется байт `0x80`;
2. затем идут нулевые байты;
3. в конце записывается исходная длина сообщения в битах - восьмью байтами.

В рассматриваемом примере:

- секрет занимает 19 байт;
- исходные параметры - 22 байта;
- общая длина составляет 41 байт.

После добавления служебных данных последовательность достигает 64 байт, то есть ровно одного блока SHA-256.

Итоговая подпись является состоянием алгоритма после обработки этого блока. Для продолжения вычисления не нужно знать содержимое блока: достаточно восьми регистров, зашифрованных в самой подписи.

Атакующий восстанавливает формат дополнения, предполагая длину секрета. Важно, что точную длину ключа иногда даже не требуется знать заранее: можно перебрать разумный диапазон вариантов и отправить серверу несколько запросов. Правильный вариант будет принят, остальные - отклонены.

Почему сервер принимает подделку

Сервер при проверке делает примерно следующее:

```text
SHA-256(secret + полученное_сообщение)
```

Если в качестве сообщения передать исходные данные, дополнение и добавленный суффикс, сервер фактически обработает:

```text
secret + message + padding + suffix
```

Первые 64 байта будут полностью совпадать с тем блоком, который уже был учтён при создании исходной подписи. Далее сервер продолжит расчёт с того же состояния и получит именно тот хеш, который заранее вычислил атакующий.

SHA-256 при этом остаётся математически стойкой хеш-функцией. Не происходит ни коллизии, ни обращения хеша, ни подбора секретного ключа. Ошибка заключается в том, что хеш-функцию применили как MAC, хотя для этого она не предназначена.

Почему простая замена параметра может не сработать напрямую

Есть важная практическая деталь. Добавление `&role=admin` не всегда автоматически означает, что приложение выберет значение `admin`. Всё зависит от обработчика параметров.

Некоторые серверы используют первое значение ключа:

```text
role=reader&role=admin
```

Другие выбирают последнее:

```text
role=reader&role=admin
```

Третьи превращают повторяющиеся параметры в массив или отклоняют запрос целиком. Поэтому успешность атаки определяется не только криптографией, но и тем, как конкретный фреймворк разбирает параметры.

Кроме того, дополнение SHA-256 содержит непечатаемые байты. Их нельзя бездумно вставлять в URL или cookie: данные придётся корректно кодировать, например в percent-encoding или другом безопасном формате передачи.

Почему нельзя просто скрыть подпись

Иногда пытаются решить проблему, спрятав подпись в cookie, добавив необычное кодирование или усложнив формат параметров. Это не помогает, если атакующий всё равно может получить исходные данные и их хеш.

Секретность формата не заменяет криптографическую защиту. Подпись должна оставаться безопасной даже в ситуации, когда злоумышленник знает:

- алгоритм;
- структуру сообщения;
- исходные данные;
- множество корректных подписей;
- длину или примерную длину ключа.

Именно это является базовым принципом современной криптографии: алгоритм не должен зависеть от сокрытия своей конструкции.

Как исправить схему

Правильный вариант - использовать HMAC:

```text
HMAC-SHA-256(secret, message)
```

HMAC специально создан для аутентификации сообщений с использованием секретного ключа. В упрощённом виде он устроен как двухэтапная конструкция:

```text
HMAC(K, M) =
SHA-256((K ⊕ opad) || SHA-256((K ⊕ ipad) || M))
```

Внутренний и внешний хеши не позволяют трактовать итоговый результат как состояние простого вычисления `SHA-256(secret + message)`. Поэтому классическая атака расширения сообщения к HMAC не применяется.

На практике следует использовать готовую библиотечную реализацию HMAC, а не писать формулу вручную. Дополнительно нужно сравнивать подписи функцией constant-time comparison, чтобы не создавать побочный канал по времени выполнения.

На что обратить внимание при аудите

При проверке собственного кода стоит найти все конструкции, похожие на:

```text
hash(secret + data)
hash(data + secret)
```

Первая форма уязвима к length extension для Merkle-Damgård-хешей. Вторая обычно не имеет именно этой проблемы, но тоже не считается универсальной заменой HMAC: ошибки в формате, кодировке, обработке длины и сравнении могут привести к другим уязвимостям.

Нужно также проверить:

- используется ли стандартный HMAC;
- одинаково ли кодируются данные при создании и проверке подписи;
- защищена ли подпись от повторного использования;
- присутствуют ли срок действия и идентификатор сессии;
- проверяется ли целостность всей структуры, а не отдельных полей;
- применяется ли безопасное сравнение подписей;
- нельзя ли изменить порядок, тип или количество параметров.

Если подпись используется для cookies, желательно подписывать не "сырой текст", а чётко сериализованную структуру с однозначным форматом. Для критичных данных полезно включать в неё срок действия, назначение токена и контекст приложения.

Итог

Конструкция `SHA-256(secret + message)` выглядит как цифровая подпись, но криптографической подписью не является. Финальный дайджест SHA-256 содержит внутреннее состояние алгоритма, а структура хеша позволяет продолжить вычисление с добавленными данными.

Поэтому злоумышленник, имеющий исходное сообщение и подпись, в некоторых условиях способен сформировать корректный хеш для расширенной строки без знания секрета. Надёжное решение - использовать HMAC-SHA-256 либо другой стандартный механизм аутентификации сообщений, предоставляемый проверенной криптографической библиотекой.

Прокрутить вверх