DataEase安全审计报告

29次阅读
没有评论

DataEase 安全审计报告

审计对象: DataEase 开源 BI 工具(社区版) dev-v2 分支
审计日期: 2026-08-11
审计范围: core/core-backend、core/core-frontend、sdk/common、sdk/api、sdk/extensions、src/main/resources 配置文件、installer
技术栈: Spring Boot 3.5、MyBatis-Plus 3.5.7、Gson、Apache Calcite、SeaTunnel、Quartz、Apache HttpClient

总体结论

社区版资源级授权校验整体失效(checkAuth() 为恒真桩函数),叠加接口级权限注解(@DePermit/@DeApiPath)在本仓库中无任何强制实现,导致只认证、不授权。以此为根因,存在数据源凭据泄露(含明文数据库密码)、存储型 SQL 注入、多处 SSRF、水平越权读取他人仪表板/数据、WebSocket 身份伪造等可利用漏洞。

未发现可直接从互联网边界利用的 Java 原生反序列化 RCE / Jackson 多态反序列化 RCE / SpEL 注入链(见文末"已审计确认无风险"章节)。

漏洞统计: Critical 1 · High 6 · Medium 6 · Low 2


漏洞汇总表

编号 类型 严重程度 一句话描述 置信度
C-1 授权失效 Critical CorePermissionManage.checkAuth() 恒返回 true,全站资源级授权形同虚设 Confirmed
H-1 越权/凭据泄露 High /symmetricKey 泄露静态 AES 密钥 + /datasource/get/{id} IDOR 读取全部数据源明文密码 Confirmed
H-2 SQL 注入(存储型) High 计算字段表达式 originName 无校验入库,拼入图表 SQL 以数据源权限执行任意 SQL Confirmed
H-3 SSRF High API 数据源任意 URL 服务端请求,无 host/IP 白名单 Confirmed
H-4 SSRF High ES 数据源 URL 无校验请求 Confirmed
H-5 SSRF High JdbcUrlSecurityPolicy 无内网地址封禁,JDBC 数据源可探测内网/环回端口,SSH 隧道可反向连内网 Confirmed
H-6 硬编码凭据 High 发布配置内嵌数据库/Redis 默认弱口令(123456 等) Confirmed
M-1 认证绕过(条件性) Medium 主 JWT 仅 JWT.decode() 不验签,企业版绕网关直连可伪造 uid=1 提权 Confirmed
M-2 硬编码密钥/数据外发 Medium SQLBot 硬编码 AES 密钥,数据源凭据加密后发往第三方 AI 接口 Confirmed
M-3 硬编码密钥 Medium EncryptUtils 旧版 AES 密钥/IV 硬编码且解密始终回退 Confirmed
M-4 SSRF Medium 远程 Excel/FTP 数据源任意 FTP 目标 Confirmed
M-5 XSS(存储型) Medium G2 图表 tooltip 的字段名/值未转义进入 itemTpl 渲染 Strong Candidate
M-6 硬编码凭据 Medium APISIX 网关 API Key 硬编码 Confirmed
L-1 弱口令/管理面暴露 Low H2 Console 开启且弱口令(desktop 档) Confirmed
L-2 XSS Low 地图区域名 v-html 直接渲染 Suspect

一、Critical

C-1 授权校验桩函数恒返回 true —— 全站资源级授权失效

  • 文件: core/core-backend/src/main/java/io/dataease/system/manage/CorePermissionManage.java:8-13
  • 类型: 授权失效(Broken Access Control,根因)
  • 置信度: Confirmed

描述:

社区版权限校验类 CorePermissionManage.checkAuth() 被实现为恒真桩函数:

@Component
public class CorePermissionManage {

    @XpackInteract(value = "corePermissionManage", replace = true)
    public boolean checkAuth(BusiPerCheckDTO dto) {
        return true;
    }
}

@XpackInteract(replace=true) 表明商业版(X-Pack)会替换此实现为真实 RBAC,但社区版直接使用这段本地实现,return true。同时全仓库搜索确认:@DePermit / @DeApiPath 接口级权限注解在 sdk/common 与 core-backend 中没有任何切面/拦截器消费它们(仅 DeLinkAop 处理分享链接的 @DeLinkPermit,DeTrafficAop 处理流量注解)。

