Ar KAM gali manipuliuoti sugeneruotus skaičius taip, kad į šauktinius būtų pakviesti tik atitinkami asmenys?

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_randomhttps://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:

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 toliauTad 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.

Short tale about proxies and /etc/hosts on GNU/Linux

I encountered an interesting behavior the other day when trying to use HTTP and/or HTTPS proxy on a GNU/Linux machine. cURL and friends read environment variables called http_proxy and https_proxy. So, you are thinking, if one wants to have “global” proxy settings, one just has to create a new executable file that is only writable by root in /etc/profile.d with content like this:

#!/bin/sh
export http_proxy=”http://1.2.3.4”
export https_proxy=”$http_proxy”

However, you might just notice an confusing occurrence when trying to use hostnames like localhost to refer to your own machine. Unfortunately, the priority is given to the proxy setting regardless if the hostname is static (i.e. it is in /etc/hosts) and it resolves to a local IP like 127.0.0.1. It could happen that your proxy will be a bit stupid and resolve localhost to its own (or even a different) IP, and then you will get burned because you want to refer to your computer with it, not the proxy IP. It has potential to especially be confusing if the output will be very similar to what you are expecting. This can happen when e.g. you are trying to set up a simple web server on your machine and the proxy responds with a place holder page. I can confess that I fell into this trap but I will not anymore.

To solve this you have to leverage another environment variable called no_proxy. It contains a list of hosts that will not go through the proxy. So, you have to append another setting to the /etc/profile.d/proxy.sh file that you created before. In the end, it will look something like this:

#!/bin/sh
export http_proxy=”http://1.2.3.4”
export https_proxy=”$http_proxy”
export no_proxy=”localhost”

Even though this solves our problem but on the other hand it has a lot of caveats as well:

  • You have to list all of the hostnames that you do not want to use the proxy for and that can become susceptible to human errors when the list becomes big. This can partially solved by using a configuration management system like Puppet so that you would be sure that you made no mistakes.
  • You have to maintain /etc/hosts in tandem with /etc/profile.d/proxy.sh to make sure that the same entries are in both files. Again, configuration management tools like Puppet or Chef can solve this easily but it is error prone when either of the files are modified by a human being so it is not an excuse. And we saw how even big companies like “Amazon” sometimes mess this up.

In conclusion, you have to be careful with /etc/hosts and proxies on GNU/Linux, especially with localhost. Do not forget to at least minimally add it to the no_proxy environment variable if you are going to set a “global” proxy setting. As far as I know, there is no way to easily change this behavior of cURL and friends so that it would look up /etc/hosts first before sending a request to the proxy. Personally, I think some kind of option should be provided to change this default behavior to alleviate the negative aspects that I mentioned before.