// 1 ZERO-DAY · 1 CVE · 1 EXPLOIT NELLE ULTIME 24H

Riferimento: Confronto tra Funzionalità Specifiche del Database e Comportamenti di Injection

Quando sei nel mezzo di una valutazione e il motore di database del target si rivela diverso da quello che ti aspettavi, hai bisogno di risposte rapidamente. Mi è capitato su varianti ShopBox in cui il backend MySQL su 192.0.2.20 era documentato male nell'inventario degli asset, mentre l'istanza PostgreSQL su 192.0.2.30 era quella che effettivamente gestiva le query di login. Questa pagina è il prontuario che avrei voluto avere plastificato nei miei primi incarichi. Incrociare con i pattern di sfruttamento manuale da "First Blood" (pagina 3) e i comportamenti di sqlmap dalla pagina 4.


Tabella 1: Costruzione di Stringhe e Sintassi dei Commenti

Funzionalità MySQL 8.x PostgreSQL 16 SQL Server 2022
Concatenazione di stringhe CONCAT('a','b') oppure 'a' 'b' (letterali separati da spazio) 'a' \|\| 'b' oppure CONCAT('a','b') 'a' + 'b' oppure CONCAT('a','b')
Commento su riga singola -- (richiede spazio finale) oppure # -- (standard) -- (standard)
Commento multi-riga /* ... */ /* ... */ /* ... */
Virgolette per letterali stringa Solo ' singola; " è identificatore ' singola; E'\n' per sequenze di escape ' singola; gli identificatori tra virgolette usano [ ] oppure "
Gestione del byte nullo Interrompe il parsing a %00 in alcuni contesti Trattato come dato, non come terminatore Trattato come dato, non come terminatore
Sensibilità alle maiuscole Nomi di database/tabelle: dipendente dal filesystem su Linux Gli identificatori diventano minuscoli a meno che non siano tra virgolette Insensibile alle maiuscole di default per gli identificatori

Nota di Sam: Il doppio trattino di MySQL -- con spazio finale è un trabocchetto che mi ha bruciato all'inizio. Senza quello spazio, MySQL tratta -- come operatore di sottrazione e continua il parsing. L'ho riscontrato su un form di login MySQL ShopBox dove admin'-- (senza spazio) falliva silenziosamente mentre admin'-- funzionava. PostgreSQL non si cura di quel spazio finale. Il -- di SQL Server si comporta come quello di PostgreSQL.

In termini semplici: lo stesso scheletro di payload non si trasferisce pulitamente. Verifica sempre prima la sintassi dei commenti con un test benigno vero/falso prima di costruire injection complesse.


Tabella 2: Enumerazione dello Schema — Equivalenti del Catalogo di Sistema

Obiettivo Informativo MySQL 8.x PostgreSQL 16 SQL Server 2022
Elencare i database information_schema.schemata oppure SHOW DATABASES information_schema.schemata sys.databases oppure information_schema.schemata
Elencare le tabelle nel DB corrente information_schema.tables information_schema.tables sys.tables oppure information_schema.tables
Elencare le colonne in una tabella information_schema.columns information_schema.columns sys.columns con join a sys.tables, oppure information_schema.columns
Nome del database corrente DATABASE() current_database() DB_NAME()
Utente corrente CURRENT_USER() oppure USER() current_user SUSER_SNAME() oppure CURRENT_USER
Versione del database VERSION() version() @@VERSION
Nome host @@hostname inet_server_addr() (IP) oppure inet_server_port() @@SERVERNAME

Nota di Sam: Il sistema di catalogo doppio di SQL Server (INFORMATION_SCHEMA per compatibilità ANSI, sys.* per nativo Microsoft) è genuinamente utile. sys.tables include is_ms_shipped per filtrare le tabelle di sistema, che INFORMATION_SCHEMA.tables non ti dà direttamente. Ho visto analisti junior sprecare tempo enumerando INFORMATION_SCHEMA su SQL Server quando sys.tables sarebbe stato più pulito. Sullo ShopBox PostgreSQL su 192.0.2.30, information_schema funziona bene ma pg_catalog.pg_tables è più veloce per schemi grandi perché è la tabella di implementazione sottostante.


Tabella 3: Funzioni di Ritardo Temporale per Rilevamento Blind

