
dotCMS SQL Injection pre-autenticazione
CVE-2026-8054 è una vulnerabilità di SQL Injection pre-autenticazione ad alto rischio (Pre-auth SQL Injection) presente nell'API di Audit delle Pubblicazioni (Publish Audit API) di dotCMS Core. Il bug è stato tracciato ufficialmente come incidente di sicurezza SI-75 ed è stato divulgato ufficialmente alla fine di maggio 2026. Poiché un attaccante può attivarlo da remoto senza alcuna credenziale, il suo danno potenziale è molto grave.
| Attributo | Valore |
|---|---|
| ID CVE | CVE-2026-8054 |
| Tracciamento ufficiale | SI-75 |
| Tipo di vulnerabilità | SQL Injection (CWE-89) |
| Componente interessato | dotCMS Core - Publish Audit API |
| Punteggio CVSS | 10.0 (Critical) |
| Versioni affette | 25.11.04-1 a 26.04.28-02 |
| Versione corretta | 26.04.28-03 |
| Vettore d'attacco | SQL injection remota non autenticata (Pre-auth) |
| Privilegi richiesti | Nessuna autenticazione |
| Interazione utente | Nessuna |
| Impatto versioni LTS | Non interessate (il ramo del codice di audit non è stato backportato all'albero LTS) |
La vulnerabilità risiede nei due endpoint REST /api/auditPublishing/get e /api/auditPublishing/getAll. Questi endpoint, quando ricevono i parametri di richiesta dal client, non eseguono alcun filtro o binding parametrizzato, ma costruiscono dinamicamente query SQL tramite concatenazione diretta di stringhe.
Ancora più grave, dotCMS ha completamente tralasciato il controllo di autenticazione e autorizzazione su questi endpoint backend sensibili relativi agli audit. Ciò significa che qualsiasi attaccante remoto senza credenziali, purché possa raggiungere il sistema attraverso la rete, può inviare direttamente richieste HTTP con payload malevoli agli endpoint.
Percorso file: dotCMS/src/main/java/com/dotcms/rest/AuditPublishingResource.java
La vulnerabilità si trova in due endpoint API REST:
GET /api/auditPublishing/get/{bundleId} - Ottiene lo stato di audit di una singola pubblicazionePOST /api/auditPublishing/getAll - Ottiene in blocco lo stato di audit delle pubblicazioniProblema chiave: Prima della correzione, questi endpoint non richiedevano alcuna autenticazione; qualsiasi utente anonimo poteva accedervi direttamente.
@Path("/auditPublishing")
@Tag(name = "Publishing")
public class AuditPublishingResource {
@POST
@Path("/getAll")
@Produces(MediaType.APPLICATION_JSON)
public Response getAll(List<String> bundleIds) {
// 【Punto vulnerabile】 Nessun controllo di autenticazione! Chiamata diretta all'API backend
try {
final List<PublishAuditStatus> statuses = auditAPI.getPublishAuditStatuses(bundleIds);
// ...
}
}
}
Percorso file: dotCMS/src/main/java/com/dotcms/publisher/business/PublishAuditAPIImpl.java
Metodo: getPublishAuditStatuses(List<String> bundleIds) (righe 224-245)
@CloseDBIfOpened
public List<PublishAuditStatus> getPublishAuditStatuses(List<String> bundleIds)
throws DotPublisherException {
try {
final List<PublishAuditStatus> result = new ArrayList<>();
DotConnect dc = new DotConnect();
// 【Punto vulnerabile 1】 Input dell'utente concatenato direttamente nell'istruzione SQL
// Vengono solo aggiunte virgolette singole, nessuna parametrizzazione o escaping
final List<String> parameter = bundleIds.stream()
.map(id -> "'" + id + "'") // Pericoloso: concatenazione di stringhe
.collect(Collectors.toList());
// 【Punto vulnerabile 2】 Uso di String.format per costruire SQL, input utente incorporato direttamente
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("Impossibile ottenere l'elenco degli elementi con errore:" + e.getMessage(), e);
}
}
Costante SQL (SELECT_ALL_BY_BUNDLES_IDS):
SELECT * FROM publishing_queue_audit WHERE bundle_id IN (%s)
graph LR
subgraph Attaccante esterno
A[Attaccante remoto] -->|Invia payload malevolo| B[API REST HTTP]
end
subgraph Livello applicazione
B -->|POST /api/auditPublishing/getAll| C[AuditPublishingResource<br/>GET/POST]
C -->|Chiama| D[PublishAuditAPI]
D -->|Chiama| E[PublishAuditAPIImpl]
E -->|Passa bundleIds| F[Elaborazione taint<br/>bundleIds.stream<br/>.map id -> id]
F -->|Concatena parametri| G[Costruzione SQL<br/>String.format]
end
subgraph Livello tecnologico
G -->|Costruisce SQL| H[Query SQL dinamica<br/>SELECT * FROM publishing_queue_audit<br/>WHERE bundle_id IN %s]
H -->|Esegue| I[Esecuzione SQL]
I -->|Esegue SQL iniettato| J[PostgreSQL/MySQL]
end
subgraph Punti vulnerabili
K[Punto vulnerabile 1<br/>Nessun controllo autenticazione] -.->|Salta autenticazione| C
L[Punto vulnerabile 2<br/>Nessun binding parametrizzato] -.->|Aggiunge solo virgolette| 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
Propagazione del taint: Input utente → API REST → Elaborazione backend → Costruzione SQL → Esecuzione database Difetti chiave: Nessuna autenticazione + Nessuna parametrizzazione = SQL injection completamente controllabile
Supponiamo che l'utente inserisca bundleIds = ["x' OR '1'='1"]
SQL normale:
SELECT * FROM publishing_queue_audit WHERE bundle_id IN ('normal-id')
SQL dopo iniezione:
SELECT * FROM publishing_queue_audit WHERE bundle_id IN ('x' OR '1'='1')
Poiché '1'='1' è sempre vero, questa query restituisce tutti i record nella tabella.
graph TD
subgraph Confronto input
A[Input normale<br/>bundle-123] -->|Costruisce| B[SQL normale<br/>WHERE bundle_id IN<br/>'bundle-123']
C[Input malevolo<br/>x OR 1=1] -->|Inietta| D[SQL iniettato<br/>WHERE bundle_id IN<br/>x OR 1=1]
end
subgraph Esecuzione database
B -->|Esegue| E[Database]
D -->|Esegue| E
end
subgraph Confronto risultati
E -->|Restituisce| F[Risultato normale<br/>1 record]
E -->|Restituisce - Fuga dati| G[Risultato esposto<br/>Tutti i record]
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[Punto di iniezione: chiusura stringa con virgoletta singola<br/>OR 1=1 rende la condizione sempre vera<br/>Risultato: restituisce tutti i record]
Un attaccante che sfrutta con successo questa vulnerabilità può eseguire comandi SQL arbitrari nel contesto dell'utente del sistema database, causando le seguenti gravi conseguenze:
graph TD
subgraph Analisi impatto attacco
subgraph Riservatezza dati
A[Hash password amministratore]
B[Credenziali utente]
C[Token di reset]
D[Configurazioni di sistema]
end
subgraph Integrità dati
E[Contenuti del sito]
F[Ruoli e permessi utente]
G[Log di audit]
end