
Преди година, на 21 март 2019 г., в в HackerOne постъпи много добър от . При внедряването на нулев байт (ASCII 0) в POST-параметър на един от API-запросите на уеб пощата, който връщаше HTTP пренасочване, в данните от пренасочването се забелязваха парчета неинициализирана памет, в която най-често излизаха фрагменти от GET-параметри и заглавки на други заявки към същия сървър.
Това е критична уязвимост, тъй като заявките съдържат, между другото, сесийни бисквитки. След няколко часа беше направен временно решение, което филтрираше нулевия байт (както по-късно стана ясно, това не беше достатъчно, тъй като оставяше възможност за инжектиране на CRLF /ASCII 13, 10, което позволява манипулиране на заглавките и данните в HTTP отговора, това е по-малко критично, но все пак неприятно). В същото време проблемът беше предаден на специалистите по сигурността и разработчиците за намиране и отстраняване на причините за възникването на бъга.
Пощата Mail.ru е много сложна апликация, в процеса на формиране на отговора могат да участват голямо количество различни фронтенд/бекенд компоненти, както open-source (голямо благодаря на всички разработчици на свободен софтуер), така и собствени разработки. Успяхме да изключим всички компоненти, освен nginx и openresty, и локализирахме проблема до извикването на в OpenResty скрипт, който се държеше не така, както очаквахме (включването на нулев байт или нов ред чрез GET параметри с rewrite в ngx_http_rewrite_module, който, според документацията, се използва и трябва да работи абсолютно аналогично, не беше успешно). Бяха отстранени възможните последици, добавено беше максимално строга филтрация и беше проверено, че филтрацията елиминира всички възможни вектори. Но механизмът, който доведе до изтичането на съдържание от паметта, остана загадка. След месец бъгрепортът беше затворен като разрешен, а разследването на причините за възникването му беше отложено за по-добри времена.
OpenResty е доста популярен плъгин, който позволява писането на Lua скриптове вътре в nginx и той се използва в няколко проекта на Mail.ru, затова проблемът не беше считан за решен. И след известно време се върнаха към него, за да разберат истинските причини, възможните последици и да съставят препоръки за разработчиците. В разследването на изходния код участваха и Установи се, че:
- В nginx, при използване на rewrite с потребителски данни, съществува възможност за directory traversal (и вероятно SSRF) в някои конфигурации, но това е известен факт и той трябва да бъде открит от статичните анализатори на конфигурации в и от Yandex (да, ние също го използваме, благодаря). При използване на OpenResty, такава възможност лесно може да се пропусне, но нашата конфигурация не беше засегната.
пример на конфигурация:
location ~ /rewrite { rewrite ^.*$ $arg_x; } location / { root html; index index.html index.htm; }резултат
curl localhost:8337/rewrite?x=/../../../../../../etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
... - В nginx има грешка, която води до изтичане на памет, ако редът rewrite съдържа нулев байт. При изпращане на редирект, nginx резервира нова памет, съответстваща на цялата дължина на реда, но копира реда чрез строчна функция, в която нулевият байт е терминатор на реда, затова редът се копира само до нулевия байт, остатъкът от буфера съдържа неинициализирани данни. Подробен анализ може да бъде намерен .
пример на конфигурация (^@ нулев байт)
location ~ /memleak { rewrite ^.*$ "^@asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdasdf"; } location / { root html; index index.html index.htm; }резултат
curl localhost:8337/secret -vv
...
curl localhost:8337/memleak -vv
...
Местоположение: http://localhost:8337/secret
...
- Nginx защитава GET-параметрите от инжекция на служебни символи и дава възможност да се използват в rewrite само GET-параметри. Следователно, експлоатирането на инжекция чрез контролирани от потребителя параметри в nginx не е възможно. POST параметрите не са защитени. OpenResty позволява работа и с GET и с POST параметри, затова при използването на POST параметри чрез OpenResty се появява възможност за инжекция на специални символи.
пример на конфигурация:
location ~ /memleak { rewrite_by_lua_block { ngx.req.read_body(); local args, err = ngx.req.get_post_args(); ngx.req.set_uri( args["url"], true ); } } location / { root html; index index.html index.htm; }резултат:
curl localhost:8337 -d "url=secret" -vv
...
curl localhost:8337 -d "url=asdfasdfasdfasdfasdfasdfasdfasdf" -vv
...
Location: http://localhost:8337/{...може да съдържа secret...}
...
По-нататъшна реакция
Проблемът беше съобщен на разработчиците на nginx и OpenResty, разработчиците не считат проблема за грешка в сигурността в nginx, тъй като в самия nginx няма възможност за експлоатация на грешката чрез инжекция на специални символи, фикс беше публикуван на 16 декември. За 4 месеца от момента на репорта в OpenResty също не бяха направени никакви промени, въпреки че имаше разбиране, че е необходим безопасен вариант на функцията ngx.req.set_uri(). На 18 март 2020 г. публикувахме информация, на 21 март OpenResty издаде , която добавя проверка на URI.
Portswigger добра статия и събра коментари от OpenResty и Nginx (вярно, коментарят за това, че се разкрива само малък фрагмент от паметта е неверен и заблуждаващ, това се определя от дължината на реда, следващ нулевия байт и, при липса на явни ограничения за дължина, може да бъде контролирано от атакуващия).
Каква беше грешката и какво да направим, за да я предотвратим?
Имаше ли грешка в nginx? Да, имаше, защото изтичането на съдържание от паметта е във всеки случай грешка.
Имаше ли грешка в OpenResty? Да, най-малкото не беше изследван и документиран въпросът за сигурността на предлаганата функционалност на OpenResty.
Беше ли допусната грешка в конфигурацията / използването на OpenResty? Да, защото при липса на явно указание беше направена непроверена предпоставка за сигурността на използваната функционалност.
Коя от тези грешки е уязвимост в сигурността с награда от $10000? За нас това всъщност не е важно. Във всяко софтуерно решение, особено на кръстопътя на няколко компонента, особено предоставяно от различни проекти и разработчици, никой и никога не може да гарантира, че всички особености на тяхната работа са известни и документирани и че липсват грешки. Затова всяка уязвимост в сигурността възниква именно там, където влияе на сигурността.
Във всеки случай, добра практика е да се нормализират или максимално ограничава/филтрират входящите данни, които отиват в който и да е външен модул/API, ако няма явни указания и недвусмислено разбиране, че това не е необходимо.
Errata
По опит , за да се запази чистотата на езика:
бъг баунти — конкурс за лов на грешки
тикет на грешка — уведомление за грешка
редирект — пренасочване
отворен код — с отворен код
errata — работа по грешки
Източник: habr.com
