Kas slepiasi už kelio užklausų
Kai kuriate web aplikaciją, dažniausiai galvojate apie funkcionalumą, dizainą, vartotojo patirtį. Bet ar kada sustojote pagalvoti, kas nutinka, kai kažkas URL eilutėje įrašo kelis nekaltai atrodančius simbolius `../`? Štai čia ir prasideda directory traversal atakų pasaulis – viena iš tų saugumo spragų, kuri atrodo paprasta, bet gali padaryti tikrą chaosą jūsų sistemoje.
Directory traversal, dar žinoma kaip path traversal ar dot-dot-slash ataka, leidžia piktavaliui pasiekti failus ir katalogus, kurie yra už web serverio šakninio katalogo ribų. Įsivaizduokite situaciją: jūsų aplikacija leidžia vartotojams parsisiųsti PDF dokumentus per URL parametrą, pavyzdžiui `download.php?file=report.pdf`. Kas nutiks, jei kažkas pakeis tą parametrą į `../../../../etc/passwd`? Jei sistema tinkamai nevaliduoja įvesties, puolėjas gali gauti prieigą prie kritinių sistemos failų.
Problema ta, kad daugelis programuotojų tiesiog pasitiki, jog vartotojai elgsis gerai. Bet realybė tokia – internete pilna žmonių, kurie aktyviai ieško būdų kaip išnaudoti tokias spragas. Ir directory traversal atakos yra vienos iš pirmųjų, kurias išbando bet kuris pradedantis pentesteris ar hakeris.
Kaip veikia šios atakos praktikoje
Pažvelkime į konkretų pavyzdį. Tarkime, turite PHP aplikaciją, kuri rodo vaizdus pagal vartotojo pasirinkimą:
„`php
„`
Atrodo nekaltas kodas, tiesa? Bet štai kas nutinka, kai puolėjas URL įrašo: `image=../../../etc/passwd`. Sistema sukonstruoja kelią `/var/www/images/../../../etc/passwd`, kuris realiai veda į `/etc/passwd` – failą su sistemos vartotojų informacija.
Dar blogiau – Windows sistemose puolėjai gali naudoti ne tik forward slash (`/`), bet ir backslash (`\`) simbolius. Be to, egzistuoja įvairios koduotės variacijos, kurios gali apeiti primityvius filtrus. Pavyzdžiui, `%2e%2e%2f` yra URL-encoded versija `../`, o `..%c0%af` – viena iš UTF-8 overlong encoding variantų, kuri kai kuriose sistemose taip pat veikia.
Realūs atakų scenarijai gali būti dar sudėtingesni. Puolėjai dažnai kombinuoja directory traversal su kitomis technikomis – pvz., bandydami pasiekti aplikacijos konfigūracijos failus (`config.php`, `.env`), duomenų bazės kredencialus, ar net serverio log failus, kuriuose gali būti session tokens ar kita jautri informacija.
Kodėl tradicinė validacija neveikia
Daugelis programuotojų mano, kad paprastas blacklist filtras išspręs problemą. Parašo kažką panašaus:
„`php
if(strpos($file, ‘../’) !== false) {
die(‘Nice try!’);
}
„`
Problema ta, kad puolėjai jau seniai žino apie tokius filtrus ir turi dešimtis būdų juos apeiti. Štai keletas populiariausių technikų:
**URL encoding variacijos** – vietoj `../` naudoti `%2e%2e%2f` arba dvigubą encoding `%252e%252e%252f`. Jei jūsų sistema automatiškai dekoduoja URL, bet filtras veikia prieš dekodavimą, ataka praeis.
**Null byte injection** – senesniuose PHP variantuose simbolis `%00` nutraukdavo string’ą. Taigi `image=../../../../etc/passwd%00.jpg` apeitų patikrinimą, kuris tikrina ar failas baigiasi `.jpg`.
**Nested sequences** – vietoj `../` naudoti `….//` arba `…/./`. Kai kurie filtrai pašalina `../` sekos, bet daro tai tik vieną kartą, todėl `….//` po pašalinimo tampa `../`.
**Case sensitivity tricks** – Windows sistemose `..\/` arba `..\` veikia taip pat kaip `../`, bet ne visi filtrai tai tikrina.
Realybė tokia, kad blacklist metodai directory traversal atveju yra praktiškai beverčiai. Jūs niekada negalite būti tikri, kad pagavote visas galimas variacijas.
Whitelist metodika – tikrasis sprendimas
Vietoj to, kad bandytumėte užblokuoti visas blogas įvestis, geriau leiskite tik tas, kurios yra aiškiai saugios. Tai fundamentalus skirtumus tarp blacklist ir whitelist požiūrių.
Praktiškai tai gali atrodyti taip:
„`php
„`
Šis metodas garantuoja, kad vartotojas gali pasiekti tik tuos failus, kuriuos jūs aiškiai leidžiate. Jokios manipuliacijos su keliu čia nepadės.
Bet kas daryti, kai negalite iš anksto žinoti visų failų pavadinimų? Pavyzdžiui, vartotojai gali upload’inti savo failus. Tokiu atveju reikia naudoti kitus saugumo sluoksnius:
**Naudokite ID vietoj failų pavadinimų** – vietoj to, kad URL būtų `download.php?file=mydocument.pdf`, naudokite `download.php?id=12345`. Tada duomenų bazėje saugokite ryšį tarp ID ir tikrojo failo pavadinimo/kelio.
**Kanonizuokite kelius** – daugelyje programavimo kalbų yra funkcijos, kurios konvertuoja bet kokį kelią į jo „canonical” formą, pašalindamos visus `../`, simbolinius nuorodas ir pan. PHP turi `realpath()`, Python – `os.path.realpath()`, Node.js – `path.resolve()`.
„`php
„`
Serverio konfigūracijos svarba
Aplikacijos lygmens apsauga yra svarbi, bet neužtenka. Serverio konfigūracija turi būti jūsų antroji gynybos linija.
**Chroot jail** – Unix sistemose galite apriboti procesą, kad jis matytų tik tam tikrą katalogų medžio dalį. Tai reiškia, kad net jei puolėjas sugebėtų išnaudoti directory traversal spragą, jis negalėtų pasiekti failų už chroot aplinkos ribų.
**Failo sistemos leidimai** – web serveris turėtų veikti su minimaliomis teisėmis. Jei jūsų Apache ar Nginx procesas veikia kaip `www-data` vartotojas, įsitikinkite, kad šis vartotojas turi read prieigą tik prie būtinų katalogų. Kritiniai sistemos failai kaip `/etc/shadow` turėtų būti visiškai nepasiekiami.
**SELinux ar AppArmor** – šios Linux saugumo sistemos leidžia apibrėžti labai detalizuotas taisykles, kokius failus kiekvienas procesas gali pasiekti. Pavyzdžiui, galite nustatyti, kad Apache procesas gali skaityti tik iš `/var/www/` ir `/tmp/` katalogų.
Štai Apache konfigūracijos pavyzdys, kuris riboja prieigą:
„`apache
Options -Indexes -FollowSymLinks
AllowOverride None
Require all granted
Options None
AllowOverride None
Require all denied
„`
`-FollowSymLinks` opcija ypač svarbi – ji neleidžia serveriui sekti simbolinių nuorodų, kurios galėtų vesti už leidžiamo katalogo ribų.
Automatizuotas testavimas ir aptikimas
Saugumo spragos dažnai atsiranda ne dėl to, kad programuotojai nežino kaip rašyti saugų kodą, bet dėl to, kad jie tiesiog pamiršta ar nepastebi tam tikrų vietų. Todėl automatizuotas testavimas yra kritinis.
**SAST (Static Application Security Testing)** įrankiai gali analizuoti jūsų kodą ir rasti potencialias directory traversal spragas dar prieš deploy’inant į produkciją. Pavyzdžiui, SonarQube su security pluginais gali aptikti pavojingus kodo pattern’us.
**DAST (Dynamic Application Security Testing)** įrankiai, tokie kaip OWASP ZAP ar Burp Suite, gali automatiškai testavimą veikiančią aplikaciją, bandydami įvairias directory traversal atakas. Šie įrankiai turi išsamias payload bibliotekas su šimtais variantų.
Bet nepasitikėkite tik automatizuotais įrankiais. Reguliarūs penetration testai su tikrais saugumo specialistais gali rasti spragas, kurių automatika nepastebi. Žmogus gali suprasti kontekstą ir sugalvoti kūrybiškesnius išnaudojimo būdus.
Integruokite saugumo testus į CI/CD pipeline. Kiekvienas commit turėtų automatiškai trigger’inti saugumo skenavimą. Jei aptinkama potenciali spraga, build’as turėtų fail’intis. Taip saugumo problemos niekada nepateks į produkciją.
Logging ir monitoring kaip detektyvo įrankiai
Net su geriausiomis apsaugomis, visada yra tikimybė, kad kažkas bandys atakuoti jūsų sistemą. Todėl svarbu ne tik prevencija, bet ir aptikimas.
Loginkite visas įtartinas užklausas. Jei matote URL parametruose `../`, `..%2f`, ar kitus įtartinus pattern’us – tai turėtų trigger’inti alert’ą. Bet būkite atsargūs su false positive – kartais teisėti vartotojai gali netyčia įvesti kažką panašaus.
„`python
import re
import logging
def check_traversal_attempt(user_input):
suspicious_patterns = [
r’\.\.’,
r’%2e%2e’,
r’%252e’,
r’\.\./’,
r’\.\.\\’
]
for pattern in suspicious_patterns:
if re.search(pattern, user_input, re.IGNORECASE):
logging.warning(f”Possible directory traversal attempt: {user_input}”)
return True
return False
„`
Naudokite SIEM (Security Information and Event Management) sistemas, kurios gali koreliuoti įvykius iš skirtingų šaltinių. Pavyzdžiui, jei tas pats IP adresas per trumpą laiką daro daug užklausų su įvairiais directory traversal payload’ais – tai aiškus atakos požymis.
Rate limiting taip pat svarbus. Net jei puolėjas rado spragą, apribokite kiek užklausų jis gali padaryti per tam tikrą laiką. Tai sulėtins duomenų ištraukimą ir duos jums laiko reaguoti.
Realūs incidentai ir ko iš jų išmokome
2019 metais Citrix Gateway turėjo kritinę directory traversal spragą (CVE-2019-19781), kuri leido puolėjams vykdyti savavališką kodą. Problema buvo ta, kad sistema netinkamai validavo XML užklausas, leidžiančias pasiekti failus už web root. Šią spragą aktyviai išnaudojo ransomware grupės, o paveiktų organizacijų skaičius siekė dešimtis tūkstančių.
Kas įdomu – Citrix išleido patch’ą, bet daugelis organizacijų neinstaliavo jo laiku. Tai parodo, kad techninis sprendimas nieko nereiškia, jei nėra tinkamų procesų.
Kitas pavyzdys – 2021 metais Microsoft Exchange Server turėjo keturias zero-day spragas (ProxyLogon), viena iš kurių buvo directory traversal. Kinijos valstybės remiami hakeriai ją išnaudojo prieš tai, kai Microsoft spėjo išleisti patch’ą. Pasekmės – šimtai tūkstančių paveiktų serverių visame pasaulyje.
Šie incidentai moko kelių dalykų. Pirma, directory traversal nėra tik teorinė problema – tai aktyviai išnaudojama spraga su realiais padariniais. Antra, net didžiausios kompanijos su milžiniškais saugumo biudžetais daro klaidas. Trečia, greitas patch’inimas yra kritiškai svarbus.
Kaip sukurti saugią failų valdymo sistemą
Jei kuriate sistemą, kuri turi dirbti su failais pagal vartotojo įvestį, štai keletas principų, kurių turėtumėte laikytis:
**Niekada nenaudokite vartotojo įvesties tiesiogiai failo kelyje**. Net jei manote, kad validavote – nenaudokite. Vietoj to, naudokite indirection layer – duomenų bazę ar mapping struktūrą.
**Saugokite failus už web root**. Jei jūsų web serverio root yra `/var/www/html`, saugokite upload’intus failus `/var/www/uploads` arba dar geriau – visiškai kitoje vietoje, pvz. `/opt/app/storage`. Tada naudokite PHP/Python/Node.js scriptą, kuris patikrina teises ir tik tada atiduoda failą.
**Generuokite atsitiktinius failų pavadinimus**. Kai vartotojas upload’ina `mydocument.pdf`, išsaugokite jį kaip `a3f5c8b2-9e4d-4a1b-8c3f-2d5e6f7a8b9c.pdf`. Originalų pavadinimą saugokite duomenų bazėje metadata.
„`javascript
const crypto = require(‘crypto’);
const path = require(‘path’);
function generateSafeFilename(originalName) {
const ext = path.extname(originalName);
const randomName = crypto.randomBytes(16).toString(‘hex’);
return `${randomName}${ext}`;
}
„`
**Implementuokite content-type validation**. Net jei failas turi `.jpg` extension, patikrinkite ar tikrai tai yra JPEG failas. Puolėjai dažnai bando upload’inti PHP scriptus su `.jpg` extension.
**Naudokite Content-Disposition headerį**. Kai atiduodate failą vartotojui, nustatykite:
„`
Content-Disposition: attachment; filename=”safe_name.pdf”
„`
Tai užtikrina, kad naršyklė parsisiųs failą vietoj to, kad bandytų jį vykdyti.
Ką daryti kai jau per vėlu
Tarkime, blogiausia įvyko – sužinojote, kad jūsų sistema buvo pažeidžiama directory traversal atakai. Galbūt aptikote įtartinę veiklą loguose, arba dar blogiau – kažkas jums pranešė apie duomenų nutekėjimą.
Pirmiausia – **nesipanikuokite, bet veikite greitai**. Turite planą:
1. **Izoliuokite problemą** – jei įmanoma, laikinai išjunkite pažeidžiamą funkcionalumą. Geriau turėti neveikiančią feature nei pažeidžiamą sistemą.
2. **Įvertinkite žalą** – peržiūrėkite logus ir išsiaiškinkite, kokie failai galėjo būti pasiekti. Ar tai buvo tik konfigūracijos failai? Ar duomenų bazės kredencialai? Ar vartotojų duomenys?
3. **Pakeiskite kredencialus** – jei yra bent menkiausia tikimybė, kad buvo pasiekti kredencialai (duomenų bazės slaptažodžiai, API raktai, SSL sertifikatai), pakeiskite juos VISUS. Taip, tai skausminga, bet būtina.
4. **Pranešite suinteresuotoms šalims** – priklausomai nuo situacijos ir jurisdikcijos, gali tekti pranešti reguliuotojams (pvz., GDPR atveju), vartotojams, partneriams.
5. **Pataisykite spragą** – ne tik quick fix, bet fundamentalus sprendimas. Peržiūrėkite visą kodą, kuris dirba su failais.
6. **Atlikite post-mortem analizę** – kaip tai nutiko? Kodėl neaptikote anksčiau? Ką galite pakeisti procesuose, kad tai nepasikartotų?
Dokumentuokite viską. Jei vėliau kils teisinių klausimų ar audito, turėsite įrodyti, kad veikėte atsakingai ir greitai.
Saugumo kultūra, ne tik technologija
Galiausiai, directory traversal apsauga – kaip ir bet kuris kitas saugumo aspektas – nėra tik technologijų klausimas. Tai kultūros klausimas.
Daugelyje organizacijų saugumas yra „kažkieno kito” problema. Programuotojai mano, kad tai DevOps komandos rūpestis. DevOps mano, kad tai saugumo komandos atsakomybė. O saugumo komanda neturi resursų viską patikrinti.
Realybė tokia, kad saugumas turi būti kiekvieno atsakomybė. Kiekvienas programuotojas turėtų žinoti bent pagrindines OWASP Top 10 spragas, įskaitant directory traversal. Code review procesas turėtų įtraukti saugumo aspektus. Nauji team nariai turėtų gauti saugumo mokymą.
Sukurkite saugų kodavimo gaires savo organizacijai. Ne 100 puslapių dokumentą, kurį niekas neskaitys, bet praktiškus pavyzdžius ir code snippets, kuriuos programuotojai gali tiesiog copy-paste’inti ir adaptuoti.
Skatinkite atvirą komunikaciją apie saugumo problemas. Jei programuotojas randa spragą savo ar kolegos kode, jis neturėtų bijoti apie tai kalbėti. Bug bounty programos gali būti naudingos ne tik išoriniams tyrinėtojams, bet ir kaip būdas motyvuoti savo darbuotojus ieškoti problemų.
Ir svarbiausia – mokykitės iš klaidų. Kai įvyksta incidentas, nesusitelkite į kaltinimus, o į tai, kaip sistema gali būti patobulinta. Postmortem analizės turėtų būti konstruktyvios ir orientuotos į sprendimus.
Directory traversal atakos gali atrodyti kaip pasenusi, paprasta problema. Bet kaip matėme, jos vis dar aktualios, vis dar išnaudojamos, ir vis dar gali padaryti rimtos žalos. Tinkama validacija, defense in depth principas, reguliarus testavimas ir saugumo kultūra – štai kas apsaugo jūsų sistemas. Ne vienas iš šių dalykų, o visi kartu. Saugumas nėra vienkartinis projektas, o nuolatinis procesas. Ir kuo anksčiau tai suprasite, tuo saugesnės bus jūsų aplikacijos.
