2011-12-29 28c3 – атака в/у hash таблици при web платформи
by Vasil KolevМалко за гледаните лекции от 28c3 – ще бъдат в няколко post-а по ред причини.
Ще започна с една лекция, която описа в общи линии нов проблем в повечето web-facing езици/framework-ове в хеш реализациите, за който даже написах малко код.
Къде е проблемът – повечето такива езици (в случая – perl, php, python, java, ruby, asp.net) използват hash-ове (познати още като асоциативни масиви), за да подадат към програмиста параметрите, които са дошли от HTTP request-а (най-вече от POST-а). Почти всички го реализират, използвайки стандартни hash таблици с 32 или 64битов hash (не-криптографски, т.е. даже и CRC32 би свършил работа, повечето ползват нещо, писано от DJB, например DJBX33A).
По принцип insert-а на нов елемент в такава таблица е с ниска/константна сложност, но ако вмъкваме само елементи, които дават същия hash, сложността става максималната възможна, т.е. O(n) и съответно вмъкването на n елемента ще е от порядъка на O(n^2).
Накратко, ако подадем (например) през POST параметри, които се хешират към същата стойност, времето, което ще отнеме на езика/framwork-а да обработи заявката (преди да стигне до вашето приложение) ще е бая, като тестовете при мен показаха нещо от порядъка на 1 минута за заявка с 62000 параметъра и голяма около 400kb. В лекцията (има и запис, mirror-нат при мен) има повече подробности и тестове.
Реших да видя доколко мен ме лови тоя проблем. Като за начало трябваше да си напиша генератор на тия стрингове (ВНИМАНИЕ: грозен гаден код на C), което се оказа сравнително лесно – измъкнах от source на php от Zend/zend_hash.h тяхната функция, поразгледах кода и се оказа, че от hash функцията се взимат първите X бита, като X е в общи линии log2(N) (където N е броя елементи в стуктурата). Съответно ако ми трябват по-малко от 65536 collide-ващи стринга ми е достатъчно да търся колизии само в първите 16 бита. Моята некадърна програмка на машина със 192GB памет (трябваха и около 120GB) се справи за около 5 минути.
(пускам кода като пример, който може да го разбере би могъл да си го напише и сам сравнително лесно).
Имам да се поровя още малко, но в някои случаи debian-ската default-на конфигурация реже тия атаки (понеже не позволява повече от 1000 променливи в какъвто и да е request, което па от своя страна чупи малко (малоумен) софтуер), в някои не и повечето php-та, които човек може да намери по net-а ги лови тоя проблем. Реализирането на DoS-а за да направи реален проблем изисква прилично количество паралелни връзки и определено не е толкова опасно, колкото беше проблемът с Range request-ите от август, но пак не е приятно. Също така може да се атакува и през други места, например ако трябва да се сглоби json request или какъвто и да е hash, чиито ключове зависят от данни, подадени от потребителя.
Решението е да се random-изира hash-а на всяка заявка, за което си написах една тестова програмка, която да пробва в/у такива подбрани string-ове hash, който освен всичко XOR-ва всеки 8-байтов блок от входа с един random seed. Мисля ако скоро не изкарат някакво хубаво решение да си patch-на директно php-то, но още не съм имал време да намеря подходящия entry point, в който да вкарам инициализацията на seed-а, така че хем да е различен за всеки request, хем да не счупи нещо по много лош начин (ако някой е бърникал по кода на php и знае къде, много моля да пише).
Та, добри хора, прочетете оригиналното advisory и се patch-вайте, ако правите нещо свързано с web най-вероятно проблемът ви засяга.
December 29th, 2011 at 23:19
Microsoft ще пускат пач за тая дупка днес. Дано и другите се усетят навреме.
December 30th, 2011 at 19:19
Забавно. Имам навика да забравям колко по-различно започва да изглежда софтуера, когато човек мисли за security.
Както и да е, идеята с random seed-а на всяка заявака не ми харесва по две причини – (1) random seed и (2) всяка заявка. Ето ти алтернативно предложение, ти ми кажи дали виждаш проблем с него:
Нека всеки хеш да си има уникален seed с който да се хешира. Така няма да ти се налага да менижираш тон глобално състояние покрай request-ите. Или не дай си боже, да си играеш с thread local простотии, ако ти трябват няколко нишки. Това решава втория проблем.
Остава въпроса какъв seed да ползваш. Очевидно всеки хеш може да си пази едно допълнително число, което да генерираш произволно. Това обаче (1) беше първия ми проблем и (2) кара хешовете да заемат (маргинално) повече памет. Вместо това, просто ползвай адреса на хеша. Или нещо производно от него. Струва ми се достатъчно произволен, за да не може да се познае.
Разбира се, тук ще има лек проблем при копирането на хеш (ще трябва да се rehash-ва или в крайна сметка да се пази seed-а), но това е заобиколимо.
Мнения? Друго проблеми в тази идея?
December 30th, 2011 at 19:33
@Стефан, имаш много повече хешове, отколкото request-и – всичко в езика ти е хеш и е по-добра идея да пазиш state per request, вместо да го добавяш за всеки хеш. Иначе, той не се променя след като го извадиш в началото на request-а (и може да се базира на псевдо-random (или прилично добър), който просто е seed-нат подходящо от начало, така че атакуващия да не може да предположи с какво да ти преебе функцията.
(под request разбирам http request, т.е. изпълняването на една заявка към web сървъра)
Иначе, хвърли един поглед на кода, то е доста проста промяна и според мен няма да се усети.
December 31st, 2011 at 11:34
Това, което ти убягва, е че може да нямаш request-и. Може да пишеш daemon. Или пък, може да правиш някакъв setup, след което да правиш fork, с който да отговаряш на request-а. Ако след fork-а правиш seed, тогава инвалидираш всички хешове преди форка. Мисли всички други езици (Python, PHP).
Въобще, това не ми изглежда като гъвкаво решение. :)
December 31st, 2011 at 11:35
%s/Python, PHP/Python, Ruby, JavaScript/
December 31st, 2011 at 11:43
Сега като се замисля, напълно достатъчно е да правиш това само за хеша с HTTP параметрите. Предполагам пак ще има уязвимост, ако attacker-а знае как може да те накара да конструираш хеш, но за това (обикновено) ще има нужда от кода ти.
December 31st, 2011 at 11:44
Ако пиша демон, ще му вкарам друг тип защити от подобни неща. Проблемът с per-hash seed и т.н. поне според мен е, че ще се затрудни сравняването на хешове и всякакви такива важни операции, които разчитат на това хешът на един елемент да си е все същия.
(да не говорим за хилядите външни модули със същото предположение)
(да не говорим, че ако пиша демон, дето поема заявки отвън определено няма да е на някой от тия езици)
December 31st, 2011 at 12:17
Бяха дали един хубав пример – ако трябва да си говори приложението с някой през JSON, за това пак се използват хешове, и може да се атакува оттам, т.е. вероятно някой с малко занимаване може да направи подобна атака в/у нещо, дето си говори с google maps или някой такъв service.