Săriți la conținut

Blog ·

O scanare care își închidea propriul slot

Fiecare scanare planificată reușită își marca propriul slot drept expirat, din pricina unui cuvânt din enumerarea greșită. Două defecte văzute doar măsurând.

De Provibr

Scanarea noastră de expunere are un planificator. Revendică un slot, trimite o comandă de scanare agentului care ajunge la mașină și așteaptă două lucruri: măsurătoarea însăși și încheierea comenzii care a purtat-o. Când un slot se termină fără răspuns, este marcat drept expirat, iar mașina apare cu „nicio măsurătoare recentă”.

Fiecare scanare reușită își marca propriul slot drept expirat. Patru din patru, măsurate împotriva unui agent real, în timp ce cele patru măsurători erau citite și păstrate fără cusur. Panoul anunța că patru mașini nu fuseseră scanate de curând, la câteva minute după ce fuseseră scanate.

Un cuvânt, dintr-un alt tabel

Tratantul care închide un slot rămas fără răspuns începea cu o ieșire timpurie: dacă a reușit comanda, nu este nimic de închis, așa că se pleacă.

Cuvântul cu care compara era succeeded. Cuvântul acela nu există în enumerarea stărilor de comandă, care conține pending, sent, completed, failed, timeout și cancelled. Este cuvântul dintr-un cu totul alt tabel, cel care urmărește instalările de agenți, și a ajuns aici fiindcă cineva a scris tratantul uitându-se la stările acelui alt tabel și fiindcă parametrul era tipizat ca text simplu.

Textul simplu este toată povestea. Comparația se compilează față de orice text, deci nimic nu obiectează, iar eșecul nu este o excepție, ci o ramură niciodată luată. Tratantul și-a parcurs calea „nu a sosit niciun răspuns” pentru fiecare scanare reușită și a marcat ascultător fiecare slot drept expirat.

Niciun test unitar nu ar fi putut găsi asta. Într-o imitație nu există un ciclu de viață al comenzii: imitația este cea care întinde starea, deci starea este ceea ce a scris autorul testului, iar autorul acela a scris același cuvânt greșit. Doar un agent real, care trimite încheieri reale prin distribuitorul real, produce starea care o dezvăluie. Parametrul este acum tipizat pe cele trei stări care pot ajunge cu adevărat la funcția aceasta, așa cum tratantul echivalent de monitorizare fusese dintotdeauna. Două funcții, aceeași întrebare, și doar una scrisă cu grijă.

Iar cele două mesaje sosesc în dezordine

Repararea comparației nu a fost de ajuns, iar motivul este un al doilea lucru pe care doar un agent real îl arată.

Agentul trimite două mesaje distincte: rezultatul scanării și încheierea comenzii. Călătoresc independent. Măsurat, rezultatul a sosit primul, cu paisprezece milisecunde înaintea încheierii. Intervalul acela este destul de mic încât nimeni să nu se gândească la el și destul de mare încât să apară mereu.

Când rulează tratantul de încheiere, slotul este așadar de regulă închis deja de rezultat. O actualizare fără condiție redeschide atunci un slot încheiat și îl marchează din nou expirat, ceea ce este defectul inițial cu altă pălărie. Actualizarea este de aceea limitată la cele două stări în care un slot mai așteaptă răspuns. Dacă este deja soluționat, tratantul nu schimbă nimic.

Este o formă generală: când un fapt poate sosi pe două căi, scriitorul care nu este cel principal trebuie să declare din ce stări are voie să plece.

O constatare închisă care a oprit toată flota

Al doilea defect este mai tăcut și a făcut mai multă pagubă.

Când o sursă de vulnerabilități se actualizează, constatările deschise sunt reevaluate față de datele noi. O constatare a cărei potrivire a dispărut este închisă, și închisă ca neverificată, nu ca rezolvată, fiindcă nu s-a măsurat nimic: ce s-a schimbat este deducția noastră, nu mașina. Deosebirea aceea este motivul pentru care starea aceea există.

Reevaluarea aducea constatările derivate existente pentru a deosebi „actualizeaz-o pe aceasta” de „creează una nouă”. Le aducea doar pe cele deschise. Este un filtru cu aspect rezonabil și este greșit, fiindcă restricția de unicitate de dedesubt nu știe nimic despre stări: stă pe server și pe o amprentă a constatării, punct.

Șirul este acesta: o bază națională de vulnerabilități corectează un interval de versiuni, potrivirea noastră dispare, constatarea se închide. Săptămâni mai târziu aceeași bază revizuiește intervalul înapoi, ceea ce chiar se întâmplă, iar potrivirea revine. Interogarea nu vede rândul închis. Codul ia ramura „aceasta încă nu există”. Inserarea lovește indexul unic și ridică o încălcare de restricție.

Iar apoi încălcarea aceea scapă. Apelantul o prinde drept „runda de avize a eșuat”, iar runda aceea nu reevaluează absolut nimic, pentru fiecare client al platformei. Condiția care a provocat-o nu dispare de la sine, deci fiecare rundă următoare a eșuat la fel. O constatare care se închisese și se redeschisese a înghețat reevaluarea unei flote întregi, permanent, cu un rând de jurnal drept unic simptom.

Reparația este mică: includeți și rândurile închise, iar când o constatare închisă se potrivește din nou, redeschideți-o cu cronologia intactă în loc să începeți una nouă. O constatare redeschisă este un fapt diferit de una nouă, iar cel care o triază merită să vadă că aceasta a revenit și nu că a sosit.

O buclă de surse, măsurată de trei ori

Procesul de surse fusese scris înainte să existe vreun mod de a-l rula fără a atinge o bază de date reală, iar aceasta este capcana în care a căzut runda de securitate anterioară: importarea pachetului de bază de date citește mediul și se conectează, așa că un test unitar cu o funcție de descărcare injectată a scris în tăcere patru avize într-o bază de dezvoltare în timp ce anunța verde.

