Nors man politika atrodo kaip nuobodus dalykas tačiau kadangi šitas liečia ir mane, ir kadangi niekas apie tai nekalbėjo tai nusprendžiau parašyti straipsnelį apie tai kas yra PRNG (pseudo-random number generator arba pseudo-atsitiktinis skaičių generatorius), kaip jis naudojamas šauktinių generavimo programoje ir kodėl tai yra problematiška.
Įžanga
2015-aisiais metais Lietuva atkūrė šauktinių tarnybą po agresyvių Rusijos veiksmų Krymo pusiasalyje ir suaktyvėjusios Rusijos karinės veiklos Kaliningrado krašte. Nepaisant to, kad, mano nuomone, tokia loterijos principu paremta tvarka yra palyginti amorali ir neteisinga, egzistuoja ir kita įdomi pusė — pats sąrašo generavimas. Valdininkai visada gynėsi, kad šauktinių generavimas yra visiškai skaidrus ir nekorumpuotas, nes yra visuomenės atstovai, nepriklausomi stebėtojai generavimo metu ir taip toliau ad nauseum. Mums pasisekė, kad pati KAM publikuoja sąrašo generavimo kodą, nes jį išanalizavę pamatysime, kad egzistuoja specialiai palikta spraga, kuria pasinaudojus galima labai lengvai iš anksto numatyti sugeneruoto šauktinių sąrašo turinį. Nesunku bus suprasti kaip tai gali išnaudoti korumpuoti asmenys iš KAM, kad sugeneruotame sąraše neliktų asmenų, kurie, pavyzdžiui, sumokėjo kyšį.
Teorija
Tam tikrose kompiuterinėse programose egzistuoja reikalavimas gauti atsitiktinius skaičius. Jie gali būti iš tikrųjų atsitiktiniai arba nevisiškai, kitaip sakant, tai yra pseudo-atsitiktiniai skaičiai arba dar vadinami deterministiniai atsitiktiniai skaičiai. Šitas generavimo būdas dažnai pasirenkamas ekonomiškumo, greitumo sumetimais. Pseudo-atsitiktinių skaičių generavimas priklauso nuo vienos ar daugiau pradinių reikšmių. Jos dažniausiai vadinamos vienu žodžiu — seed. Tas pats seed gali būti arba atsitiktinis, arba ne. Pseudo-atsitiktinių skaičių generatorius galėtų pavyzdžiui atrodyti taip (pavyzdys paimtas iš C programavimo kalbos standarto preliminarios versijos, ISO/IEC 9899:TC3):
static unsigned long int next = 1; int rand(void) // RAND_MAX assumed to be 32767 { next = next * 1103515245 + 12345; return (unsigned int)(next/65536) % 32768; }
void srand(unsigned int seed) { next = seed; }
Kaip matome, rand() funkcijos rezultatas absoliučiai priklauso tik nuo kintamojo next, kuris būna nustatytas į sekančią reikšmę pagal nuspėjamą formulę. Tai reiškia, kad rand() funkcijos rezultatus galima visiškai nuspėti pagal srand() perduotą seed. Iš kitos pusės visiškai atsitiktinių skaičių generatorius nepriklauso nuo kažkokių pradinių reikšmių, o, pavyzdžiui, naudoja entropijos šaltinį (-ius), kad gauti tikrai nepriklausomas reikšmes. Štai tokio generatoriaus generuojamų skaičių galima sakyti, kad neįmanoma nuspėti. Tačiau, deja, šauktinių sąrašo generavimo programa nenaudoja tokio tipo generatoriaus, o naudoja pseudo-atsitiktinį generatorių.
Šauktinių sąrašo generavimo programos analizė
Šauktinių kodą galima rasti http://www.kam.lt/download/47896/kodas.zip. Visas mums įdomus veiksmas vyksta Package Saukimo_Sarasas.txt faile, 87–89 eilutėse:
INSERT INTO PRS_A (PersonalCode, SarasoID, EilesNr, AtsitiktinisSkaicius, RevOrgNo, UserID, RevisionDate) SELECT PersonalCode, in_SarasoID, rownum as Eil_Nr, atsitiktinis, 0, User, SysDate FROM (SELECT dbms_random.value as atsitiktinis, PersonalCode
Kaip matome, išraiška dbms_random.value yra naudojama kaip atsitiktinis skaičius. Iškarto galime greitai surasti Oracle dokumentaciją apie dbms_random: https://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_random.htm. Visų pirma matome, kad šis generatorius nėra skirtas kriptografijai, nes jis generuoja pseudo-atsitiktinius skaičius, juos galima nuspėti. Pati dokumentacija netgi patvirtina tai ką aš noriu pasakyti: “If this package is seeded twice with the same seed, then accessed in the same way, it will produce the same results in both cases”. Tačiau čia nesustojame analizuoti ir ieškome dar tvirtesnių argumentų. Toliau paieškoję kode pastebime, kad aiškiai nėra inicializuojamas dbms_random su žinomu seed tad yra du variantai: arba jis būna iš anksto duomenų bazės serveryje nustatytas (ko nėra kode) prieš naudojimą, arba einama antru keliu ir automatiškai sugeneruojamas seedpirmo dbms_random panaudojimo metu.
Pirmu atveju galima labai lengvai apeiti visus dabartinius „skaidrumo“ saugiklius — stebėtojus ir t.t. — tiesiog prieš generuojant šauktinių sąrašą slapčia nustatyti seed į reikiamą skaičių, kad būtų generuojama tam tikra seka ir neliktų nereikalingų asmenų. Šis kelias yra lengviausias. Ieškojau ir niekur neradau, kad bent vienas iš tų stebėtojų būtų atlikęs įrangos apžiūrą ir pažiūrėjęs ar seed nebuvo iš anksto nustatytas, ir ar išviso būtų apžiūrėjęs kokia programinė įranga yra naudojama ant serverių.
Antru atveju truputį sudėtingiau, nes seed inicializuojamas naudojant dabartinę datą sekundės tikslumu, user ID ir session ID (pasak Oracle dokumentacijos). Tačiau viskas nėra prarasta ir visgi nesunku tai irgi apeiti. Pabandžiau surinkti informaciją apie tuos dalykus iš įvairių interneto kampelių tačiau vis tiek iki galo nėra aišku apie tuos dalykus, nes visgi visa Oracle programinė įranga nėra laisva programinė įranga. Štai ta informacija:
- User ID — unikalus skaičius, kuriuo naudojantis galima atpažinti sistemos vartotoją. Iš mūsų pusės ir stebėtojams tai visiškai nežinoma, nematoma reikšmė tad negalime nieko pasakyti čia išskyrus tai, kad tas skaičius yra 100% žinomas tų, kurie ištikrųjų generuoja tą sąrašą ir paruošia duomenų bazės serverį prieš darbą. Taip yra todėl, kad tai jie prisijungia prie tos duomenų bazės su kažkokiu vartotojo vardu ir slaptažodžiu, kad paruošti serverį darbui ir to pasekoje jie žino to vartotojo ID.
- Session ID — oficialios dokumentacijos iš Oracle dėl šito skaičiaus nėra tačiau įvairūs šaltiniai internete rašo, kad ištikrųjų tai yra seka iš kintamojo audses$. Ta seka tai yra tiesiog kažkoks pastoviai didinamas skaičius, kuris turi minimalią ir maksimalią reikšmę. Po kurio laiko ji „persisuka“ ir taip pat nei viena sesija negali turėti tokio pačio ID kaip kita sesija. Tad jeigu serveris būna tik įjungtas ir paruoštas šauktinių generavimui tai session ID eis iš eilės nuo mažo skaičiaus — 1, 2, 3 ir taip toliau. Iš to seka, kad session ID irgi labai paprasta iš anksto nuspėti.
Įvairūs šaltiniai iš kurių imta informacija:
- https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:162012348068
- https://mwidlake.wordpress.com/2010/06/17/what-is-audsid/
- https://www.experts-exchange.com/questions/10192409/USERENV-‘SESSIONID’.html
Mano tikslas buvo parodyti, kad nėra visiškai jokio tikro atsitiktinumo ir šitame kelyje nors ir gali pasirodyti kitaip. Didžiausias iššūkis šitu atveju yra paspausti generavimo mygtuką reikiamomis sekundėmis ir viskas — seedpatampa koks mums reikia, sąrašas generuojamas kokio mums reikia ir nebelieka nereikalingų asmenų! Taip pat verta pastebėti, kad nebūtinai tik viena sekundė būna tinkama — priklausomai nuo kriterijų skaičiaus, kurį sąrašas turi atitikt, tinkamų sekundžių kiekis gali apimti nuo labai mažo iki labai didelio masyvo.
Rekomendacijos
Kalbant apie atsitiktinių skaičių generavimą, KAM vietoje aš iškart nustočiau naudoti PRNG, kad sugeneruoti šauktinių sąrašą. Jo naudojimas atveria platų kelią piktnaudžiavimui ir išnaudojimui. Deja, bet Oracle, atrodo, kad neturi palaikymo „tikram“ atsitiktinių skaičių generatoriui tad reiktų naudoti kažkokią paslaugą, kurią siūlo naudojama operacinė sistema, kaip, pavyzdžiui, /dev/random ant beveik visų GNU/Linux plėtočių arba OS/X šeimos operacinių sistemų. Toks pakeitimas pasitarnautų kovojant prieš korupciją šauktinių sąrašo generavimo procese.
Išvados
Vien rimtesnio atsitiktinio skaičiaus generatoriaus, skirto kriptografijai, naudojimas nepadarytų šio proceso visiškai aiškiu ir atspariu korupcijai kadangi egzistuoja žymiai daugiau problematiškų vietų. Galime brėžti daug paralelių su kitu reiškiniu— internetiniu balsavimu. Šiuos du dalykus saisto didelis kiekis bendrų problemų: labai sunku įvykdyti bendros paskirties kompiuterių auditą jog būtų užtikrinta, kad ta mašina tikrai vykdo tik tas komandas, kurias mes norėtume ir jokias kitas; kad rezultatai yra nepriklausomi ir neįmanoma jų iš anksto nuspėti; kad niekas nesugebėtų pakeisti rezultatų post factum ir taip toliau. Tad rekomenduočiau galbūt daugiau semtis patirties iš užsienio valstybių tam, kad būtų užlopytos bent aiškiausios skylės tokios kaip ši ir šis procesas neatrodytų toks klaidingas ir pažeidžiamas kaip dabar yra. Tiesą sakant, man šiuo metu pats procesas atrodo mėgėjiškas ir padarytas be didelio apgalvojimo ar pasiruošimo.