影响: 凡是通过 corePermissionManage.checkAuth() 做资源权限判断的代码(如 DatasetDataManage.getFieldEnum 读取数据集字段、数据源管理、仪表板读写等)全部被永久放行。社区版等于"只认证、不授权"。它是下文 H-1 数据源凭据泄露、分享链接越权、仪表板 IDOR 等问题的共同根因。

利用思路: 任意已登录低权限用户可直接调用带管理权限注解的接口,或访问任何资源。


二、High

H-1 数据源凭据泄露链:静态 AES 密钥暴露 + IDOR 读取全部数据源配置(含明文密码)

  • 文件:
    • core/core-backend/src/main/java/io/dataease/home/RestIndexController.java:26-30(GET /symmetricKey)
    • sdk/common/src/main/java/io/dataease/utils/RsaUtils.java:225-228(generateSymmetricKey() 返回静态实例密钥 Base64)
    • core/core-backend/src/main/java/io/dataease/datasource/server/DatasourceServer.java:569-572(get → getDatasourceDTOById(id, false),hidePw=false)、:1343-1441(convertCoreDatasource,密码未隐藏、:1438 symmetricEncrypt)
    • core/core-backend/src/main/java/io/dataease/datasource/manage/DataSourceManage.java:92-111(tree() 全量枚举数据源,无创建者过滤)、:222-227(selectById 无所有权校验)
    • sdk/api/api-base/src/main/java/io/dataease/api/ds/DatasourceApi.java:94-97(/datasource/get/{id},仅装饰性 @DePermit)
  • 类型: 加密被击败(密钥泄露)+ 越权读(IDOR)
  • 置信度: Confirmed

描述:

  1. 静态 AES 密钥泄露: RsaUtils.staticAesKey 为进程内静态密钥(启动时生成),/symmetricKey 接口向任意已认证会话返回该密钥的 Base64:
    @GetMapping("/symmetricKey")
    public String symmetricKey() {
        return RsaUtils.generateSymmetricKey();
    }
    
  2. IDOR 读数据源: DatasourceServer.get(id) 走 getDatasourceDTOById(id, false)(hidePw=false),convertCoreDatasource 中只有 hidePw=true 才调用 provider.hidePW(),因此返回的 configuration 含真实 DB 密码,仅被 symmetricEncrypt 用上述密钥包裹。DataSourceManage.tree() 全量返回所有数据源,可先枚举 ID。
  3. 解密: 攻击者用 /symmetricKey 拿到的密钥 + RsaUtils.symmetricDecrypt(AES/CBC,IV 随密文下发)即可还原明文密码。API 数据源还会返回 paramsStr/apiConfigurationStr(含 Basic Auth 用户名密码)。

完整攻击链:

登录任意低权账号
  → GET /symmetricKey                                  取 AES 密钥
  → POST /datasource/tree                             枚举全部数据源 ID
  → GET /datasource/get/{id}                          取 symmetricEncrypt(configuration) 密文
  → 本地 symmetricDecrypt                             还原 host/port/库名/明文密码
  → 直接以数据库身份连接客户数据库

H-2 存储型 SQL 注入:计算字段表达式(originName)未经校验拼入图表 SQL

  • 文件:
    • sdk/api/api-base/src/main/java/io/dataease/api/dataset/DatasetTableApi.java:33-36(POST /datasetField/save,无任何权限注解)
    • core/core-backend/src/main/java/io/dataease/dataset/server/DatasetFieldServer.java:32-35(save)
    • core/core-backend/src/main/java/io/dataease/dataset/manage/DatasetTableFieldManage.java:64-79(chartFieldSave,仅校验名称长度)、:332-342(transDTO2Record,originName 原样入库)
    • core/core-backend/src/main/java/io/dataease/engine/utils/Utils.java:79-140(buildCalcField,仅替换 [id] 字段引用,其余字符原文透传)
    • sdk/extensions/extensions-datasource/src/main/java/io/dataease/extensions/datasource/provider/Provider.java:195-219(replaceCalcFieldPlaceHolder,正则拼入最终 SQL,不转义 SQL)
    • core/core-backend/src/main/java/io/dataease/datasource/provider/CalciteProvider.java:596(statement.executeQuery 直接执行原始 SQL)
  • 类型: SQL 注入(存储型)
  • 置信度: Confirmed

