
Injection SQL pré-authentification dotCMS
CVE-2026-8054 est une vulnérabilité d'injection SQL (SQL Injection) à haut risque, sans authentification, accessible publiquement (Pre-auth SQL Injection) présente dans l'API Publish Audit de dotCMS Core. Elle est officiellement suivie comme incident de sécurité SI-75 et a été officiellement collectée et divulguée à la fin du mois de mai 2026. Étant donné qu'un attaquant peut la déclencher à distance sans aucun privilège de compte, son danger potentiel est très grave.
| Propriété | Valeur |
|---|---|
| Identifiant CVE | CVE-2026-8054 |
| Suivi officiel | SI-75 |
| Type de vulnérabilité | Injection SQL (CWE-89) |
| Composant affecté | dotCMS Core - API Publish Audit |
| Score CVSS | 10.0 (Critique) |
| Versions affectées | 25.11.04-1 à 26.04.28-02 |
| Version corrigée | 26.04.28-03 |
| Vecteur d'attaque | Injection SQL à distance non authentifiée (Pre-auth) |
| Privilèges requis | Aucune authentification |
| Interaction utilisateur | Aucune |
| Impact sur les versions LTS | Non affecté (la branche du code d'audit n'a pas été rétroportée dans l'arborescence LTS) |
La vulnérabilité existe dans les deux interfaces REST /api/auditPublishing/get et /api/auditPublishing/getAll. Lorsque ces interfaces reçoivent les paramètres de requête saisis par le client, aucun filtrage ni liaison paramétrée n'est effectué ; les requêtes SQL sont directement construites dynamiquement par concaténation de chaînes de caractères.
Plus critique encore, dotCMS a complètement omis les contrôles d'authentification et d'autorisation sur ces interfaces backend sensibles liées à l'audit. Cela signifie que tout attaquant distant sans identifiants, pourvu qu'il puisse accéder au système via le réseau, peut directement envoyer des requêtes HTTP contenant des payloads malveillants à ces interfaces.
Chemin du fichier : dotCMS/src/main/java/com/dotcms/rest/AuditPublishingResource.java
La vulnérabilité existe dans deux points de terminaison de l'API REST :
GET /api/auditPublishing/get/{bundleId} - Récupère le statut d'audit d'une publication individuellePOST /api/auditPublishing/getAll - Récupère en masse les statuts d'audit de publicationProblème clé : avant le correctif, ces deux points de terminaison ne nécessitaient aucune authentification ; tout utilisateur anonyme pouvait y accéder directement.
@Path("/auditPublishing")
@Tag(name = "Publishing")
public class AuditPublishingResource {
@POST
@Path("/getAll")
@Produces(MediaType.APPLICATION_JSON)
public Response getAll(List<String> bundleIds) {
// 【漏洞点】没有认证检查!直接调用后端API
try {
final List<PublishAuditStatus> statuses = auditAPI.getPublishAuditStatuses(bundleIds);
// ...
}
}
}
Chemin du fichier : dotCMS/src/main/java/com/dotcms/publisher/business/PublishAuditAPIImpl.java
Méthode : getPublishAuditStatuses(List<String> bundleIds) (lignes 224-245)
@CloseDBIfOpened
public List<PublishAuditStatus> getPublishAuditStatuses(List<String> bundleIds)
throws DotPublisherException {
try {
final List<PublishAuditStatus> result = new ArrayList<>();
DotConnect dc = new DotConnect();
// 【漏洞点1】直接将用户输入拼接到SQL语句中
// 仅添加单引号包裹,没有任何参数化或转义处理
final List<String> parameter = bundleIds.stream()
.map(id -> "'" + id + "'") // 危险: 字符串拼接
.collect(Collectors.toList());
// 【漏洞点2】使用String.format构造SQL,用户输入被直接嵌入
dc.setSQL(String.format(SELECT_ALL_BY_BUNDLES_IDS,
String.join(",", parameter)));
List<Map<String, Object>> items = dc.loadObjectResults();
for(Map<String, Object> item: items) {
result.add(turnIntoPublishAuditStatus(NO_LIMIT_ASSETS, item));
}
return result;
} catch(Exception e) {
Logger.debug(PublisherUtil.class, e.getMessage(), e);
throw new DotPublisherException("Unable to get list of elements with error:" + e.getMessage(), e);
}
}
Constante SQL (SELECT_ALL_BY_BUNDLES_IDS):
SELECT * FROM publishing_queue_audit WHERE bundle_id IN (%s)
graph LR
subgraph 外部攻击者
A[远程攻击者] -->|发送恶意payload| B[HTTP REST API]
end
subgraph Application Layer
B -->|POST /api/auditPublishing/getAll| C[AuditPublishingResource<br/>GET/POST]
C -->|调用| D[PublishAuditAPI]
D -->|调用| E[PublishAuditAPIImpl]
E -->|传递bundleIds| F[污点处理<br/>bundleIds.stream<br/>.map id -> id]
F -->|拼接参数| G[SQL构造<br/>String.format]
end
subgraph Technology Layer
G -->|构造SQL| H[动态SQL查询<br/>SELECT * FROM publishing_queue_audit<br/>WHERE bundle_id IN %s]
H -->|执行| I[SQL执行]
I -->|执行注入SQL| J[PostgreSQL/MySQL]
end
subgraph 漏洞点
K[漏洞点1<br/>无认证检查] -.->|跳过认证| C
L[漏洞点2<br/>无参数化绑定] -.->|仅添加引号| F
end
style A fill:#ff6b6b,stroke:#333,color:#fff
style K fill:#ff6b6b,stroke:#333,color:#fff
style L fill:#ff6b6b,stroke:#333,color:#fff
style J fill:#ffa94d,stroke:#333
Propagation des données contaminées : entrée utilisateur → API REST → traitement backend → construction SQL → exécution en base de données Défauts critiques : absence d'authentification + absence de paramétrage = injection SQL entièrement contrôlable
Supposons une entrée utilisateur bundleIds = ["x' OR '1'='1"]
SQL normal :
SELECT * FROM publishing_queue_audit WHERE bundle_id IN ('normal-id')
SQL après injection :
SELECT * FROM publishing_queue_audit WHERE bundle_id IN ('x' OR '1'='1')
Comme '1'='1' est toujours vrai, cette requête renvoie tous les enregistrements de la table.
graph TD
subgraph 输入对比
A[正常输入<br/>bundle-123] -->|构造| B[正常SQL<br/>WHERE bundle_id IN<br/>'bundle-123']
C[恶意输入<br/>x OR 1=1] -->|注入| D[注入SQL<br/>WHERE bundle_id IN<br/>x OR 1=1]
end
subgraph 数据库执行
B -->|执行| E[数据库]
D -->|执行| E
end
subgraph 结果对比
E -->|返回| F[正常结果<br/>1条记录]
E -->|返回 数据泄露| G[泄露结果<br/>所有记录]
end
style C fill:#ff6b6b,stroke:#333,color:#fff
style G fill:#ff6b6b,stroke:#333,color:#fff
style D fill:#ff6b6b,stroke:#333,color:#fff
note1[注入点: 单引号闭合原有字符串<br/>OR 1=1 使条件永远为真<br/>结果: 返回所有记录]
Un attaquant qui exploite avec succès cette vulnérabilité peut exécuter des commandes SQL arbitraires dans le contexte de l'utilisateur système de la base de données, entraînant les conséquences graves suivantes :
graph TD
subgraph 攻击影响分析
subgraph 数据机密性
A[管理员密码哈希]
B[用户凭证]
C[重置Token]
D[系统配置]
end
subgraph 数据完整性
E[网站内容]
F[用户角色权限]
G[审计日志]
end
subgraph 系统可用性
H[DROP TABLE]
I[DELETE数据]
J[UPDATE数据]
end
subgraph 权限提升
K[管理员接管]
L[文件系统读写]
M[远程代码执行]
end
end
N[SQL注入漏洞] -->|泄露| A
N -->|泄露| B
N -->|泄露| C
N -->|篡改| E
N -->|篡改| F
N -->|执行| H
N -->|实现| K
N -->|实现| L
O[CVSS 10.0 Critical] -.->|评估| N
style N fill:#ff6b6b,stroke:#333,color:#fff
style O fill:#ff6b6b,stroke:#333,color:#fff
Grâce à l'injection SQL, un attaquant peut extraire les tables critiques de la base de données et obtenir :