[slovensky]
matej.sustr.sk / publikácie / články / ako stiahnuť databázu

Ako stiahnuť databázu

Majme databázu rozmerov rádovo 103 až 107 záznamov, verejne prístupnú cez web formulár (naraz po jednom alebo niekoľkých záznamoch). Táto databáza je pre nás niečím zaujímavá, a chceme mať jej off-line kópiu.

V skratke je potrebné:

  1. Zmyslieť si, že danú databázu chceme stiahnuť.
  2. Stiahnuť.
  3. Tešiť sa.
  4. Dúfať, že ak sa to prevalí, tak novinári nebudú prekrucovať.

Zatiaľ čo body 1 a 3 sú triviálne a bod 4 beznádejný, samotné ťahanie môže byť menej či viac komplikované...

Disclaimer

Budeme popisovať rôzne úskalia pri ťahaní verejne prístupnej databázy. Nejedná sa o prienik do systému a surové "ukradnutie" databázy - to je síce spôsob efektnejší (nie nutne efektívnejší), nie vždy však jednoduchý, o zákonnosti ani nehovoriac.

Príklady tu uvedené sú v bashi, samozrejme dá sa to rovnako alebo aj lepšie spraviť v perli alebo inom skriptovacom jazyku. Bolo by takisto možné napísať to celé v C alebo vyšších kompilovaných jazykoch, či už volaním systémových API alebo pomocou nejakého frameworku, ale zrejme by to trvalo dosť dlho a najmä by šlo z veľkej časti o "znovuvynaliezanie kolesa" -- v *nix shelli už totiž máme hotové všetky potrebné nástroje na sťahovanie a parsovanie výstupu, stačí ich pozliepať v skripte (rýchlosť kompilovaných programov v tomto prípade nepotrebujeme, viď. ďalej časť Obmedzenia).

Nežiadajte o detailnú implementáciu pre váš cieľ, ani o poskytnutie nejakej "stiahnutej" databázy, spravte si sami -- regexp powa.

Používatelia Windows môžu siahnuť po Cygwin.

Ilustračná DB

Ako príklad poslúži jednoduchá jednotabuľková databáza. Budeme ťahať tabuľku zvieratiek a ich rodných čísel s L=5,396,168 riadkami (k 30.6.2007). Empiricky zistíme, že budeme pracovať s údajmi: id (v databáze), meno, mesto, cislo.

ZvieratkoDB <http://zvieratko.db/> má 2 dynamické web stránky (podobne, ako veľa reálnych DB):

search.cgi
vyhľadávacia stránka s formulárom --- zadáme meno alebo časť mena (?query=), vypľuje tabuľku s odkazmi na zobrazenie detailov
show.cgi
zobrazovacia stránka pre jedno zvieratko --- podľa odkazu z vyhľadávania (?id=) zobrazí detaily o zvieratku, teda okrem mena a mesta, ktoré boli aj vo výsledkoch vyhľadávania, aj rodné číslo

Je nutné pripomenúť, že ilustračná DB je vymyslená, a akákoľvek podobnosť s reálnym svetom je čisto náhodná.

1. Jednoduchý prípad

Nech ZvieratkoDB nemá žiadne obmedzenia a id v databáze sú za sebou nasledujúce od 1 do L. Ručne stiahneme niekoľko exemplárov show.cgi?id=..., aby sme určili presný formát výstupu a spôsob, ako z toho HTML vyparsovať čisté dáta (pomocou grep, sed, prípadne awk, cut apod.). Celú databázu potom stiahneme:

#!/bin/bash
# priklad c.1, tahanie zvieratiek so za sebou nasledujucimi id
i=0; L=5389180
while [[ $i -lt $L ]]; do
	i=$((i+1))
	wget -O - http://zvieratko.db/show.cgi?id=$i \
	| grep ... | sed ... >> LokalneZvieratkoDB.txt
done

Tento a ostatné uvedené skripty sú ilustračné. Je potrebné ich upraviť tak, aby sme korektne vyparsovali požadované dáta, a prípadne aby posielali ďalšie hodnoty, ktoré cieľ požaduje (napr. category=xyz&do=show&bla). V prípade, ak cieľový formulár "nežerie" hodnoty cez GET, ale cez POST v http hlavičke, je možné jednoducho použiť prepínač --post-data, viď. wget(1). Napríklad:

wget --post-data="id=$i" -O - http://zvieratko.db/show.cgi

1.1 Ide to pomaly...