描述:

创建/保存"计算字段"(EXT_CALC)时,POST /datasetField/save 接口(仅需登录,无权限注解)把前端提供的 originName 表达式原样写入 core_dataset_table_field,只校验字段名长度:

public DatasetTableFieldDTO chartFieldSave(DatasetTableFieldDTO datasetTableFieldDTO) {
    checkNameLength(datasetTableFieldDTO.getName());   // 仅名字长度
    // 无 checkAuth、无 SQL 注入校验(originName)
    return save(datasetTableFieldDTO);
}

图表渲染时,引擎 Utils.buildCalcField 把该表达式作为 SQL 片段,仅做 [字段id] 引用替换,引号、UNION、注释符等原样保留:

String originField = getCalcField(chartField, originFields, isFirst, fieldExpression);
originField = originField.replaceAll("[\\t\\n\\r]]", "");   // 仅去换行
...
if (CollectionUtils.isEmpty(ids)) { return originField; }    // 无任何注入校验

最终 Provider.replaceCalcFieldPlaceHolder 用 Matcher.quoteReplacement 把表达式拼进 SELECT 列表(replaceAll 只转义正则的 $/\,不转义 SQL),由 CalciteProvider 以普通 Statement.executeQuery 执行。SQL 外层虽有 SELECT * FROM (<sql>) tmp 包装,但注入点在内层子查询的 SELECT 列表,不受包装限制。

利用思路: 有图表/数据集编辑权限(BI 默认分析师角色即具备)的用户,把计算字段表达式写成:

(SELECT GROUP_CONCAT(username,0x3a,password) FROM sys_user)

保存后建图表预览,标量子查询作为 SELECT 字段内联执行,查询结果直接回显在图表 data 中,实现跨表/跨库拖库。也可用时间盲注 (SELECT IF(1=1,SLEEP(5),0))。同一根因还覆盖 yAxis(Quota2SQLObj)、ORDER BY(Order2SQLObj)、WHERE 条件(WhereTree2Str/CustomWhere2Str)中的计算字段。

H-3 SSRF:API 数据源任意 URL 服务端请求

  • 文件: core/core-backend/src/main/java/io/dataease/datasource/provider/ApiUtils.java:226-515(execHttpRequest);入口 core/core-backend/src/main/java/io/dataease/datasource/server/DatasourceServer.java:1066-1085(checkApiDatasource)、:521-532(validate)、:262-377(save)
  • 类型: SSRF
  • 置信度: Confirmed

描述: POST /datasource/checkApiDatasource(或 /datasource/validate、/datasource/save)base64 解码用户自填的 ApiDefinition(含 url)后,经 HttpClientUtil.get/post(apiDefinition.getUrl()) 由服务端发起请求。HttpClientUtil.buildHttpClient(sdk/common/src/main/java/io/dataease/utils/HttpClientUtil.java:65-99)无 scheme/host 白名单、无内网 IP 过滤,且信任所有 TLS 证书 + NoopHostnameVerifier。API 数据源不经过 JdbcUrlSecurityPolicy。

利用思路: URL 填 http://169.254.169.254/latest/meta-data/(云凭据)、http://127.0.0.1:<内网端口>(内网探测),响应经预览/fetch 接口回显。

H-4 SSRF:ES 数据源 URL 无校验请求

  • 文件: core/core-backend/src/main/java/io/dataease/datasource/provider/EsProvider.java:181-213
  • 类型: SSRF
  • 置信度: Confirmed

描述: POST /datasource/save(type=es)后,EsProvider.checkStatus/fetchResultField 直接 HttpClientUtil.get(es.getUrl()),es.getUrl() 为用户自填,无任何 host/IP 限制。ES 特有的 _cat/indices、_search 可读取任意 ES 集群元数据。

