
Pred enim letom, 21. marca 2019, je na HackerOne prišla zelo dobra od . Pri uvajanju ničelnega bajta (ASCII 0) v POST-parametre enega od API-zahtevkov spletne pošte, ki je vrnil HTTP-preusmeritev, so se v podatkih preusmeritve pokazali kosi neinitializirane pomnilnika, kjer so se najpogosteje razkrili fragmenti iz GET-parametrov in glav drugih zahtevkov na istem strežniku.
To je kritična ranljivost, saj zahtevki vključujejo tudi sejne piškotke. Čez nekaj ur je bila uvedena začasna rešitev, ki je filtrirala ničelni bajt (kakor se je izkazalo, to ni bilo dovolj, saj je ostajala možnost injekcije CRLF / ASCII 13, 10, kar omogoča manipulacijo z glavi in podatki HTTP-odgovora, kar je manj kritično, a še vedno neprijetno). Obenem je bila težava posredovana analitikom za varnost in razvijalcem za iskanje in odpravo vzrokov napake.
Mail.ru pošta je zelo zapletena aplikacija, pri oblikovanju odgovora lahko sodeluje veliko različnih frontend/bbackend komponent, tako odprtokodnih (velika hvala vsem razvijalcem odprte programske opreme), kot tudi lastnega razvoja. Uspešno je bilo izločiti vse komponente razen nginx in openresty ter lokalizirati težavo do klica v OpenResty skriptu, ki se ni obnašal tako, kot je bilo pričakovano (vstavitev ničelnega bajta ali prehoda vrstice preko GET-parametrov s prepisovanjem v ngx_http_rewrite_module, ki se po dokumentaciji uporablja in bi se, ali tako se je zdelo, moral obnašati povsem enako, ne bo delovalo). Možne posledice so bile odpravljene, dodano je bilo največje strogo filtriranje in preverjeno je bilo, da filtriranje odpravlja vse možne vektorje. A mehanizem, ki je privedel do uhajanja vsebine pomnilnika, je ostal uganka. Čez mesec so poročilo o napaki zaprli kot razrešenega, medtem ko so raziskave vzrokov napake odložili na boljše čase.
OpenResty je precej priljubljen vtičnik, ki omogoča pisanje Lua skriptov znotraj nginx-a, in se uporablja v več projektih Mail.ru, zato težava ni bila obravnavana kot rešena. In po določenem času so se k temu dejansko vrnili, da bi razumeli prave vzroke, možne posledice in pripravili priporočila za razvijalce. V izkopavanjih izvorne kode so sodelovali in Ugotovljeno je da:
- V nginx-u, pri uporabi preusmerjanja z uporabniškimi podatki obstaja možnost napada z traversalo map (in verjetno tudi SSRF) v nekaterih konfiguracijah, vendar je to znan razlog in ga morajo statični analizatorji konfiguracij odkriti v in od Yandex (da, tudi mi ga uporabljamo, hvala). Pri uporabi OpenResty je to možnost enostavno spregledati, vendar naša konfiguracija ni bila prizadeta.
primer konfiguracije:
location ~ /rewrite { rewrite ^.*$ $arg_x; } location / { root html; index index.html index.htm; }rezultat
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
... - V nginx-u obstaja napaka, ki vodi do puščanja vsebine iz pomnilnika, če niz preusmerjanja vsebuje ničelni bajt. Pri vračanju preusmeritve nginx alocira nov pomnilniški predal, ki ustreza celotni dolžini niza, a ga kopira preko nizovne funkcije, kjer je ničelni bajt terminator niza; zato se niz skopira le do ničelnega bajta, preostali del pomnilniškega predala vsebuje neinicirane podatke. Podroben razdelek najdete .
primer konfiguracije (^@ ničelni bajt)
location ~ /memleak { rewrite ^.*$ "^@asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdasdf"; } location / { root html; index index.html index.htm; }rezultat
curl localhost:8337/secret -vv
...
curl localhost:8337/memleak -vv
...
Location: http://localhost:8337/secret
...
- Nginx ščiti GET-parametre pred vnosom nadzornih simbolov in omogoča uporabo samo GET-parametrov v preusmeritvah. Zato ni mogoče izkoristiti vnosa skozi nadzorovane uporabniške parametre v nginx-u. POST parametri pri tem niso zaščiteni. OpenResty omogoča delo tako z GET kot s POST parametri, zato se pri uporabi POST parametrov preko OpenResty pojavi možnost vnosa posebnih simbolov.
primer konfiguracije:
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; }rezultat:
curl localhost:8337 -d "url=secret" -vv
...
curl localhost:8337 -d "url=asdfasdfasdfasdfasdfasdfasdfasdf" -vv
...
Location: http://localhost:8337/{...может содержать secret...}
...
Nadaljnja reakcija
O težavi so obvestili razvijalce nginx-a in OpenResty, razvijalci problem ne obravnavajo kot varnostno napako v nginx-u, ker v samem nginx-u ni možnosti izkoristiti napako preko vnosa posebnih simbolov; popravilo je bilo objavljeno 16. decembra. V štirih mesecih od poročanja v OpenResty prav tako ni bilo narejenih kakršnih koli sprememb, čeprav so bili razumeli, da je potrebna varna različica funkcije ngx.req.set_uri(). 18. marca 2020 smo objavili informacije, 21. marca je OpenResty izdal , ki dodaja preverjanje URI.
Portswigger доброго стату и взял комментарии у OpenResty и Nginx (хоть комментарий о том, что раскрывается только небольшой фрагмент памяти является неверным и может ввести в заблуждение, это определяется длиной строки, следующей за нулевым байтом и, при отсутствии особых ограничений на длину, может контролироваться атакующим).
Так в чем же была ошибка и что делать, чтобы ее избежать?
Была ли ошибка в nginx? Да, была, потому что утечка содержимого памяти — это в любом случае ошибка.
Была ли ошибка в OpenResty? Да, как минимум не был исследован и задокументирован вопрос безопасности предлагаемой OpenResty функциональности.
Была ли допущена ошибка в конфигурации / использовании OpenResty? Да, потому что при отсутствии явного указания было сделано непроверенное предположение о безопасности используемой функциональности.
Какая из этих ошибок является уязвимостью безопасности с баунти на $10000? Для нас это, собственно, не важно. В любом ПО, особенно на стыке нескольких компонентов, особенно предоставляемых разными проектами и разработчиками, никто и никогда не может гарантировать, что все особенности их работы известны и задокументированы, и что ошибок не существует. Поэтому любая уязвимость безопасности возникает именно там, где она затрагивает безопасность.
В любом случае, хорошей практикой будет нормализовать или максимально ограничивать/фильтровать входные данные, которые уходят в любой внешний модуль/API, если нет явных указаний и однозначного понимания, что это не требуется.
Errata
По опыту , ради сохранения чистоты языка:
баг баунти — конкурс охоты за ошибками
poročilo o napakah — уведомление об ошибке
редирект — перенаправление
опенсорсный — с открытым кодом
errata — работа над ошибками
Izvor: habr.com
