Premessa
La sicurezza nel web non è mai abbastanza ma soprattutto in prodotti Open Source è sempre meglio prendere tutte le precauzioni possibili per evitare che i malintenzionati possano far danni.
Problema
PHP lavora principalmente con le stringhe ed anche le richieste, GET, POST, REQUEST o altro, sono sempre e comunque gestite come stringhe.
Tutti gli applicativi mediamente sicuri hanno uno o più metodi per verificare l'affidabilità di una richiesta al fine di evitare sql injections, request injections ed altro ancora.
Ma qual'è il modo più semplice per fare questo ?
Soluzione
I dati inviati in una pagina sono anch'essi principalmente di tipo stringa ed il modo più sicuro per evitare problemi è fare verifiche dedicate per ogni tipo di dato.
Un nome ad esempio non potrà contenere caratteri speciali, al massimo spazi ed apostrofi, quindi su una form contenente i soliti campi nome e cognome, la soluzione migliore potrebbe essere quella di verificare con una espressione regolare che la variabile inviata sia un match di questa stringa: "/^[a-zA-Z' ]+$/".
Ogni altro campo potrebbe avere un tipo di controllo analogo o completamente diverso, il punto è che una giusta espressione regolare può essere in grado di scremare di suo un altissima percentuale di "tentativi maliziosi".
A questo si deve aggiungere, per ogni query fatta in database, la corrispettiva funzione di escape (mysql_escape_string, sqlite_escape, eccetera ...).
Definito quanto detto per le stringhe e stabilito quindi che fare una funzione contenente tutti i controlli per un tipo di campo piuttosto che un altro non avrebbe senso, restano gli altri utili e soliti valori da verificare.
I dati di tipo int, float, o di tipo booleano, possono essere infatti parsati in modo molto semplice, sfruttando una espressione regolare dedicata ma sicuramente affidabile.
Oltre a questo è possibile usare una funzione apposita per evitare anche di mostrare i soliti e fastidiosi notice negli script meno scrupolosi e che non si preoccupano delle possibili falle, dei possibili attacchi e dei possibili errori di codice.
La funzione
codice:
// http://www.devpro.it/code/129.html
function getRequest(&$method, $name, $type = null, $default = null) {
/**
* getRequest(&$_GLOBAL:mixed, $name:string[, $type:string[, $default:mixed]]):mixed
*
* @param mixed super global array (i.e. $_POST, $_GET, $_REQUEST ... others)
* @param string expected variable name (i.e. for $_GET["test"] the name "test")
* @param string type of the value:
* bool verify if request is a boolean value (true/false/0/1)
* int verify if request is an integer value (range, -N, N+)
* uint verify if request is an unsigned integer (range, 0, N+)
* float verify if request is a float number (range, -Z, Z+)
* serialized verify if request is a real serialized value (returns unserialized value)
* [default null]
* @param mixed default type for value [default null]
*/
$validTypes = array("bool", "int", "uint", "float", "serialized");
$result = $default;
if(isset($method[$name])) {
if(!is_null($type) && in_array($type, $validTypes)) {
$method[$name] = trim($method[$name]);
switch($type) {
case "bool":
if(preg_match("/^(true|false|0|1)$/", $method[$name]))
$result = (bool)str_replace('false', '0', $method[$name]);
break;
case "int":
if(preg_match("/^([\-]?[0-9]+)$/", $method[$name]))
$result = (int)$method[$name];
break;
case "uint":
if(preg_match("/^([0-9]+)$/", $method[$name]))
$result = (int)$method[$name];
break;
case "float":
if(preg_match("/^[0-9]*(\.|\,)[0-9]+$/", $method[$name]))
$result = (float)str_replace(',', '.', $method[$name]);
break;
default:
$result = @unserialize($method[$name]);
if($result === false && $method[$name] !== serialize($result))
$result = $default;
break;
}
}
else
$result = &$method[$name];
}
return $result;
}
L'utilizzo
Se vogliamo usare una variabile $_POST, ad esempio $_POST["nome"], non dovremmo mai evitarne il controllo.
Grazie a questa semplice funzione è possibile fare una chiamata come questa
codice:
$_POST["nome"] = getRequest($_POST, "nome");
Assicurandoci che il server non mostri NOTICE da nessuna parte e restituendoci la stringa esatta, se esistente o il valore null, se la variabile non è stata inviata.
Un semplice controllo subito dopo potrebbe darci la certezza che lo script lavorerà con una variabile e non con un "fantasma".
codice:
if($_POST["nome"]) ...
#oppure in linea
if(($_POST["nome"] = getRequest($_POST, "nome")) !== null) ...
La funzione è anche in grado di restituirci tipi primitivi esattamente come ci servono.
Vediamo un pò di esempi
codice:
// form con un checkbox con valore 1 per l'approvazione di qualcosa
$booleano = getRequest($_POST, "approvato", "bool", false);
// $booleano sarà una primitiva booleana true solo se il campo
// di nome approvato esisteva e solo se conteneva il valore 1 o true
// <input type="checkbox" name="approvato" value="true" /> approvo le condizioni
// immaginiamo una select con degli id presi da un database.
// tale select dovrà inviare sempre un intero maggiore di zero
// per essere certi che un id è stato scelto ed inviato ...
$selectid = getRequest($_POST, "dbid", "uint", 0);
// solo se $selectid è maggiore o diverso da 0 saremo certi che l'utente ha scelto un id
// un intero
$miointero = getRequest($_POST, "prodotti", "int");
// se $miointero sarà diverso da null sarà un numero intero valido
// un float, scritto con la virgola o con il punto
$miofloat = getRequest($_POST, "prodotti", "float");
// se $miofloat sarà diversa da null sarà un numero float valido
// in fine, unaserializzata
$tantivalori = getRequest($_POST, "valori", "serialized");
// solo se la variabile $_POST["valori"] esiste, è stata inviata, ed il suo contenuto è un valore correttamente serializzato, allora $tantivalori sarà una variabile valida.
Conclusione
La funzione mostrata diventa inutile se oltre i suoi controlli non si aggiungono le giuste verifiche per i dati di ingresso ma può aiutare ad evitare di scrivere ogni volta il solito controllo sul dato
if(isset($_GET["nome"]) && $_GET["nome"] === ... )
nonchè può aiutare ad evitare attacchi fastidiosi restituendo un valore di default e scremando quindi i dati non previsti che sempre grazie a questa funzione saranno restituiti come tipo primitivo già castato.