Dajme tomu, že jeden takýto wget zbehne za sekundu, čas parsovania a ukladania na disk zanedbáme. Potom stiahnutie bude trvať približne 5389180/3600/24 = 62.4 dní.

Preto skript rozdelíme na viaceré (rôzne počiatočné a koncové $i, a tiež rôzne .txt [aby sme nemuseli riešiť race conditions]) a pustíme na jednom alebo viacerých strojoch. Kým to cieľová mašina stíha (aj naša mašina a [bez]drôt), môžeme dosiahnuť až N-násobné zrýchlenie (N je počet navzájom rýchlostne neovplyvnených "tahačov").

1.2 Ošetrovanie chýb

Wget nemusíme smerovať priamo do paličky, jeho výstup môžeme napríklad ukladať do /tmp/bla$$ a následne zisťovať, či sa podarilo daný záznam stiahnuť - pri chybe opakovať, alebo zapísať hlášku do logu, apod. Konkrétna implementácia závisí od detailov danej DB a prístupu k nej, toto necháme na rozcvičku pre čitateľa.

2. Obmedzenia

Keďže prevádzkovateľ ZvieratkoDB nechce, aby sme pomocou 25 ťahačov stiahli jeho databázu za 2.5 dňa, môže nám klásť do cesty rôzne prekážky. Povieme si, ako väčšinu z nich obísť, počet potrebných ťahačodní (alebo strojových hodín) nám však s každým obmedzením narastá.

2.1 Náhodné id

Id v databáze (resp. id, ktoré "žerie" show.cgi) nemusia byť za sebou nasledujúce. Ak sú kroky medzi nimi malé, môžeme ich brute-forcenúť, spomalenie bude úmerné priemernej veľkosti kroku medzi dvoma susednými id.

Ak sú kroky medzi jednotlivými id príliš veľké na brute-force (ak by sme sa vyšplhali na neúnosný počet ťahačodní), musíme nejako získať zoznam id. Pristupujeme k tomu podobne, ako bežný smrteľník, teda cez search.cgi, v skratke:

#!/bin/bash
# priklad c.2, tahanie id podla wordlistu
for n in `cat wordlist.txt`; do
	wget -O - http://zvieratko.db/search.cgi?meno=$n \
	| grep ... | sed ... >> idlist.txt
done

Ako wordlist môžeme použiť všeobecnú encyklopédiu zvieratiek, korpus, rôzne predtým stiahnuté db apod.

Takto získaný zoznam id potom stiahneme:

#!/bin/bash
# priklad c.3, tahanie zvieratiek podla stiahnuteho idlistu
for i in `cat idlist.txt`; do
	wget -O - http://zvieratko.db/show.cgi?id=$i \
	| grep ... | sed ... >> LokalneZvieratkoDB.txt
done

Rozdelenie na viaceré ťahače pomocou `split -l`, ako aj ošetrovanie chýb, je analogické ku prvému, sekvenčnému príkladu. Jednotlivé ťahače dáme do rôznych adresárov (každý bude mať vlastný idlist.txt, aj výstupný LokalneZvieratkoDB.txt), na rôzne stroje, alebo ich odlíšime nejakým parametrom pri spúšťaní (instance=$1), ktorý pripájame k názvom súborov (idlist-$instance.txt, LokalneZvieratkoDB-$instance.txt).

Dobré na search.cgi môže byť:

  • nemusí byť tak limitovaný ako show.cgi (viď. ďalej "Limit na IP"),
  • pri niektorých db dokonca netreba zadávať celé meno (stačia 2-3 písmená, ktoré vo vnorenom cykle prejdeme -- netreba wordlist)
  • pľuje naraz celé stránky so zoznamom idčiek (ak sú mená podobné, resp. začínajú na rovnaké zadané písmená)

Zlé na search.cgi môže byť vyžadovanie ďalších parametrov, napríklad meno + mesto, alebo meno + okres, čím sa zvyšujú nároky na náš ťahač (pridáme ďalšie cykly), ale aj sa oveľa viac znižuje komfort používateľa.

2.2 Limit na IP

Prevádzkovateľ DB si môže dať záležať na tom, koľko kto ťahá. Obmedzenie sa väčšinou týka iba show.cgi; môže sa vzťahovať na cookies (v prípade wgetu zanedbáme), alebo IP adresu.

Môže byť na ľubovoľný časový úsek, typicky na 1 deň (zistíme empiricky pomocou slučky s výpisom `date` do logu). Dajme tomu, že prevádzkovateľ ZvieratkoDB sa rozhodol pre limit 100 záznamov/IP/deň.