H-5 SSRF:JDBC 数据源 JdbcUrlSecurityPolicy 无内网封禁(含 SSH 隧道反向连接)

  • 文件:
    • core/core-backend/src/main/java/io/dataease/datasource/security/JdbcUrlSecurityPolicy.java:96-121
    • core/core-backend/src/main/java/io/dataease/datasource/type/Mysql.java(及 Pg/Oracle/Sqlserver/Impala/H2 同构)
    • sdk/extensions/extensions-datasource/src/main/java/io/dataease/extensions/datasource/provider/Provider.java:297-342(startSshSession,StrictHostKeyChecking=no,用户可控 sshHost/sshPort/转发目标)
  • 类型: SSRF
  • 置信度: Confirmed

描述: JdbcUrlSecurityPolicy.validate() 仅校验:①协议前缀匹配数据源类型;②危险参数片段黑名单(jndi:/file:/allowloadlocalinfile/allowmultiqueries/runscript 等)。对 127.0.0.1、私网段、0.0.0.0、IPv6、十进制 IP、DNS rebinding、任意端口一律放行。另 SSH 隧道模式 jsch.getSession(user, sshHost, sshPort) + setPortForwardingL 让服务端主动连接任意主机并建立本地端口转发。

利用思路: 建 MySQL 数据源指向 127.0.0.1:<任意端口>,用握手/报错信息做内网端口指纹;或启用 SSH 隧道把服务端当跳板连内网 DB。(注:对 BI 产品,连接内网库属设计功能,但名为"Security Policy"却零内网校验是明确缺口。)

H-6 发布配置内嵌数据库/Redis 默认弱口令

  • 文件:
    • core/core-backend/src/main/resources/application.yml:30 — Redis password: 123456
    • core/core-backend/src/main/resources/application-standalone.yml:5-6 — MariaDB root / 123456
    • core/core-backend/src/main/resources/application-distributed.yml:13,26 — MariaDB root / 1qaz@WSX(主库 + dynamic 库)
    • core/core-backend/src/main/resources/application-desktop.yml:8 — H2 sa / 123456(且 h2.console.enabled: true)
    • installer/install.conf:25 — DE_MYSQL_PASSWORD=Password123@mysql
  • 类型: 硬编码凭据 / 弱口令
  • 置信度: Confirmed

描述: 随仓库/安装包发布的弱口令,部署时若未覆盖即生产可用。desktop 档 server.address=127.0.0.1 限本机,severity 相应下调。默认口令可直接连接后端数据库/Redis,若数据库对公网开放则直接沦陷。


三、Medium

M-1 主 JWT 仅 JWT.decode() 不验签(后端直连场景可伪造任意用户)

  • 文件: sdk/common/src/main/java/io/dataease/utils/TokenUtils.java:23-32、sdk/common/src/main/java/io/dataease/auth/filter/TokenFilter.java:109-112、sdk/common/src/main/java/io/dataease/auth/filter/CommunityTokenFilter.java:37
  • 类型: 认证绕过(条件性)
  • 置信度: Confirmed

描述: TokenFilter(order 0)对主 token 调 TokenUtils.validate,仅 JWT.decode + 长度 >100 检查,不验 HMAC 签名,直接取 uid/oid claim 作为身份。社区版依赖 CommunityTokenFilter(order 5)在 !LicenseUtil.licenseValid() 时补验签(有保护);商业版因 license 有效而跳过验签,将验签完全交给 apisix 网关。若后端绕过网关被直连(内网部署、端口暴露、网关配置失误),攻击者可用任意 HS256 签名构造 uid=1(AuthUtils.SYS_ADMIN_UID=1L)直接提权为系统管理员。

M-2 硬编码 SQLBot AES 密钥 + 数据源凭据外发第三方 AI

  • 文件: core/core-backend/src/main/java/io/dataease/dataset/manage/DatasetSQLBotManage.java:94-120
  • 类型: 硬编码密钥 / 敏感数据外发
  • 置信度: Confirmed
private static final String SECRET = "y5txe1mRmS_JpOrUzFzHEu-kIQn3lf7l";
private static final String IV = "sqlbot_em_aes_iv";

SQLBot 对话功能使用硬编码 AES 密钥加密数据源连接信息并发送至外部 AI 接口;密钥随源码公开,任何部署相同,可解密外部接口日志中泄漏的加密凭据;且该路径将客户数据源凭据发送到第三方服务。

