PHP – Interrogazioni al Database in PHP – SQL Injection
Sommario
Connessione al Database
Per interagire con un database MySQL in PHP, possiamo utilizzare mysqli
o PDO
. Si veda l’articolo PHP – Linguaggio di programmazione e PHP – Inserimento, modifica e cancellazione di record Ecco un esempio di connessione con mysqli
:
<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "test";
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connessione fallita: " . $conn->connect_error);
}
?>
Ipotizziamo ora che il nostro database “test” contenga la tabella “utenti” e che quest’ultima sia popolata come segue:

Vogliamo selezionare e mostrare tutti i record della tabella. Occorre una query SQL che permetta la selezione dei dati di tutti i record:
Copied!SELECT * FROM utenti
oppure se vogliamo selezionare solo alcuni dei campi (ad esempio id, nome, cognome, email):
Copied!SELECT id, nome, cognome, email FROM utenti
A questo punto dobbiamo memorizzare la query in una stringa PHP ed eseguirla
Copied!$sql = "SELECT id, nome, email FROM utenti"; $result = $conn->query($sql);
A questo punto dobbiamo iterare i risultati della query SQL eseguita restituendo i dati riga per riga
Copied!if ($result->num_rows > 0) { while($row = $result->fetch_assoc()) { echo "ID: " . $row["id"] . " - Nome: " . $row["nome"] . " - Cognome: " . $row["cognome"] . " - Email: " . $row["email"] . "<br>"; } } else { echo "Nessun risultato"; }
-
$result
è un oggetto che contiene i risultati della query SQL. - La proprietà
$result->num_rows
restituisce il numero di righe ottenute dalla query. - Se il numero di righe è maggiore di 0, significa che ci sono dati nel risultato della query.
- Il ciclo
while
esegue un’iterazione per ogni riga di dati trovata. -
$result->fetch_assoc()
restituisce la riga successiva sotto forma di array associativo (associative array
), dove i nomi delle colonne sono le chiavi dell’array. -
$row["id"]
,$row["nome"]
,$row["cognome"]
, e$row["email"]
estraggono i valori corrispondenti alla riga attuale. - Vengono concatenati i valori della riga e stampati come testo, separati da un trattino
-
. - Il tag
<br>
serve per andare a capo dopo ogni riga. - Se
$result->num_rows
è 0, significa che non sono state trovate righe nella tabella e viene mostrato il messaggio"Nessun risultato"
Qui di seguito il codice completo:
<?php
//se è stato creato uno script conn.php lo includo
require 'conn.php';
$sql = "SELECT id, nome, cognome, email FROM utenti";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo "ID: " . $row["id"] . " - Nome: " . $row["nome"] . " - Cognome: " . $row["cognome"] . " - Email: " . $row["email"] . "<br>";
}
} else {
echo "Nessun risultato";
}
?>
Salviamo il codice come seleziona.php, facciamo partire Apache e MySQL dal pannello di controllo di XAMPP, apriamo il browser e sulla barra degli indirizzi scriviamo:
Copied!localhost/test/seleziona.php
visualizziamo:

SQL Injection: Cos’è e come prevenirla
L’SQL Injection è un attacco informatico che permette a un malintenzionato di manipolare una query SQL attraverso input non validati. Questo può portare all’accesso non autorizzato ai dati o alla modifica dei contenuti del database.
Esempio di SQL Injection
Se un modulo di login contiene il seguente codice vulnerabile:
Copied!$user = $_POST["utente"]; $pwd = $_POST["password"]; $sqlcmd = "SELECT * FROM utenti WHERE username='$user' AND password='$pwd'"; $result = $conn->query($sqlcmd);
Un utente malintenzionato potrebbe inserire come password:
Copied!' OR '1'='1
La query risultante sarebbe:
Copied!SELECT * FROM utenti WHERE username='' OR '1'='1' AND password=''
Poiché la condizione 1=1
è sempre vera, l’utente ottiene accesso senza credenziali valide.
Tecniche di Protezione
1. Prepared Statements
La soluzione più efficace è l’uso di query parametrizzate con mysqli
o PDO
come descritto in PHP – Inserimento, modifica e cancellazione di record:
Copied!$stmt = $conn->prepare("SELECT * FROM utenti WHERE username = ? AND password = ?"); $stmt->bind_param("ss", $user, $pwd); $stmt->execute(); $result = $stmt->get_result();
2. Escape dei caratteri speciali
Per combattere questo tipo di attacco è anche possibile trasformare la stringa di caratteri forniti dall’utente sostituendo i caratteri di apice e doppio apice con le corrispondenti sequenze di escape: \’ e \”. Sia Php che MySQL interpretano questi caratteri come parte della stringa e non più come delimitatori, rendendo vano l’attacco. Per eseguire questa sostituzione si fa uso della funzione predefinita di Php mysql_real_escape_string che riceve una stringa e la restituisce sostituendo i caratteri apice, doppio apice, backslash e la stringa nulla con le rispettive sequenze di escape.
Copied!$user = mysqli_real_escape_string($conn, $_POST["utente"]); $pwd = mysqli_real_escape_string($conn, $_POST["password"]);
3. Validazione dell’input
Con questa tecnica ciascun carattere delle stringhe fornite dall’utente viene controllata per vede-re se corrisponde a un carattere alfabetico (maiuscolo o minuscolo) oppure a una cifra numerica
Copied!if (!ctype_alnum($user) || !ctype_alnum($pwd)) { die("Input non valido"); }
ctype_alnum($user)
→ Questa funzione di PHP verifica se la stringa $user
contiene solo caratteri alfanumerici (lettere dalla A alla Z e numeri da 0 a 9).
- Se
$user
contiene solo caratteri validi, restituiscetrue
. - Se
$user
contiene caratteri speciali, spazi o simboli, restituiscefalse
.
Negazione !ctype_alnum($user)
→ L’operatore !
(negazione) inverte il valore di ctype_alnum($user)
.
- Se
$user
è valido (true
), diventafalse
e il codice continua l’esecuzione. - Se
$user
è invalido (false
), diventatrue
e il bloccoif
viene eseguito.
Controllo su $pwd
→ Stessa logica applicata alla variabile $pwd
.
Operatore logico ||
(OR)
- Se almeno una delle due variabili (
$user
o$pwd
) non è alfanumerica, la condizione diventatrue
e viene eseguita l’istruzionedie("Input non valido");
. - Se entrambe le variabili sono alfanumeriche, il codice continua normalmente.
die("Input non valido");
- Termina immediatamente l’esecuzione dello script e mostra il messaggio
"Input non valido"
se almeno una delle variabili non è alfanumerica.
4. Controllo di parole chiave pericolose
Copied!$vietate = ["select", "insert", "update", "delete", "drop", "alter", "--", "'"]; foreach ($vietate as $word) { if (stripos($user, $word) !== false || stripos($pwd, $word) !== false) { die("Dati non validi"); } }
- Crea un array chiamato
$vietate
, contenente parole riservate usate nelle query SQL (SELECT
,INSERT
,UPDATE
,DELETE
, ecc.) e caratteri speciali (--
e'
), comunemente utilizzati negli attacchi SQL Injection. - Avvia un ciclo
foreach
per scorrere tutte le parole contenute nell’array$vietate
- La funzione
stripos($user, $word)
cerca la parola$word
all’interno della stringa$user
. - La funzione
stripos($pwd, $word)
fa lo stesso per$pwd
.stripos()
non è case-insensitive (non fa distinzione tra maiuscole e minuscole), quindi “SELECT”, “select” o “SeLeCt” saranno tutte trovate. - Se la parola
$word
è trovata,stripos()
restituisce la posizione della parola nella stringa. - Se la parola non è presente, restituisce
false
. - Se la parola è trovata, la posizione sarà un numero (es.
0
,5
, ecc.), quindi la condizione diventatrue
ed eseguedie()
. - Se la parola non è trovata,
stripos()
restituiscefalse
, quindi la condizione non viene soddisfatta.
Sotto un’immagine che spiega il funzionamento

5. Buffer overflow – Limitare la lunghezza dell’input
Un altro tipo di attacco è effettuato immettendo nelle caselle di testo di una maschera stringhe dicaratteri molto lunghe, nella speranza che una stringa di lunghezza superiore a quella prevista nello script o nel database invada la memoria con effetti imprevedibili. La contromisura da adottare per questo tipo di attacco è di troncare la stringa al numero di caratteri previsto per un dato campo. Per estrarre una sottostringa da una stringa si usa la funzione predefinita substr. Per esempio, seper utente e password sono previsti 15 caratteri, per prevenire un attacco di questo tipo bisogna ricordarsi di troncare le stringhe ricevute come valore di questi parametri ai primi 15 caratteri.
Copied!$user = substr($_POST["utente"], 0, 15); $pwd = substr($_POST["password"], 0, 15);
Conclusione
Scrivere codice sicuro è essenziale per proteggere i dati. L’uso di Prepared Statements, la validazione dell’input e altre tecniche di sicurezza permettono di prevenire attacchi SQL Injection e mantenere il database protetto.
Esercizio Completo: Visualizzare in una griglia l’elenco degli utenti di una città scelta dall’utente all’interno di un elenco
Si consideri il problema di estrarre dalla tabella cittadini del database test l’elenco degli utenti appartenenti a una città richiesta da tastiera attraverso una casella di testo. Il problema può essere risolto con un form contenente una casella di testo che consente all’utente di specificare la città richiesta e un pulsante di comando che attiva lo script per ricevere il valore dal form e selezionare i dati nella tabella del database.
Mostriamo una soluzione efficiente per il problema dell’estrazione degli utenti di una città, con particolare attenzione agli aspetti di sicurezza, attraverso l’uso di una casella combinata per la scelta della città.
Occorre creare la tabella cittadini nel database test. Dal Pannello PHPMyAdmin nella scheda SQL scrivere il seguente codice e premere esegui:
Copied!CREATE TABLE IF NOT EXISTS utenti ( codice INT NOT NULL AUTO_INCREMENT PRIMARY KEY, cognome VARCHAR(50) NOT NULL, nome VARCHAR(50) NOT NULL, indirizzo VARCHAR(100) NOT NULL, citta VARCHAR(50) NOT NULL )
Popoliamo la tabella in modo da avere una decina di record come nella figura sotto:

Il problema viene risolto con due file:
- il primo è la pagina con il form che consente all’utente di specificare la città richiesta; anche questo file è una pagina PHP perché deve accedere al database per leggere la tabella utenti e presentare all’utente l’elenco delle città effettivamente presenti nella tabella;
- il secondo file è una pagina PHP che riceve il valore dal form e seleziona i dati nella tabella del database.
Il comando SQL che estrae le città è il seguente:
Copied!SELECT DISTINCT citta FROM utenti ORDER BY citta
La pagina per la scelta della città utilizza il tag HTML per la visualizzazione di una casella combinata:
Copied!<select name=". . ."> option value="valore1">descrizione1</option> option value="valore2">descrizione2</option> . . . </select>
Il tag viene generato dinamicamente dallo script PHP che legge la tabella utenti estraendo le città presenti nel database.
Sotto la codifica di cercacitta.php
<!DOCTYPE html>
<html>
<head>
<title>Scelta della città</title>
</head>
<body>
<form action="elencocitta.php" method="post">
<p>Scegli la città nell'elenco</p>
<select name="citta">
<?php
$host = "localhost";
$username = "root";
$password = "";
$db_nome = "test";// connessione al server
$conn = new mysqli($host, $username, $password, $db_nome);
if ($conn->connect_errno) {
echo "Impossibile connettersi al server: " . $conn->connect_error . "\n";
exit;
}
// lettura dei dati dalla tabella
$tab_nome = "cittadini";
$sql = "SELECT DISTINCT citta FROM $tab_nome ORDER BY citta";
$result = $conn->query($sql);
while ($row = $result->fetch_assoc()) {
echo "<option value=\"".$row['citta']."\">".$row['citta']."</option> \n";
}
$result->free();
$conn->close();
?>
</select>
<input type="submit" name="invio" value="Cerca" />
</form>
</body>
</html>
Nell’istruzione all’interno del ciclo while:
Copied!echo "<option value=\"".$row['citta']."\">".$row['citta']."</option> \n";
la sequenza di escape\” inserisce le virgolette nella stringa del comando echo, mentre la sequenza di escape\n inserisce il ritorno a capo nella visualizzazione con il browser del codice sorgente generato dal server. L’istruzione echo crea dinamicamente le voci della casella combinata secondo la sintassi del linguaggio HTML per la struttura <select> … </select>. Il campo citta viene utilizzato due volte: come valore della voce nella casella combinata (attributo value) e come descrizione visualizzata per l’utente nelle righe del tag <option> … </option>.
Se sul browser scriviamo localhost/test/cercacitta.php otterremo lìimmagina a sinistra qui sotto e se analizziamo la pagina vediamo la figura a destra


la action del form richiama lo script elencocitta.php che farà visualizzare effettivamente l’elenco dei cittadini secondo la città scelta:
<!DOCTYPE html>
<html>
<head>
<title>Elenco utenti per città</title>
</head>
<body>
<?php
// valore fornito dall'utente
$citta = $_POST["citta"];
echo "<h2>Elenco utenti: città = ". $citta . "</h2> \n";
$host = "localhost";
$username = "root";
$password = "";
$db_nome = "test";
// connessione al server
$conn = new mysqli($host, $username, $password, $db_nome);
if ($conn->connect_errno) {
echo "Impossibile connettersi al server: " . $conn->connect_error . "\n";
exit;
}
// lettura dei dati dalla tabella
$tab_nome = "cittadini";
$sql = "SELECT * FROM $tab_nome WHERE Citta = '$citta'";
$result = $conn->query($sql);
?>
<!-- intestazione della tabella -->
<table border="1">
<tr>
<th>Codice</th>
<th>Cognome</th>
<th>Nome</th>
<th>Indirizzo</th>
</tr>
<?php
while ($row = $result->fetch_assoc()) {
// operazioni sulla riga
echo "<tr> \n";
echo "<td>" . $row["codice"] . "</td> \n";
echo "<td>" . $row["cognome"] . "</td> \n";
echo "<td>" . $row["nome"] . "</td> \n";
echo "<td>" . $row["indirizzo"] . "</td> \n";
echo "</tr> \n";
}
// fine while
$result->free();
$conn->close();
?>
</table>
</body>
</html>
Nella pagina Web il codice HTML e le istruzioni in PHP si alternano. Il codice PHP è delimitato dai tag <?php … ?>. Le righe che formano la tabella sono delimitate dai tag <tr> e </tr>: sono generate all’interno di un ciclo while, durante il quale si recupera una riga dal risultato e si visualizza il valore dei vari campi, tramite il comando echoe i tag <td> e </td>. La prima riga della tabella utilizza i tag <th> e </th> per le intestazioni delle colonne.
L’esecuzione degli script, scegliendo, ad esempio Bari, produce il risultato riportato nell’immagine seguente:

Si potrebbe risolvere il problema utilizzando anche una sola pagina php. Si utilizza la variabile $_SERVER [‘PHP_SELF’] che contiene il nome dello script in esecuzione. La pagina PHP viene eseguita due volte: la prima volta per leggere dal database le città e popolare la casella combinata per la scelta dell’utente; la seconda per eseguire la query che estrae gli utenti appartenenti alla città selezionata. L’uso della funzione isset consente di distinguere il primo dal secondo caricamento della stessa pagina.Il codice completo della pagina PHP è riportato di seguito:
<!DOCTYPE html>
<?php
$host = "localhost";
$username = "root";
$password = "";
$db_nome = "test";
$tab_nome = "cittadini";
$conn = new mysqli($host, $username, $password, $db_nome);
if ($conn->connect_errno) {
echo "Impossibile connettersi al server: " . $conn->connect_error . "\n";
exit;
}?>
<html>
<head>
<title>Elenco utenti per città</title>
</head>
<body>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
<p>Scegli la città nell'elenco</p>
<select name="citta">
<?php
// comando SQL
$sql = "SELECT DISTINCT Citta FROM $tab_nome ORDER BY Citta";
$result = $conn->query($sql);
while ($row = $result->fetch_assoc()) {
echo "<option value=\"" . $row['Citta'] . "\">" . $row['Citta'] . "</option> \n";
}
?>
</select>
<input type="submit" name="invio" value="Cerca" />
<input type="submit" name="aggiorna" value="Aggiorna" />
</form>
<?php
if(isset($_POST["aggiorna"])) {
header("Location: " . $_SERVER['PHP_SELF'] );
} if(isset($_POST["invio"])) {
// acquisisce il dato dal form HTML
$citta = $_POST["citta"];
echo "<h2>Elenco utenti: città = ". $citta . "</h2> \n";
// comando SQL
$sql = "SELECT * FROM $tab_nome WHERE citta = '$citta'";
$result = $conn->query($sql);
?>
<!-- intestazione della tabella -->
<table border="1">
<tr>
<th>Codice</th>
<th>Cognome</th>
<th>Nome</th>
<th>Indirizzo</th>
</tr> <?php
while ($row = $result->fetch_assoc()) {
// operazioni sulla riga
echo "<tr> \n";
echo "<td>" . $row["codice"] . "</td> \n";
echo "<td>" . $row["cognome"] . "</td> \n";
echo "<td>" . $row["nome"] . "</td> \n";
echo "<td>" . $row["indirizzo"] . "</td> \n";
echo "</tr> \n";
} // fine while
} // fine isset su invio
$result->free();
$conn->close();
?>
</table>
</body>
</html>
La variabile predefinita $_SERVER[‘PHP_SELF’] contiene il nome dello script in esecuzione, mentre la funzione predefinita isset controlla che alla variabile $_POST[“invio”] sia stato assegnato un valore, cioè che l’utente abbia fatto clic sul tasto Cerca.
Nel form di questa pagina PHP è stato aggiunto un nuovo pulsante Aggiorna che consente all’utente di fare un refresh della pagina, eliminando l’elenco prodotto dalla precedente ricerca prima di effettuare la scelta di una nuova città. Il pulsante è gestito dal seguente codice PHP:
Copied!if(isset($_POST["aggiorna"])) { header("Location: " . $_SERVER['PHP_SELF']); }
Quando l’utente fa clic sul pulsante Aggiorna viene ricaricata la pagina stessa, identificata dalla variabile predefinita $_SERVER[‘PHP_SELF’], inviando la richiesta al browser tramite la funzione header. La funzione header, avente la sintassi generale:
Copied!header("Location: URL pagina");
provoca il reindirizzamento del browser a un’altra pagina indicata come argomento della funzione.
Nella sezione DOWNLOAD o direttamente da qui: https://profgiagnotti.it/download/4860/?tmstv=1744539632 è possibile scaricare gli script descritti nell’articolo
Lascia un commento