Obnova v čase je napríklad:

  • nulovanie o polnoci --- O polnoci (alebo v nejakú inú, presne stanovenú hodinu) sa počítadlá pre všetky IP vynulujú, a môžeme ťahať znovu. Stiahneme teda povolený počet každý deň v ľubovoľnú hodinu. V extrémnom prípade, ak máme dostatočný počet IP (viď. nižšie) a vzhľadom na rozmery databázy a nastavený limit by sme vedeli stiahnuť celú DB za 2 dni, urobíme to cez noc (prvá dávka napr. 21:00, druhá po polnoci). Ráno sa prevádzkovateľ môže už iba diviť, ako mu cez noc narástli logy a hit-countery.
  • nulovanie po stanovenom čase neaktivity --- Medzi jednotlivé dávky ťahania vložíme dostatočne dlhý `sleep`.
  • ako token bucket (priebežné znižovanie) --- Medzi jednotlivé wgety vložíme adekvátne dlhý `sleep`.
#!/bin/bash
# priklad c.4, tahanie zvieratiek podla idlistu, po davkach
c=0
for i in `cat idlist.txt`; do
	c=$((c+1))
	wget -O - http://zvieratko.db/show.cgi?id=$i \
	| grep ... | sed ... >> LokalneZvieratkoDB.txt
	if [ $c -eq 100 ]; then c=0; sleep 86400; fi
done

Ako získať veľa IP?

V IPv4 existuje menej ako 232 použiteľných verejných(!) IP adries. Bežný smrteľník môže byť rád, ak má vôbec jednu vlastnú. Kde ich teda zohnať, keď ich treba?

Vezmime prípad ZvieratkoDB s obmedzením 100 záznamov/IP/deň pre show.cgi. Potrebujeme takmer 54,000 ipdní. Ak to chceme stihnúť do mesiaca, treba nám okolo 1800 rôznych IP. Táto na prvý pohľad neuveriteľná požiadavka nemusí byť až tak nesplniteľná, ak uvážime:

  • Dynamicky prideľované IP adresy --- odpojením a pripojením (dialup, ADSL, ...) môžeme získať novú adresu. Ak to vieme za deň úspešne spraviť z jedného miesta 1800-krát (alebo z viacerých miest menejkrát), môžeme byť vychechtaní.

    # v priklade c.4 namiesto sleep restartneme ppp

    Nie vždy nám ale bude pridelená iná IP, preto tento spôsob nie je práve na pomerne veľkú ZvieratkoDB vhodný.

  • Veľké prázdne subnety --- Ak sme na väčšom subnete s verejnými IP adresami a miestne podmienky to umožňujú (napr. ethernet, nie point-to-point), zmenu IP adresy môžeme spraviť jednoducho pomocou ifconfig. Nie je možné aplikovať v sieťach, kde je natvrdo transparentný http proxy redirectovaný pre všetky spojenia na port 80 (teda napríklad niektoré internátne siete, kde by inak použiteľných IP boli tisíce [cez deň keď študenti nedrvia]). Tiež sa nedá v sieťach, kde je väčšina adries obsadených -- jedine ak sedíme na routri a adresu si bez súhlasu na tých pár sekúnd "požičiame". A samozrejme nepodarí sa v sieťach, kde má router napevno ARP tabuľku (nebýva časté).

    Ak nechceme prísť o vlastnú konektivitu, môžeme použiť subinterface, teda napríklad ifconfig eth0:$instance, a následne wget --bind-address. Nezabudnúť povoliť si IP v iptables, OUTPUT chain.

  • Proxy servery --- po internete sa povaľujúce voľne prístupné proxy, zriedka to však býva dostatkový tovar. Zoznam googlime, alebo nmapujeme náhodné stroje na portoch 8080, 3128.

    #!/bin/bash
    # priklad c.5, tahanie zvieratiek so za sebou nasledujucimi id
    # cez proxace
    i=0; L=5389180
    for proxy in `cat zoznamproxy.txt`; do
    	c=0
    	while [ $c -lt 100 ]; do
    		i=$((i+1)); c=$((c+1))
    		http_proxy="$proxy" \
    		wget -O - http://zvieratko.db/show.cgi?id=$i \
    		| grep ... | sed ... >> LokalneZvieratkoDB.txt
    		if [ $i -eq $L ]; then exit; fi
    	done
    done
  • Tor --- anonymizujúci software, ktorý využíva spleť Tor serverov na nadväzovanie spojenia. Zabudované šifrovanie a 3 hopy sú skôr na obtiaž, nám by totiž stačil koncový bod (na tento účel by bolo možné pokúsiť sa upraviť zdrojáky, dajte vedieť ak sa do toho niekto idete púšťať) -- je preto trochu pomalý, hlavne pre to, že deti cez to ťahajú torrenty, na zväčšenie nášho "IP poolu" je to ale celkom zaujímavý program.

    Tor funguje iba ako SOCKS proxy, čo wget nevie, preto je nutné použiť nejaký "http proxy -> SOCKS proxy" medzičlánok. Napríklad privoxy, ktorý má v dodávanom konfiguráku pripravenú sekciu pre spoluprácu priamo s Tor-om, stačí odkomentovať. Nastavíme rozumné hodnoty pre "NewCircuitPeriod" a "MaxCircuitDirtiness", wget spúšťame s premennou http_proxy="127.0.0.1:9050".

  • Botnety všakovakého druhu --- ideálna voľba pre hardcore ťahačov. Sťahovanie vykonávame z napadnutých mašín, ktorých je veľa. Tí, čo majú veľa peňazí, si botnet kúpia; tí, čo majú veľa času, si to nakódia a rozšíria sami. A my ostatní sa spokojíme so subnetmi, http_proxy a Tor-om (ak ho niekto upraví).

