
Acum un an, pe 21 martie 2019, în a venit un raport de bug foarte bun de la . La introducerea byte-ului nul (ASCII 0) în parametrul POST al uneia dintre cererile API de webmail, care returna un redirect HTTP, în datele redirectului apăreau fragmente de memorie neinițializată, în care, cel mai frecvent, se dezvăluiau fragmente din parametrii GET și din anteturile altor cereri către același server.
Aceasta este o vulnerabilitate critică, deoarece cererile conțin, de asemenea, cookie-uri de sesiune. După câteva ore, a fost realizat un fix temporar, care filtra byte-ul nul (așa cum s-a dovedit ulterior, aceasta nu a fost suficient, deoarece rămânea posibilitatea injectării CRLF / ASCII 13, 10, ceea ce permite manipularea anteturilor și datelor din răspunsul HTTP, aceasta fiind mai puțin critică, dar totuși neplăcut). De asemenea, problema a fost transmisă analistilor de securitate și dezvoltatorilor pentru a căuta și a elimina cauzele apariției bugului.
Mail.ru este o aplicație foarte complexă, în formarea răspunsului pot participa un număr mare de componente frontend/backend, atât open-source (mulțumiri tuturor dezvoltătorilor de software liber), cât și cele dezvoltat intern. S-a reușit să se excludă toate componentele cu excepția nginx și openresty și să se localizeze problema până la apelul în scriptul OpenResty, care s-a comportat diferit față de așteptări (introducerea byte-ului nul sau a unui caracter de linie nouă prin parametrii GET cu rewrite în ngx_http_rewrite_module, care, conform documentației, este utilizat și ar trebui să funcționeze în mod similar, nu va funcționa). Au fost eliminate posibilele consecințe, s-a adăugat o filtrare cât mai strictă și s-a verificat că filtrarea eliminară toate posibilele vectori. Dar mecanismul care a dus la scurgerea conținutului din memorie a rămas o enigmă. După o lună, raportul de bug a fost închis ca fiind rezolvat, iar analiza cauzelor apariției bugului a fost amânată pentru vremuri mai bune.
OpenResty este un plugin foarte popular care permite scrierea de scripturi Lua în interiorul nginx și este utilizat în mai multe proiecte Mail.ru, așa că problema nu a fost considerată rezolvată. După un timp, s-a revenit asupra acesteia pentru a înțelege cauzele reale, posibilele consecințe și a elabora recomandări pentru dezvoltatori. La investigarea codului sursă au participat și . S-a constatat că:
- În nginx, utilizând rewrite cu date personalizate, există posibilitatea de directory traversal (și, probabil, SSRF) în anumite configurații, dar este un fapt bine cunoscut care ar trebui să fie detectat de analizoare statice de configurație în și de la Yandex (da, îl folosim și noi, mulțumim). Când se folosește OpenResty, această posibilitate poate fi ușor trecută cu vederea, dar configurația noastră nu a fost afectată.
exemplu de configurație:
location ~ /rewrite { rewrite ^.*$ $arg_x; } location / { root html; index index.html index.htm; }rezultatul
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
... - În nginx există o eroare care conduce la scurgeri de conținut din memorie dacă șirul de rewrite conține un byte zero. Atunci când nginx efectuează un redirect, alocă un nou buffer de memorie corespunzător lungimii totale a șirului, dar copiază șirul folosind o funcție de linie, în care byte-ul zero este terminatorul. Din această cauză, șirul este copiat doar până la byte-ul zero, iar restul buffer-ului conține date neinițializate. O analiză detaliată poate fi găsită .
exemplu de configurație (^@ byte zero)
location ~ /memleak { rewrite ^.*$ "^@asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdasdf"; } location / { root html; index index.html index.htm; }rezultatul
curl localhost:8337/secret -vv
...
curl localhost:8337/memleak -vv
...
Location: http://localhost:8337/secret
...
- Nginx protejează parametrii GET de injecția caracterelor speciale și permite utilizarea în rewrite doar a parametrilor GET. Prin urmare, exploatarea injecției prin parametrii controlați de utilizatori în nginx nu este posibilă. Parametrii POST, pe de altă parte, nu sunt protejați. OpenResty permite lucrul atât cu parametrii GET cât și cu cei POST, astfel că, atunci când se folosesc parametrii POST prin OpenResty, apare posibilitatea injecției caracterelor speciale.
exemplu de configurație:
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; }rezultatul:
curl localhost:8337 -d "url=secret" -vv
...
curl localhost:8337 -d "url=asdfasdfasdfasdfasdfasdfasdfasdf" -vv
...
Location: http://localhost:8337/{...poate conține secret...}
...
Reacția ulterioară
De problema au fost informați dezvoltatorii nginx și OpenResty, aceștia nu consideră problema ca fiind o eroare de securitate în nginx, deoarece în nginx nu există posibilitatea exploatării unei erori prin injectarea de caractere speciale, fix a fost publicat pe 16 decembrie. De la raportare, timp de 4 luni, nu s-au făcut modificări în OpenResty, deși era clar că este necesară o variantă sigură a funcției ngx.req.set_uri(). Pe 18 martie 2020, am publicat informațiile, iar pe 21 martie OpenResty a lansat , care adaugă verificarea URI.
Portswigger a scris un articol bun și a luat comentarii de la OpenResty și Nginx (deși comentariul că se dezvăluie doar un mic fragment al memoriei este greșit și induce în eroare, acesta depinde de lungimea șirului care urmează unui byte nul și, în lipsa unor limite explicite de lungime, poate fi controlat de atacator).
Atunci, care a fost eroarea și ce trebuie să facem pentru a o preveni?
A fost o eroare în nginx? Da, a fost, deoarece scurgerea conținutului memoriei este, în orice caz, o eroare.
A fost o eroare în OpenResty? Da, cel puțin nu a fost investigată și documentată problema de securitate a funcționalității oferite de OpenResty.
A fost o greșeală de configurare / utilizare a OpenResty? Da, deoarece în lipsa unei indicații explicite, s-a făcut o presupunere nesecurizată cu privire la siguranța funcționalității utilizate.
Care dintre aceste erori este o vulnerabilitate de securitate cu o recompensă de $10000? Pentru noi, în general, nu contează. În orice software, în special la intersecția mai multor componente, mai ales furnizate de diferite proiecte și dezvoltatori, nimeni și niciodată nu poate garanta că toate particularitățile funcționării lor sunt cunoscute și documentate și că nu există erori. Prin urmare, orice vulnerabilitate de securitate apare exact acolo unde afectează siguranța.
În orice caz, o practică bună va fi normalizarea sau maximizarea limitării / filtrării datelor de intrare care suntem trimise către orice modul extern / API, dacă nu există indicații explicite și o înțelegere clară că acest lucru nu este necesar.
Errata
Din experiența , pentru păstrarea clarității limbii:
bug bounty — concurs de vânătoare a erorilor
bugreport — notificare de eroare
redirect — redirecționare
open-source — cu cod deschis
errata — corectarea erorilor
Sursa: habr.com