Motore Funzione Precisione Tipica Note dal Campo
MySQL 8.x SLEEP(n) Granularità al secondo Più affidabile; ritorna 0 dopo il ritardo. Attenzione: SLEEP() in SELECT può essere ottimizzato via nelle clausole WHERE se l'ottimizzatore decide che la riga non corrisponde.
MySQL 8.x BENCHMARK(count, expr) Sub-secondo possibile Vincolato alla CPU, non all'orologio; rumoroso su hosting condiviso. Lo evito ora.
PostgreSQL 16 pg_sleep(n) Millisecondo con pg_sleep(interval) pg_sleep('0.5') funziona per mezzo secondo. Richiede il privilegio EXECUTE sulla funzione, che è pubblico di default ma può essere revocato.
PostgreSQL 16 pg_sleep_for(interval) Stesso Variante sintattica; stesso meccanismo sottostante.
SQL Server 2022 WAITFOR DELAY '00:00:05' ~10-15ms di floor pratico Non richiede privilegi speciali. Il formato della stringa è rigido: HH:MM:SS oppure HH:MM:SS.mmm.
SQL Server 2022 WAITFOR TIME '12:00:00' Allineato all'orologio Raramente utile per injection; incluso per completezza.

Nota di Sam: Il rilevamento blind tramite timing è dove guadagni la tua pazienza. Su un'istanza MySQL ShopBox caricata, SLEEP(5) potrebbe impiegare 8 secondi di wall-clock a causa della coda delle query, quindi stabilisco sempre una baseline con SLEEP(0) e SLEEP(5) su una condizione nota-come-vera prima di interpretare i risultati. pg_sleep di PostgreSQL accetta secondi frazionari più naturalmente del SLEEP() intero-only di MySQL. WAITFOR di SQL Server è la sintassi più verbosa ma anche la più prevedibile nella mia esperienza — non l'ho mai visto ottimizzato via.

⚠️ Solo uso autorizzato e difensivo.


Tabella 4: Primitive per File System ed Esecuzione di Comandi

Capacità MySQL 8.x PostgreSQL 16 SQL Server 2022
Leggere file dal server LOAD_FILE('/path') — richiede privilegio FILE e impostazione secure_file_priv pg_read_file('path', offset, length) in pg_catalog; superuser o grant esplicito OPENROWSET(BULK...) oppure BULK INSERT; ACL complessi
Scrivere file sul server INTO OUTFILE '/path' oppure INTO DUMPFILE — stessi vincoli di privilegio COPY TO con PROGRAM o percorso file; superuser tipicamente richiesto xp_cmdshell con redirezione, oppure alternative sp_OACreate/sp_OAMethod
Esecuzione diretta di comandi OS Non disponibile nativamente — richiede UDF (User-Defined Function) o plugin come lib_mysqludf_sys COPY ... FROM PROGRAM 'command' (superuser); o estensioni plpythonu/plperlu xp_cmdshell 'command' — stored procedure estesa
Abilitare esecuzione disabilitata N/A (nessun equivalente built-in) Caricamento estensioni via CREATE EXTENSION oppure ALTER SYSTEM sp_configure 'xp_cmdshell', 1; RECONFIGURE;
Requisito di privilegio per esecuzione FILE per lettura/scrittura; UDF richiede INSERT in mysql.func più accesso filesystem Superuser per COPY PROGRAM; altrimenti dipendente dall'estensione Ruolo sysadmin di default; account proxy possibili per [S3]

Nota di Sam: La catena xp_cmdshell di SQL Server è l'unica di cui posso parlare con fiducia grazie a ripetuto lavoro in laboratorio. La sequenza completa di abilitazione, come abbiamo accennato in "The Attacker's Toolkit" (pagina 2):

-- Check current setting
EXEC sp_configure 'xp_cmdshell';
-- If 'show advanced options' is 0, enable it first
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
-- Now enable xp_cmdshell
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;

Perché questo importa: RECONFIGURE non è opzionale — [S3] conferma che la modifica dell'impostazione non ha effetto senza di esso. Ho visto studenti abilitare xp_cmdshell, provare ad eseguirlo, ricevere un errore di permessi, e assumere di non essere sysadmin. No — hanno semplicemente dimenticato RECONFIGURE. L'output di sp_configure che mostra run_valueconfig_value è la tua diagnostica.

Per l'esecuzione di comandi su MySQL e PostgreSQL, devo essere cauto: ho usato UDF MySQL in ambienti CTF ma mai in una valutazione di produzione, e lo stato di manutenzione del progetto lib_mysqludf_sys varia. COPY ... FROM PROGRAM di PostgreSQL è potente ma l'ho trovato più spesso disabilitato o limitato che abilitato. Controlla i file pg_hba.conf e postgresql.conf del tuo target per allow_system_table_mods e impostazioni correlate.