2.3 Captcha

Prevádzkovateľ ZvieratkoDB, ktorý naozaj veľmi nechce, aby sme si databázu stiahli "strojovo", zabezpečí prístup ku show.cgi pomocou tzv. captcha obrázkov. Tieto nielenže znepríjemňujú život obyčajným smrteľníkom (najmä tým s horším zrakom), znemožňujú celý pobyt na stránke používateľom textových terminálov (aj keď už som videl aj ASCIIart captcha, čo ma veľmi potešilo), ale ak sú dobre vymyslené, aj zabraňujú "strojovému" ťahaniu. Vytrvalého ťahača ale ani captcha neodradí, môže totiž skúsiť:

  • OCR --- slabo "spotvorené" texty na obrázku je možné prečítať (a v rámci pokroku číslicového spracovania sa možno o pár [desiatok] rokov dočkáme úplného vyhynutia captcha v textovo-obrázkovej forme).

  • Brute-force --- ak má captcha veľmi málo znakov (2-3), môžeme ho strojovo "hádať", veľmi to však predlžuje počet potrebných IPdní. Na ťahanie väčšej databázy je tento prístup bez obrovského botnetu nepoužiteľný.

  • "Hacknúť" zle spravené captcha --- slabá implementácia môže viesť k jednoduchému a rýchlemu strojovému riešeniu (niekedy priam triviálnemu). Náchylné zvyknú byť také captcha, ktoré niekto omylom nazval "technológiou", zatiaľ čo ich navrhoval stredoškolák, synček podpredsedu, bez zjavných teoretických znalostí alebo serióznych praktických skúseností. Konkrétne aj prevádzkovateľ ZvieratkoDB ;).

    Plaintextová hodnota v Cookie alebo v skrytej premennej vo formulári je lahôdkou pre grep|sed:

    <INPUT type="hidden" name="porovnaj_s" value="k682k4sfds">
    <IMG src="captcha/k682k4sfds.gif">
    Takto testujeme, ci nie si slepy:
    <INPUT type="text" name="pouzivatel_zadal" value="" size="10">

    Iný prípad (tiež triviálny) je znovupoužitie, teda niečo na spôsob:

    <INPUT type="hidden" name="captchaid" value="98745">

    Kontroluje sa, či zadaný text je správny pre dané captchaid (uložené v databáze, alebo nejakej session, bez kontroly znovupoužitia). Captcha ručne vyriešime raz a použijeme L krát takto:

    wget -O - "http://zvieratko.db/show.cgi?id=$i\
    &captchaid=98745&pouzivatel_zadal=k682k4sfdd"

    Vyzývam všetkých, čo to pochopili, aby to nešli tým lamám na zvieratko.db vysvetľovať (ani iným lamám, a už vôbec nie novinárom) -- chceme totiž naďalej voľne stiahnuteľnú ZvieratkoDB.

  • Riešenie náhodnými ľuďmi --- ak vlastníme (alebo nejaký kamarát, známy, apod. má v správe) často navštevovanú (porno) stránku, môžeme pomocou skriptu stiahnuť obrázok, ktorý chceme lúštiť, prezentovať ho návštevníkovi ako podmienku vstupu na stránku, a obratom použiť na cieľovej DB.

  • Najímanie ľudí --- každý piaty je Číňan, preto by nemal byť problém nájsť ich dostatočný počet na vyriešenie úlohy v požadovanom čase ;).

