10.6 Input validáció security szemszögből
Áttekintő
Az előző fejezetekben (8.5) már szó volt arról, hogy a felhasználói inputot validálni kell – akkor főleg a production-ready kód szemszögéből. Most ugyanezt a témát vesszük elő, de security fókusszal.
A különbség nem apró: egy UX-szempontú validáció azt mondja, „a mezőbe szám kerüljön". Egy security-szempontú validáció azt mondja, „semmit ne higgyek el, amit a felhasználó küld – még akkor sem, ha szám érkezik."
Ez az alfejezet arról szól, hogyan gondolkodik egy security-tudatos fejlesztő az inputról. Nem kell security expertté válnod. De ezt az egyetlen szabályt jegyezd meg, és tarts ki mellette:
Never trust user input. Soha, semmilyen körülmények között.
Részletes leírás
A validáció mint biztonsági eszköz – nem csak UX
Sokan azt hiszik, hogy az input validáció azért van, hogy a felhasználó ne hibázzon. Ez igaz – de csak a fele az igazságnak.
A másik fele: az input validáció egy biztonsági kapu. Minden adat, ami a rendszeredbe kerül a külvilágból, potenciális támadási felület.
A külvilág nem csak az ügyfeleidből áll. Egy támadó:
- közvetlenül HTTP kéréseket küld az API-odra (böngészőt teljesen megkerülve),
- módosítja a kéréseket (pl. Burp Suite, curl, Postman),
- automatizált scriptet futtat ezer különböző payloaddal,
- megkerüli a kliensoldali validációt – az mindig megkerülhető.
A kliensoldali validáció kényelmi funkció. A szerveroldali validáció biztonsági eszköz.
Ha csak kliensen validálsz, az olyan, mintha a bankfiókban a recepciós asztalra kitennél egy táblát: „Lopni tilos." A tolvaj megkerüli a táblát, és bemegy.
Whitelist vs. blacklist megközelítés
Ez az egyik legfontosabb gondolkodásmódbeli döntés validáció esetén.
Blacklist (tiltólista): meghatározod, mi NEM mehet át.
# Blacklist megközelítés – gyenge
def validate_username(username):
forbidden = ["<", ">", "'", '"', ";", "--"]
for char in forbidden:
if char in username:
return False
return True
Miért gyenge? Mert a támadó csak olyat keres, amit elfelejtettél tiltani. A blacklist soha nem teljes. Ha SQL injection ellen védesel így, a támadó találni fog egy encodingot vagy egy variánst, ami átcsúszik.
Whitelist (engedélyezési lista): meghatározod, mi mehet át. Minden más tiltott.
# Whitelist megközelítés – erős
import re
def validate_username(username):
# Csak betű, szám, kötőjel, 3-30 karakter
pattern = r'^[a-zA-Z0-9\-]{3,30}$'
return bool(re.match(pattern, username))
Ez a logika: „Pontosan tudom, mi az érvényes. Minden más le van tiltva."
Alapszabály: mindig whitelistelj, ha lehetséges.
Kivétel: szabad szöveges mezőknél (pl. komment, leírás) nem lehet teljesen whitelist alapon menni. Ott a sanitizáció a megoldás (escape, HTML encoding), nem a teljes tiltás.
A szerveroldali validáció elmaradhatatlan
Nézzük meg, mi történik, ha csak kliensoldali validáció van:
// Frontend form validáció – JavaScript
document.getElementById('age').addEventListener('input', function() {
if (this.value < 0 || this.value > 150) {
this.setCustomValidity('Érvénytelen kor');
}
});
Ez a validáció a böngészőben fut. Bárki, aki ismeri a DevTools-t, letilthatja ezt a kódot. Bárki, aki curl-t vagy Postmant használ, soha nem is látja.
Egy támadó így küld kérést:
curl -X POST https://yourapp.com/api/user \
-H "Content-Type: application/json" \
-d '{"age": -99999}'
A böngésző validációja ezzel szemben tehetetlen.
Minden inputot a szerveren kell validálni. A kliensoldali validáció csak UX – felhasználói élményt javít, nem biztonságot.
File upload biztonság
A fájlfeltöltés az egyik leggyakrabban elrontott funkció junior fejlesztőknél. Látszólag egyszerű – a felhasználó feltölt egy fájlt, te elmented. Mi ennél az egyszerűbbnél is bonyolódhat?
Sok minden.
1. probléma: fájltípus ellenőrzés csak kiterjesztés alapján
// ROSSZ – csak a nevet nézi, könnyű megkerülni
$extension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
if ($extension !== 'jpg') {
die('Csak JPG engedélyezett!');
}
Egy támadó feltölthet egy malware.php-t, amelyet átnevez malware.jpg-re. A kiterjesztés-ellenőrzés megkerülhető.
2. probléma: a MIME type is hamisítható
A $_FILES['file']['type'] értékét a böngésző küldi – tehát a kliens. Hamisítható.
Mit csinálj helyette?
// JOBB – fájl tartalmát ellenőrzöd, nem csak nevét
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($_FILES['file']['tmp_name']);
$allowed = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($mimeType, $allowed)) {
die('Nem engedélyezett fájltípus');
}
Ez már a tényleges fájl tartalmat elemzi, nem a nevet vagy a böngésző által küldött MIME-típust.
3. probléma: a feltöltött fájlt a webszerver futtathatja
Ha a feltöltött fájlok egy olyan mappában landolnak, ahol a webszerver .php (vagy .js, .py) fájlokat futtat, akkor egy támadó PHP kódot tölt fel és közvetlenül futtatja azt.
# Veszélyes konfiguráció – a /uploads mappa PHP-t futtat
yourapp.com/uploads/malware.php → a szerver lefuttatja!
Megoldás:
- A feltöltési mappa ne legyen közvetlenül web-elérhető, vagy
- Webszerver konfiguráción tiltsd le a szkriptek futtatását az adott mappában, és
- A fájlt át kell nevezni (ne az eredeti nevet használd!), random generált névvel mentsd el.
import uuid
import os
def save_upload(file, upload_dir):
# Ne az eredeti nevet használd!
safe_filename = str(uuid.uuid4()) + '.jpg'
save_path = os.path.join(upload_dir, safe_filename)
file.save(save_path)
return safe_filename
4. probléma: fájlméret korlát hiánya
Ha nincs méretkorlát, egy támadó feltölt egy 10 GB-os fájlt és feltölti a szerver tárhelyét vagy memóriáját.
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB
if request.content_length > MAX_FILE_SIZE:
return "Fájl túl nagy", 413
„Never trust user input" a gyakorlatban
Ez nem csak szlogen. Nézzük meg konkrétan, honnan érkezhet „felhasználói input":
| Forrás | Miért veszélyes |
|---|---|
| HTML form mezők | Módosíthatók DevToolssal, curl-lel |
| URL paraméterek | ?id=1 OR 1=1 – SQL injection próbálkozás |
| HTTP headerek | X-Forwarded-For, User-Agent – hamisíthatók |
| Cookie-k | Módosíthatók, nem megbízhatók |
| Feltöltött fájlok | Tartalom és típus is hamisítható |
| Webhookok | Külső rendszer által küldött adat |
| API válaszok | Harmadik fél adatai is validálást igényelnek |
Az utolsó sort sokan elfelejtik: ha egy külső API-tól kapsz adatot, azt is validálni kell, mert a külső rendszer kompromittálódhat, vagy csak hibás adatot küldhet.
Praktikus ellenőrzőlista minden inputhoz:
- Típus: Szám-e tényleg, ha számot várok? String-e, ha stringet?
- Hossz: Van-e minimum és maximum korlát?
- Formátum: Megfelel-e a várt mintának (regex, email formátum, stb.)?
- Értékkészlet: Felsorolható értékek esetén az engedélyezett listán van?
- Szerver oldal: Ez a validáció a szerveren fut, nem csak a kliensen?
Path traversal – egy kevéssé ismert veszély
Ha fájlneveket kezelnek felhasználói inputból, érdemes ismerni ezt a támadást:
# VESZÉLYES
filename = request.args.get('file')
with open(f'/var/app/uploads/{filename}', 'r') as f:
return f.read()
Egy támadó ezt küldi: file=../../etc/passwd
A szerver így nyitja meg: /var/app/uploads/../../etc/passwd → ami valójában /etc/passwd – a Linux jelszófájl.
Megoldás:
import os
BASE_DIR = '/var/app/uploads'
def safe_open(filename):
# A valódi teljes útvonalat számítod ki
requested_path = os.path.realpath(os.path.join(BASE_DIR, filename))
# Ellenőrzöd, hogy a kért fájl tényleg a BASE_DIR-en belül van
if not requested_path.startswith(BASE_DIR):
raise ValueError("Hozzáférés megtagadva")
return open(requested_path, 'r')
Összefoglalás – a security validáció lényege
| Elv | Mit jelent a gyakorlatban |
|---|---|
| Never trust user input | Mindent validálj szerveren, függetlenül a klienstől |
| Whitelist > blacklist | Definiáld, mi érvényes – ne a tiltottat sorold fel |
| File upload = külön figyelmet igényel | MIME, méret, név, futtathatóság |
| Kliensoldali validáció = UX | Soha ne legyen az egyetlen védelmi vonal |
| Minden forrás gyanús | Form, URL, cookie, header, webhook – mind validálandó |
Életszerű példák
1. példa: A „biztonságos" regisztrációs form
Képzeld el ezt a szituációt: kész a regisztrációs form. Frontenden minden validálva van – email formátum, jelszó hossz, felhasználónév karakterkészlet. Teszteled böngészőből, minden működik.
Egy héttel later a support jelenti: valaki a felhasználónévbe <script>alert('XSS')</script>-et írt be, és most minden más felhasználónak megjelenik egy alert, amikor a nevét valahol megjelenítik.
Hogy történhetett? A támadó egyszerűen a DevToolsban letiltotta a JavaScript validációt, és így elküldte a kérést. A backenden nem volt validáció.
Tanulság: A kliensoldali validáció nulledik lépés. A backend validáció az igazi kapu.
2. példa: A profilkép feltöltés
Egy junior fejlesztő implementálja a profilkép feltöltést:
// Amit írt
if ($_FILES['avatar']['type'] == 'image/jpeg') {
move_uploaded_file($_FILES['avatar']['tmp_name'],
'uploads/' . $_FILES['avatar']['name']);
}
Két hiba is van:
- A
$_FILES['avatar']['type']a böngésző által küldött érték – hamisítható. - Az eredeti fájlnevet használja – ha valaki
shell.php-t tölt fel ésimage/jpegMIME-et küld, a szerver elmenti ashell.php-t azuploads/mappába.
Ha az uploads/ mappa web-elérhető, a támadó megnyitja a böngészőben: yoursite.com/uploads/shell.php – és már tetszőleges PHP kódot futtathat a szerveren.
Megoldás: Valódi MIME-ellenőrzés a fájl tartalma alapján, random fájlnév generálás, az uploads mappa web-elérhetőségének letiltása.
3. példa: Az életkor mező
Egy egészségügyi alkalmazásban van egy életkor mező. Kliensoldali validáció: 0 és 150 között. Logikusnak tűnik.
Egy tesztelő (vagy tesztelés nélkül egy valódi felhasználó) curl-lel beküld -1-et. Az alkalmazás ezt tárolja az adatbázisban. Egy héttel later egy riport kigenerálja az átlagos életkort – és az eredmény értelmetlen, mert (-1 + 35 + 42 + ...) / n nem ugyanolyan, mint kellene.
Nem minden támadás rosszindulatú. Néha csak bugtesztelés, néha véletlenszerű rossz adat. A szerver oldali validáció adatintegritást is véd, nem csak biztonságot.
Tesztfeladatok
1. feladat – Fogalmi megértés
Miért nem elegendő kizárólag kliensoldali (JavaScript) validáció a biztonsághoz? Válassz minden helyes választ!
- [ ] A. Mert a JavaScript lassabb, mint a szerver oldali kód
- [x] B. Mert a támadó megkerülheti a böngészőt és közvetlenül küldhet HTTP kérést
- [x] C. Mert a kliensoldali kód DevToolsban letiltható
- [ ] D. Mert a JavaScript nem tud regex-et kezelni
2. feladat – Whitelist vs. blacklist
Melyik megközelítés a biztonságosabb egy felhasználónév validálásánál?
A megközelítés:
def validate(name):
blocked = ["<", ">", "'", "\""]
return not any(c in name for c in blocked)
B megközelítés:
import re
def validate(name):
return bool(re.match(r'^[a-zA-Z0-9_]{3,20}$', name))
- [ ] A. Az A, mert explicit tiltja a veszélyes karaktereket
- [x] B. A B, mert whitelist alapon csak az engedélyezett karaktereket fogadja el
- [ ] C. Mindkettő egyformán biztonságos
- [ ] D. Az A, mert hosszabb és részletesebb
Magyarázat: A blacklist soha nem teljes. A B megközelítés pontosan definiálja, mi érvényes – minden más le van tiltva.
3. feladat – File upload
Egy fejlesztő így ellenőrzi a feltöltött fájl típusát:
if ($_FILES['file']['type'] === 'image/png') {
// elfogadja
}
Mi a probléma ezzel?
- [ ] A. A PHP nem tudja kezelni a PNG fájlokat
- [x] B. A
$_FILES['file']['type']a böngésző által küldött érték, hamisítható - [ ] C. Az
===operátor nem megfelelő típusellenőrzésre - [ ] D. Nincs probléma, ez helyes megközelítés
4. feladat – Szerver oldali validáció
Egy webshopban a termék ára kliensoldali JavaScriptből kerül be a POST kérésbe. A szerver ezt közvetlenül feldolgozza:
price = float(request.form['price'])
total = price * quantity
process_payment(total)
Mi a biztonsági probléma?
- [ ] A. A Python
float()függvény nem elég pontos - [ ] B. A szorzás nem helyes
- [x] C. A felhasználó a POST kérésben tetszőleges árat küldhet (pl. 0.01 Ft-ot), amit a szerver elfogad
- [ ] D. Nincs probléma, a kliensoldali validáció elegendő
5. feladat – Path traversal
Egy fájl letöltő endpoint így működik:
filename = request.args.get('file')
with open(f'/var/uploads/{filename}') as f:
return f.read()
Mi a veszély, ha egy támadó file=../../etc/passwd-t küld?
- [ ] A. A Python hibát dob, mert a
..nem érvényes fájlnév - [x] B. A szerver megnyitja a
/etc/passwdfájlt és visszaküldi a tartalmát - [ ] C. A
request.args.getautomatikusan sanitizálja az inputot - [ ] D. Nincs veszély, a fájlrendszer megvédi magát
6. feladat – File upload mentési stratégia
Melyik a helyes megközelítés feltöltött fájlok mentésénél?
- [ ] A. Az eredeti fájlnevet menteni, mert a felhasználónak szüksége van rá
- [ ] B. A MIME típust a
$_FILES['type']alapján ellenőrizni - [x] C. Random generált fájlnevet használni, és a fájl tartalmából meghatározni a MIME típust
- [ ] D. Csak a fájlkiterjesztést ellenőrizni
7. feladat – Forrás azonosítás
Melyik adatforrások igényelnek szerver oldali validációt? (Több jó válasz is lehet!)
- [x] A. HTML form adatok
- [x] B. URL query paraméterek
- [x] C. Cookie értékek
- [x] D. Harmadik fél API-tól érkező webhook adat
- [ ] E. Az adatbázisban tárolt adatok, amelyeket már korábban validáltunk
8. feladat – Életszerű szituáció
Egy junior fejlesztő implementál egy komment funkciót. A felhasználó szabad szöveget írhat (whitelist nem alkalmazható). Mi a helyes megközelítés?
- [ ] A. Blacklist: tiltani a
<script>tageket - [x] B. HTML encoding / escape a megjelenítésnél, hogy a szöveg ne kerüljön értelmezésre
- [ ] C. Nem kell semmit csinálni, a böngésző kezeli
- [ ] D. A kommentek teljes tiltása, ha nem lehet whitelistelni
9. feladat – Méretkorlát
Miért fontos a feltöltött fájlok méretének szerver oldali korlátozása?
- [ ] A. Mert a nagy fájlok lassítják a böngészőt
- [x] B. Mert korlátlan méret esetén egy támadó feltölti a szerver tárhelyét vagy memóriáját (DoS)
- [ ] C. Mert a fájlrendszer nem tud 1 GB-nál nagyobb fájlt kezelni
- [ ] D. Nincs különösebb biztonsági jelentősége
10. feladat – Összefoglalás
Egy kezdő fejlesztő azt mondja: „Nálunk frontend validáció van, és a felhasználóink megbízhatók, szóval jó vagyunk." Mi a helyes válasz?
- [ ] A. Ha a felhasználók megbízhatók, valóban elegendő a kliensoldali validáció
- [ ] B. Csak érzékeny funkcióknál kell szerveroldali validáció
- [x] C. A szerveroldali validáció nem a felhasználók megbízhatóságáról szól – egy támadó nem használja a frontendet
- [ ] D. A kliensoldali és a szerveroldali validáció ugyanazt a védelmet nyújtja