Tabella 5: Sintassi dei Prepared Statement e Binding dei Parametri

Aspetto MySQL 8.x PostgreSQL 16 SQL Server 2022
API prepared statement lato client PREPARE stmt FROM 'SELECT ... WHERE id = ?' PREPARE stmt (INT) AS SELECT ... WHERE id = $1 sp_executesql N'SELECT ... WHERE id = @p1', N'@p1 INT', @p1 = ?
Segnaposto parametro ? (posizionale) $1, $2, ecc. (posizionale) @name (nominato) oppure ? con ADO.NET
Supporto protocollo binario Sì (mysqlnd, C API) Sì (extended query protocol) Sì (Tabular Data Stream, TDS)
Comportamento cursore lato server Opzionale; default è emulare prepared True server-side prepare di default sp_executesql compila e mette in cache il piano
Esempio binding PDO/PHP $stmt->bind_param('i', $id) $stmt->bindParam(1, $id, PDO::PARAM_INT) $cmd->Parameters->AddWithValue('@p1', $id)
Astrazione ORM Doctrine, Eloquent gestiscono internamente Doctrine, Eloquent gestiscono internamente Entity Framework, Dapper

Nota di Sam: Il principio fondamentale da [S1] vale ovunque — il template SQL va al server prima, i parametri secondi. Ma i dettagli implementativi contano per la difesa. Il PREPARE di MySQL da concatenazione di stringhe è ancora vulnerabile se costruisci il template string con input utente: PREPARE stmt FROM CONCAT('SELECT * FROM ', user_input) vanifica lo scopo. Ho visto questo in codice ShopBox legacy che "usava prepared statements" ma concatenava il nome della tabella.

I parametri numerati di PostgreSQL ($1) sono meno leggibili ma più difficili da confondere in query complesse. I parametri nominati di SQL Server (@p1) brillano nelle stored procedure ma richiedono disciplina — SQL dinamico dentro sp_executesql con concatenazione di stringhe è ancora injection city.

In termini semplici: i prepared statement non sono magia. Il template deve essere fisso. Se qualsiasi parte della struttura SQL proviene da input utente, sei tornato al punto di partenza.


Riferimento Rapido: Porting dei Payload ShopBox

Passo d'Attacco (dalla Pagina 3) MySQL 192.0.2.20 PostgreSQL 192.0.2.30
Confermare punto di injection 1' AND SLEEP(5)-- 1'; SELECT pg_sleep(5)--
Enumerare nome database UNION SELECT 1,DATABASE(),3-- UNION SELECT 1,current_database(),3--
Enumerare tabelle FROM information_schema.tables WHERE table_schema=DATABASE() FROM information_schema.tables WHERE table_schema=current_database()
Estrarre valori delle colonne GROUP_CONCAT(username,':',password) STRING_AGG(username || ':' || password, ',')

La differenza tra GROUP_CONCAT e STRING_AGG mi ha morso una volta durante una valutazione a tempo. GROUP_CONCAT di MySQL ha un limite default di 1024 byte (configurabile via group_concat_max_len); STRING_AGG di PostgreSQL non ha tale limite di default. SQL Server userebbe STRING_AGG dalla versione 2017, oppure FOR XML PATH('') su versioni più vecchie — controlla la build del tuo target.


Nota conclusiva di Sam per questa pagina: Tengo una copia stampata delle Tabelle 1 e 2 nascosta dentro il mio taccuino. Il resto posso ricostruirlo, ma la sintassi dei commenti e le tabelle del catalogo sono le prime cose di cui ho bisogno quando il motore mi sorprende. Nella prossima pagina, "When Defenses Fail," guarderemo cosa succede quando le regole del tuo WAF conoscono la sintassi MySQL ma l'attaccante passa a PostgreSQL — il problema del payload poliglotta. Per ora, prova questo in laboratorio: avvia le varianti ShopBox su 192.0.2.20 e 192.0.2.30, esegui lo stesso comando sqlmap dalla pagina 4 con --dbms=mysql versus --dbms=postgresql, e osserva la differenza di payload nei tuoi log applicativi. I pattern di correlazione difensiva dalla pagina 5 funzionano solo se sai cosa stai cercando.

Letture consigliate