Nie vždy je nutné captcha naozaj riešiť. Niekde môže byť použité ako prevencia príliš častého ťahania, t.j. nie je vyžadované pri každom show.cgi, ale až po prekročení limitu (na umožnenie neobmedzeného prezerania ľuďmi). V takých prípadoch, ak captcha nemáme možnosť strojovo vyriešiť/obísť, ho skrátka ignorujeme a rešpektujeme limit. Ťahanie pokračujeme z inej IP (viď. časť Limit na IP).

2.4 Ďalšie obmedzenia

Prípadné ďalšie obmedzenia môžu síce prispieť ku vyššej ochrane pred stiahnutím databázy "ťahačmi", výrazne však znižujú možnosti prístupu ku stránke rôznym alternatívnym web browserom, proxy cache serverom, zafirewallovaným systémom s anonymizérmi atď. Napriek "dobrému úmyslu" to môže dopadnúť tak, že web stránka by bola prístupná iba ľuďom s WinXP/Vista s plne opatchovaným IE7, so zapnutými obrázkami, bez proxy servera a firewallu po ceste.

Jedná sa o obmedzenia ako napríklad:

  • Kontrolovanie http Referer hlavičky;
  • Povolenie prístupu iba niektorým prehliadačom (zakázanie wgetu);
  • Vyžadovanie Cookies;

Keď na to príde, všetky tieto vlastnosti sa dajú pomocou wget nasimulovať (--referer, --user-agent, --load-cookies), a vytrvalý ťahač bude na tom nakoniec stále lepšie, ako nevinný smrteľník, ktorému tá stránka napr. v Opere ani za svet nepôjde.

3. Iné vychytávky

Nechceme sa tváriť pri ťahaní ZvieratkoDB príliš nápadne. Ak vieme vyčerpať denný limit za pol hodinu, nebudeme tú pol hodinu spamovať cieľ -- mohlo by to viesť ku zahlteniu (a spomaleniu) servera, alebo sa výrazne prejaviť na grafoch prevádzky, čo by si mohol niekto "zodpovedný" všimnúť. Pridáme sem-tam nejaký ten sleep pred wget, prípadne na ťahanie použijeme business hours, nech sa "stratíme" medzi bežným trafficom.

Taktiež nemusíme ťahať záznamy porade, ale môžeme sa tváriť náhodne. Na tento účel môžeme "idlist.txt" prehnať cez rl, alebo využiť primitívne čísla modulo P (P prvočíslo, P>L).

Ťahači, ktorí to myslia vážne, si do bash skriptu dorobia veci ako ukladanie stavu pri signále (napr. trap ulozstav INT HUP), multifunkciu proxy/zmenaIP, a čokoľvek ďalšie, čo ich napadne.

4. Záver

Stiahnutú databázu buď ponecháme v univerzálnom formáte .txt, alebo podstrčíme mladšiemu súrodencovi na nakódenie frontendu pre sql kvôli priamym aj reverzným lookupom. V prípade ZvieratkoDB nezabudnúť na recode windows1250..iso8859-2.

Žiadna verejne prístupná DB nie je strojovo nestiahnuteľná. Niekedy sa to dá poriadne skomplikovať, ak sa to však preženie, ani bežný smrteľník sa k tomu potom nedostane. Použitím "rozumného" IP limitu vie prevádzkovateľ DB síce odradiť slabších ťahačov, ale zároveň zrušiť väčšie siete používajúce proxy (spomeňme napríklad bratislavský roburnet alebo ynet, a väčšina firemných sietí).

Napríklad taká ZvieratkoDB je stiahnuteľná za 54k IPdní (napriek tomu, že má captcha "ochranu"), čo je iba mesiac z biednych 1800 verejných IP ;) Použitím dobre implementovaného captcha vie prevádzkovateľ odradiť aj tých hustejších ťahačov. Vytrvalý a odhodlaný ťahač s dostatočným počtom Číňanov ale stiahne všetko :)

No a keď databáza nie je verejne prístupná, ostávajú už len metódy, ktoré sa vo všeobecnosti môžu nazývať ("hackerskými") prienikmi.


Pôvodne uverejnené po častiach na portáli blackhole.sk: časť 1, časť 2, časť 3, kde si môžete pozrieť aj diskusiu.