Blog ·
Un secret greșit seamănă cu tăcerea
Trei protocoale de monitorizare scrise de mână în loc să fie importate, și o defecțiune pe care protocolul însuși nu o lasă deosebită de un server mort.
De Provibr
Adăugarea unei verificări de monitorizare pentru un protocol nou pare o chestiune de dependențe. Există un pachet pentru MongoDB, unul pentru BSON, unul care vorbește protocolul de rețea al SQL Server. Trei rânduri într-un manifest, patruzeci de rânduri de lipici, gata.
Toate cele trei protocoale au fost scrise de mână, iar motivele merită consemnate fiindcă niciunul nu este „nu a fost inventat aici”. Două sunt măsurători, iar al treilea este o promisiune deja făcută clienților despre ce poate face acest agent.
Ce aduce cu adevărat o dependență
Pachetul BSON trage după el patruzeci și una de dependențe. Asta se poate măsura și a fost măsurat, dar nu numărul a decis.
Verificarea citește patru câmpuri dintr-un răspuns al serverului. Un tip de document generic ține tot răspunsul în memorie: lista de gazde, etichetele, momentul ultimei scrieri, ora grupului, tot ce a vrut serverul să trimită. În acel moment, „citim doar patru câmpuri” devine o înțelegere între dezvoltatori în loc de o proprietate a codului, iar înțelegerile sunt încălcate de runda următoare, care are nevoie de un al cincilea câmp dintr-un motiv bun.
Există deci un cititor care parcurge elementele, întoarce cele patru cerute și sare peste rest fără să îl depună undeva. Nu poate reține răspunsul, așa că promisiunea este o formă și nu un obicei. Aceeași decizie ca lista de permisiuni pentru comanda INFO din Redis, și aceeași care a ținut un driver complet de bază de date în afara verificării PostgreSQL: un driver aduce cu el un constructor de interogări, și atunci există, în binarul care rulează în rețeaua unui client, cod capabil să pună o întrebare arbitrară bazei sale de date.
Pachetul pentru SQL Server este varianta mai tăioasă. Se compilează pentru ținta noastră de lansare, exact lucrul de care ne așteptam să se împiedice. Ce aduce este o a doua stivă TLS: o linie ieșită din întreținere lângă cea actuală pe care o livrăm deja. Furnizorul criptografic al întregului proces se instalează pentru linia actuală și nu face nimic pentru cea veche, deci TLS-ul unei verificări ar folosi un alt furnizor decât orice altă conexiune a acestui agent; o vulnerabilitate în acea linie este una pe care o livrăm și nu o putem repara fără să așteptăm proiectul din amonte; iar politica de certificate, care stă într-un singur modul tocmai ca să nu se abată, ar primi o a doua implementare pe altă interfață.
Săritura este locul unde greșește un cititor scris de mână
Cine își scrie propriul cititor pentru un format binar poate face o greșeală care cântărește mai mult decât celelalte, și ea nu stă în analiză. Stă în săritură.
Fiecare valoare BSON are un octet de tip și o lungime care decurge din el. Un cititor care cunoaște tipurile întâlnite din întâmplare și care sare peste un tip necunoscut cu zero octeți nu eșuează: citește mai departe restul documentului din locul greșit. Tot ce urmează este deplasat. Se obțin valori plauzibile care nu sunt cele trimise.
Tabelul lungimilor este de aceea complet, cu un refuz închis pentru tot restul. Un octet de tip necunoscut este o eroare de protocol și niciodată o săritură.
Aceeași familie de greșeli stă cu un nivel mai sus, în felul în care se citesc numerele. Un server MongoDB răspunde ok: 1 ca virgulă mobilă și un code de eroare ca întreg pe 32 de biți, în același document. Un cititor care știe doar 32 de biți îl pierde pe primul în tăcere, și atunci fiecare autentificare eșuată se citește ca reușită. Cititorul acceptă un număr BSON în toate cele trei lățimi și întoarce un întreg.
Pasul de autentificare care trebuie să poată eșua
Verificarea se autentifică cu SCRAM-SHA-256 și nu cu SCRAM-SHA-1, din două motive care arată în aceeași direcție.
Mecanismul vechi nu introduce parola în SCRAM. Introduce un MD5 hexazecimal al numelui de utilizator, al unui șir fix și al parolei. Susținerea lui ar însemna să scriem cod care face un MD5 din credențiale, pentru un mecanism pe care niciun server actual nu mai trebuie să îl ofere.
Motivul tăcut este mai rău. Un server care le oferă pe amândouă ar ajunge, la retragere, pe cel mai slab fără ca ceva să devină roșu, și atunci o măsurătoare verde stă deasupra unei autentificări mai slabe decât crede clientul. Dacă serverul nu oferă SHA-256 pentru acel utilizator, verificarea refuză cu un cod propriu și o spune.
Scrierea SCRAM de mână a fost refuzată într-o rundă anterioară pentru verificarea PostgreSQL, iar obiecția era reală: schimbul are o semnătură a serverului pe care clientul ar trebui să o verifice, iar o implementare care sare peste acel pas este verde în fața unui server autentic și la fel de verde în fața unuia falsificat. Aici obiecția nu a fost ocolită, ci rezolvată. Verificarea semnăturii este o funcție separată cu un cod de eroare propriu, pentru ca „pasul lipsește” să nu fie același lucru cu „pasul a reușit”, și există un test care oferă o semnătură falsificată și cere ca autentificarea să eșueze. Acel test a fost dovedit roșu prin scoaterea verificării.
Pachetul folosit de verificarea PostgreSQL nu era utilizabil aici, iar asta este măsurat și nu presupus. Implementarea lui de SCRAM scrie primul mesaj al clientului cu numele de utilizator gol, fiindcă PostgreSQL trimite numele în pachetul său de pornire. MongoDB nu are un asemenea pachet și citește numele tocmai din acel câmp. Introducerea lui ulterioară ar schimba chiar octeții peste care s-a semnat deja, iar serverul ne-ar respinge dovada. Nu există o interfață prin care să fie transmis.
O defecțiune pe care protocolul nu o lasă numită
Acum partea pe care niciun surogat nu ar fi găsit-o.
RADIUS se verifică printr-o cerere Status-Server, care întreabă serverul dacă trăiește și nimic altceva. Alternativa evidentă, o cerere de acces cu un cont de test, a fost înlăturată: aceea este o încercare reală de autentificare în rețeaua clientului, cu un cont real de care cineva s-ar putea folosi, și scrie un rând în jurnalul lui de autentificare printre utilizatorii lui adevărați. La un interval de un minut sunt o mie patru sute patruzeci de rânduri pe zi care nu spun nimic despre serviciul lui, iar în multe instalări o parolă greșită este motiv de blocare a unui cont, astfel încât monitorizarea provoacă exact incidentul pe care trebuia să îl vadă venind.
Apoi s-a măsurat, față de un server real, ce face un secret partajat greșit.
Nimic. Un server care nu poate verifica autentificatorul de mesaj al unei cereri aruncă pachetul în tăcere, fără răspuns de eroare. Stă în specificație, și înseamnă că tabloul unei defecțiuni dintr-un secret greșit nu se deosebește de o regulă de firewall, de un port greșit sau de o mașină moartă.
Asta nu se repară într-un adaptor, fiindcă este o proprietate a protocolului. Ce se poate face este să nu ne mai prefacem. Mesajul „fără răspuns” numește un secret partajat greșit drept cauză posibilă, în textul pe care îl citește clientul, ca un administrator să nu petreacă o după-amiază în firewall căutând ceva care stă într-un fișier de configurare. Iar dacă serverul răspunde totuși, autentificatorul de răspuns este verificat, și o nepotrivire acolo este raportată ca fapt de autentificare în loc de eroare de protocol, fiindcă în acel montaj chiar este.
| Situație | Ce vede clientul | De ce |
|---|---|---|
| Secret greșit, fără răspuns | Fără răspuns, cu secretul drept cauză posibilă | Serverul a aruncat pachetul; nu s-a întors nimic de examinat |
| Secret greșit, sosește un răspuns | Un secret greșit, ca rezultat de autentificare | Autentificatorul de răspuns nu se potrivea |
| Secret corect, fără răspuns | Fără răspuns | Aceeași tăcere, și merită aceleași cuvinte |
Rândul din mijloc este singurul caz în care diferența se vede, și nu este cel obișnuit.
Un server care nu poate fi măsurat, spus cu voce tare
Ultima măsurătoare este cea care ne-a plăcut cel mai puțin.
SQL Server pe Linux generează un certificat la prima pornire. Acel certificat este X.509 versiunea 1, o formă fără extensii care de mult nu mai este destinată TLS, iar analizatorul de certificate de dedesubt o refuză. Modul cifrat eșuează așadar pe o versiune de certificat neacceptată.
Merită să fim preciși în privința motivului pentru care acel mod mai verifică ceva. Identitatea nu este cântărită acolo: numele din certificat nu este controlat. Semnătura, da, fiindcă fără ea nu mai rămâne o strângere de mână, ci un schimb de octeți cu oricine a răspuns. Ca să cântărești o semnătură trebuie citită cheia publică din certificat, și tocmai acesta este pasul care refuză.
Rezultatul este deci: un SQL Server cu propriul certificat generat nu poate fi măsurat cu această verificare. Clientul citește eroarea reală a analizatorului ca detaliu, citată în loc de parafrazată, iar remediul este un certificat adevărat în configurația serverului, lucru pe care un administrator care își face serverul monitorizabil îl face oricum.
Două lucruri nu au fost făcute în mod intenționat. Verificarea semnăturii nu este sărită în acel mod, fiindcă ar rupe promisiunea exact la setarea pe care o aleg cei mai mulți și ar transforma un refuz vizibil într-o slăbiciune invizibilă. Și încă nu există un cod de eroare propriu pentru acest caz, fiindcă singurul mod de a-l recunoaște ar fi compararea cu textul lizibil al unei biblioteci, iar aceasta este o presupunere despre codul altcuiva care se poate deplasa în tăcere. Merită o rundă proprie, cu o probă proprie.
Nu există niciun mod complet fără cifrare, dintr-un motiv propriu acestui protocol: parola din pachetul de autentificare este mascată cu un sau exclusiv fix și o inversare de nibble-uri. Aceasta este o codificare, nu o cifrare. Cine vede octeții citește parola, deci un mod necifrat ar trimite parola unui client în clar prin rețeaua lui, sub un nume care sugerează altceva.
Ce a găsit canarul
Totul a rulat față de servere reale și nu față de surogate: paisprezece verificări față de MongoDB simplu și cu TLS, un server RADIUS real și un SQL Server real. Cel mai util rezultat este plictisitor. O parolă greșită este pretutindeni o eroare de autentificare și niciodată una de protocol. Aceasta este distincția care îi spune unui client dacă să se uite la credențiale sau la rețea, iar o rundă anterioară o greșise în verificarea PostgreSQL: o eroare din timpul pasului de autentificare era tradusă prin „acesta nu este un server PostgreSQL”.
Ce am consemna
- Numărați ce reține o dependență, nu doar cât costă. Un tip de document generic transformă „citim patru câmpuri” într-o înțelegere în loc de o proprietate.
- Un driver aduce cu el un constructor de interogări. Dacă promisiunea este că nicio interogare arbitrară nu poate pleca, codul care ar trimite una nu ar trebui să existe în binar.
- Două stive TLS într-un binar sunt o problemă de politică, nu de mărime. Cea pe care nu ați ales-o este cea pe care nu o puteți repara.
- Într-un cititor scris de mână, tabelul săriturilor este partea periculoasă. Un tip necunoscut trebuie să fie o eroare; sărirea lui cu zero octeți deplasează tot ce urmează.
- Acceptați un număr în fiecare lățime permisă de protocol. Un câmp care sosește în virgulă mobilă ajunge ca să citiți fiecare eșec drept succes.
- O retragere spre un mecanism mai slab trebuie să fie un refuz, nu o retragere. Verdele deasupra unei autentificări mai slabe este mai rău decât roșul.
- Dacă protocolul face o defecțiune imposibil de deosebit, spuneți-o în mesaj. Formularea este singurul loc unde acea distincție mai încape.
- Unele servere nu pot fi măsurate, iar asta intră în documentație și nu într-un ocol. Ocolul aici ar fi fost să nu mai verificăm semnături.
Ca să fie limpede ce este aceasta: cele trei verificări sunt construite, probate față de servere reale și în spatele unui comutator de lansare. Rămân nedovedite: călătoria tuturor trei pe fir către un agent real al unui client, contorizarea RADIUS dincolo de cererea de stare și SQL Server în alte configurații decât o instanță unică pe Linux.
Din munca de platformă la Provibr. Cum a scos aceeași abordare cu canar două defecte în verificările de baze de date anterioare se află în un canar care rulează față de servere reale, iar cum ajung la agent credențialele folosite de aceste verificări se află în un secret care călătorește o singură dată.