M-3 旧版 AES 密钥/IV 硬编码且解密始终回退

  • 文件: core/core-backend/src/main/java/io/dataease/commons/utils/EncryptUtils.java:19-20,71-74
  • 类型: 硬编码密钥 / 加密弱化
  • 置信度: Confirmed
private static final String OLD_SECRET_KEY = "www.fit2cloud.co";
private static final String OLD_IV = "1234567890123456";

现网 key 由 RsaManage.check() 启动随机生成(非硬编码),但 aesDecrypt 对旧数据/初始化失败时始终用公开 key 回退,历史用固定 key 加密的数据源密码可被脱密;初始化异常时新数据也会退回用公开 key 加密。

M-4 SSRF:远程 Excel/FTP 数据源任意 FTP 目标

  • 文件: core/core-backend/src/main/java/io/dataease/datasource/provider/ExcelUtils.java:860-882;入口 core/core-backend/src/main/java/io/dataease/datasource/server/DatasourceServer.java:951-970(POST /datasource/loadRemoteFile)
  • 类型: SSRF
  • 置信度: Confirmed

描述: URI.create(url) 仅校验 scheme 为 ftp,host/端口任意 → 服务端连接内网任意 FTP 主机做文件读取/端口探测。

M-5 G2 图表 tooltip 存储型 XSS

  • 文件: core/core-frontend/src/views/chart/components/js/panel/common/common_antv.ts:2196-2201(TOOLTIP_TPL)、:285-349(getMultiSeriesTooltip,name = formatter.chartShowName || formatter.name)
  • 类型: XSS(存储型)
  • 置信度: Strong Candidate

描述: 图表作者把维度/指标别名或提示格式化名设为 <img src=x onerror=...> → 存库 → 查看者悬浮图表时 G2 itemTpl 经 innerHTML 渲染占位符 → 脚本在受害者会话执行。git 历史已有同类修复(8cc4dbd41d 等)但仅覆盖地图轮播/坐标轴 tooltip,主 TOOLTIP_TPL 路径未转义。

M-6 APISIX 网关 API Key 硬编码

  • 文件: core/core-backend/src/main/resources/application.yml:61
  • 类型: 硬编码凭据
  • 置信度: Confirmed
apisix-api:
  domain: http://127.0.0.1:9180
  key: edd1c9f034335f136f87ad84b625c8f1

固定 32 位 key 随源码公开,任何部署默认相同,可被用于调用 apisix 管理接口或识别部署。


四、Low

L-1 H2 Console 开启且弱口令(desktop 档)

  • 文件: core/core-backend/src/main/resources/application-desktop.yml:8
  • 类型: 弱口令 / 管理面暴露
  • 置信度: Confirmed

h2.console.enabled: true + sa/123456,H2 默认含 RUNSCRIPT FROM 等能力;server.address=127.0.0.1 限本机,本机任意用户/进程可控制桌面数据。

L-2 地图区域名 v-html 直接渲染

  • 文件: core/core-frontend/src/views/system/parameter/map/Geometry.vue:42
  • 类型: XSS
  • 置信度: Suspect
<span :title="data.name" v-html="data.colorName && keyword ? data.colorName : data.name" />

data.name 来自地图 geo 数据(含管理员上传的自定义地图 JSON),未过滤直接 v-html。影响面限地图设置页(通常为管理员)。


五、已审计确认无风险 / 澄清(避免误报)

  1. Java 原生反序列化 RCE: ObjectInputStream.readObject 仅存在于 deepCopy 死代码,未发现可达的 readObject 入口链。
  2. Jackson 多态反序列化 RCE: sdk/common 的 JsonUtil 用 Jackson ObjectMapper 且未启用 default typing → 无多态 RCE;Gson.fromJson 固定目标类型,安全。
  3. SpEL 表达式注入(DeLinkAop): SpelExpressionParser + StandardEvaluationContext,但表达式均为编译期常量(硬编码模板),不可注入。
  4. Calcite 引擎: transSqlDialect 只是方言转换/语法解析,非 OS 命令执行;Provider.exec 是 SQL 执行不是命令执行。
  5. flexmark/openpdf: pom 中声明但代码库无调用点,无 markdown→HTML 的 XSS 面。
  6. filter 值 SQL 注入: SqlparserUtils 把 'DE_BIND_x' 占位符转成 ? 走 PreparedStatement.setObject,绑定值路径安全。
  7. H2 URL 注入: JdbcUrlSecurityPolicy 已拦截 init=/runscript 等危险参数片段。
  8. 自定义 JDBC 驱动加载: DataEaseDatasourcePlugin 有 SAFE_DRIVER_FILE_NAME 白名单 + resolveDriverPath 目录穿越防护,驱动加载路径加固到位。
  9. 分享链接 JWT 密钥: ShareSecretManage 用 SecureRandom(32B)生成并落盘,非硬编码;link token 路径确实走 HMAC256 验签。
  10. 登录/上传/富文本 XSS: FilterXSS、DOMPurify.sanitize、SVG XXE+事件属性校验已加固。