Partea de stocare este acum o cusătură, iar bucla se poate pune la încercare față de o contabilitate în memorie, în timp ce codul de producție o folosește pe cea reală neschimbată. Apoi a rulat de trei ori față de sursele publice reale.

RundăCereriCe s-a întâmplatTimp
La rece47.066 de avize păstrate, 31,4 MO după decomprimare18,7 s
Neschimbat1304 Not Modified, validatorii păstrați reținuți, zero scrieri0,3 s
Cursor cu două zile înapoi3355 de avize actualizate, niciunul nousecunde

Codul 304 este tot rostul exercițiului. Un catalog de avize servit static răspunde unei cereri condiționate cu „nu s-a schimbat nimic”, iar comportamentul corect este păstrarea validatorilor reținuți, nescrierea nimicului și numărarea rundei drept reușită.

Câteva mărimi merită știute, fiindcă tocmai acolo se ghicește: catalogul vulnerabilităților exploatate activ cântărește circa 188 kO pe fir fiindcă este comprimat, iar o pagină de două mii de înregistrări din sursa mai mare are în jur de 18,7 MO. Amândouă sunt citite în spatele unui plafon dur de octeți și nu prin ajutorul JSON obișnuit, care nu are nicio limită; sunt cele mai mari două răspunsuri pe care platforma le înghite și vin de la mașini pe care nu le administrăm.

O fereastră care se înjumătățește în loc să sară

Sursa mai mare se parcurge pe pagini, într-o fereastră temporală mobilă. Două lucruri erau greșite în bucla aceea, iar amândouă pierdeau date fără să semnaleze nimic vreodată.

Dacă o fereastră conținea mai multe înregistrări decât permitea plafonul de pagini, bucla se oprea și muta totuși cursorul la capătul ferestrei. Tot ce trecea peste plafon era sărit în tăcere și ar fi fost recuperat doar dacă o altă schimbare ar fi atins din nou aceleași înregistrări.

Iar o pagină goală în mijlocul unei ferestre, în timp ce se mai declarau înregistrări, muta cursorul cu o pagină întreagă. Două mii de înregistrări pierdute.

Amândouă sunt tratate acum după același principiu: dacă răspunsul nu încape, puneți o întrebare mai mică, iar dacă răspunsul este imposibil, nu mutați nimic.

O fereastră prea mare este înjumătățită în loc să fie trunchiată, de cel mult șase ori, până la o podea de o oră. Șase înjumătățiri aduc o săptămână la puțin peste două ore, ceea ce ajunge pentru orice restanță realistă. Sub o oră se oprește: o fereastră de minute care încă întoarce douăzeci de mii de înregistrări nu este o problemă de fereastră, ci o avarie, iar a nu scrie lăsând cursorul unde este se întâmplă să fie singurul răspuns cinstit disponibil. O pagină goală la mijlocul ferestrei este tratată la fel, ca o eroare care lasă cursorul în pace, ca runda următoare să încerce din nou în loc să treacă peste o gaură.

Înjumătățirea are loc după scrierea paginii, nu înainte. Ce am citit deja aparține ferestrei inițiale și este valid; aruncarea lui ar schimba un fel de pierdere de date cu altul.

Un lucru mic din același colț, demn de repetat fiindcă sub presiune iese ușor greșit: cheia de acces merge într-un antet și niciodată în șirul de interogare. URL-urile ajung în jurnale, în memorii tampon intermediare și în rapoarte de eroare. Anteturile nu.

Ce am nota

  1. Tipizați parametrul, cu atât mai mult când este o stare. O comparație de text cu un cuvânt din enumerarea altui tabel se compilează, rulează și ia pentru totdeauna ramura greșită.
  2. Două mesaje despre un singur eveniment sosesc în dezordine. Limitați scriitorul secundar la stările din care poate pleca.
  3. O restricție de unicitate care ignoră stările va întâlni cod care nu le ignoră. Aduceți și rândurile închise, altfel prima redeschidere devine o încălcare.
  4. Întrebați-vă ce otrăvește un singur eșec. O excepție care scapă dintr-o buclă pe element transformă un rând prost într-o oprire la nivelul întregii platforme, iar condiția nu se limpezește singură.
  5. O cerere condiționată căreia i se răspunde 304 este un succes. Păstrați validatorii, nu scrieți nimic și nu o socotiți un eșec.
  6. Dacă o fereastră este prea mare, înjumătățiți-o; dacă o pagină este imposibilă, opriți-vă. Mutarea unui cursor peste date necitite este singurul deznodământ fără cale de întoarcere.
  7. Un proces care nu rulează fără bază de date va fi încercat pe baza voastră de date. O cusătură de stocare costă mai puțin decât curățenia.

Pentru claritate: scanarea de expunere este construită și stă în spatele unui comutator de lansare, deci niciun client nu o are în față. Reevaluarea, sursele și distribuirea pe mai multe adrese sunt exersate; rămân nedovedite îngustarea ferestrei față de o sursă vie în loc de una injectată, comportamentul indexului pe un catalog cu adevărat plin și o scanare pornită de pe altă mașină decât cea care a cerut-o.

Din munca de platformă la Provibr. De ce comparatorul refuză să fie sigur de o versiune se află în o potrivire care refuză certitudinea, iar de ce sonda din spate nu trimite aproape nimic se află în de ce un scaner de vulnerabilități ar trebui să trimită zero octeți.

Începeți cu o gazdă și un agent

Creați un cont, instalați agentul cu o singură comandă și conectați primul dumneavoastră host Proxmox. Perioada de probă durează 14 zile și nu cere date de plată.