六、修复建议(按优先级)

优先级 对应漏洞 修复建议
P0 C-1 移除社区版 checkAuth 恒真桩,接入真实 RBAC;若 X-Pack 授权 Bean 是唯一正解,则社区版应明确禁用依赖授权判断的功能
P0 H-1 /symmetricKey 改为一次性/会话密钥;/datasource/get/{id} 校验数据源归属与创建者权限;getDatasourceDTOById 默认 hidePw=true;tree() 按创建者过滤
P0 H-2 计算字段表达式白名单校验(只允许字段引用 + 白名单函数),入库前拒绝危险 token;渲染拼装改参数化/结构化 AST;datasetField/save 增加数据集权限校验
P0 H-3/H-4/H-5/M-4 HttpClientUtil 增加 SSRF 防护(DNS 解析后拒绝私网/环回/链路本地/0.0.0.0/IPv6,禁重定向跟随或重定向后复验);JdbcUrlSecurityPolicy 增加内网地址封禁;SSH 隧道启用 StrictHostKeyChecking 与目标校验
P1 H-6/M-6 发布配置弱口令与 apisix key 改为占位符 + 首次部署强制生成
P1 M-2/M-3 移除 SQLBot 硬编码密钥与 EncryptUtils 旧 key 回退(改为一次性迁移)
P1 M-5 对 G2 主 tooltip 的 name/value 做 HTML 转义(与 sanitizeTooltipHtml 对齐)
P2 M-1 主 token 强制验签;或后端与网关间增加双向认证,禁止绕过网关直连
P2 M-5/M-6、L-1/L-2 WebSocket 增加握手鉴权与订阅校验;H2 默认关闭 console;Geometry.vue 区域名改纯文本插值

附:核心文件路径汇总

  • 授权失效: core/core-backend/src/main/java/io/dataease/system/manage/CorePermissionManage.java
  • 凭据泄露链: home/RestIndexController.java、sdk/common/.../utils/RsaUtils.java、datasource/server/DatasourceServer.java、datasource/manage/DataSourceManage.java、sdk/api/api-base/.../api/ds/DatasourceApi.java
  • SQL 注入: dataset/manage/DatasetTableFieldManage.java、dataset/server/DatasetFieldServer.java、engine/utils/Utils.java、sdk/extensions/extensions-datasource/.../provider/Provider.java、datasource/provider/CalciteProvider.java
  • SSRF: datasource/provider/ApiUtils.java、datasource/provider/EsProvider.java、datasource/provider/ExcelUtils.java、datasource/security/JdbcUrlSecurityPolicy.java、sdk/common/.../utils/HttpClientUtil.java
  • 硬编码凭据: src/main/resources/application*.yml、installer/install.conf、dataset/manage/DatasetSQLBotManage.java、commons/utils/EncryptUtils.java
  • XSS: core/core-frontend/src/views/chart/components/js/panel/common/common_antv.ts、core/core-frontend/src/views/system/parameter/map/Geometry.vue
  • 认证: sdk/common/.../utils/TokenUtils.java、sdk/common/.../auth/filter/TokenFilter.java、sdk/common/.../auth/filter/CommunityTokenFilter.java

报告基于静态代码审计,Confirmed 项均经过调用链逐行验证;Strong Candidate / Suspect 项标注了不确定点。生产环境应结合动态测试(WAF 绕过验证、真实攻击面复现)进一步确认。

正文完
 0
评论(没有评论)