Innanzi tutto complimenti per l'iniziativa
Un ottimo lavoro!
E' importantissima la sicurezza e molto spesso la si da per scontata.
Volevo dare il mio modesto contributo alla pillola.
Registers globals
Credo che sia il caso di non utilizzare più le variabili globali.
Utilizziamo invece i nuovi array $_POST, $_GET, ecc.
Le variabili globali sono attualmente depracate e potrebbero scomparire da un momento all'altro nelle nuove versioni di PHP.
Creare ora uno script con le globali, inizializzate o meno, oltre a comportare potenziali problemi di sicurezza rende i vostri script potenzialmente incompatibili con le prossime verisioni di php oltre che meno comprensibili.
Molti host le tengono abilitate, quindi come già suggerito, in fase di sviluppo cercate di inizializzare tutte le variabili e considerate ogni anomalia.
Le notice vanno sempre prese in considerazione e risolte. Molto spesso può capitare di perdere un sacco di tempo a cercare l'origine di un warning o di un anomalia solamente perchè non si visualizzano le notice.
Include dinamici
Si è risolto controllando la cartella uno dei principali problemi di sicurezza.
Siamo così sicuri che lo script incluso deve trovarsi nella stessa cartella del file che lo include.
Tuttavia esistono almeno altri due problemi di sicurezza anche controllando la cartella.
Il primo, riguarda la possibilità di includere tutti i file appartenenti alla stessa cartella.
Quindi attenzione a cosa mettiamo in quella cartella.
Il secondo, quello meno simpatico, riguarda la possibilità da parte di un malintenzionato di mandare in loop lo script fino a causare il crash del webserver.
Prendiamo come esempio lo script d'esempio della pillola.
Codice PHP:
if ((isset($_GET['include_script'])) && (dirname($_GET['include_script']) == ".")) {
include($_GET['include_script']);
}
Supponendo che il file contenente questo codice sia security_hole.php che cosa accadrebbe richiamando un url come questo?
Codice PHP:
http://sectest.html.it/security_hole.php?include_script=security_hole.php
La variabile GET include_script risulta settata, la directory risulta la stessa, e lo script include se stesso.
La copia inclusa ricomincia il test, lo passa e include per l'n-esima volta se stesso..
E via così fino a saturare le risorse.
Pensate l'effetto di un centinaio di richieste simili dirette al vostro webserver.
Un bel DoS.
La soluzione è utilizzare include_once() al posto di include().
Che cosa fa include_once a differenza di include?
Semplicemente controlla se il file è già stato incluso. Se il file è già stato incluso non lo include più.
Inoltre vorrei sottolineare che, anche supponendo di aver alzato adeguate barriere per filtrare l'input, siamo sempre in una situazione di potenziale pericolo.
Qualcosa potrebbe esserci scappato.
Gli include dipendenti da input esterni, se proprio è indispensabile usarli (in realtà non lo è mai), possono essere resi sicuri numerando i file ed utilizzando un semplice switch.
Codice PHP:
/**
1 = file includi_foto.php
2 = file includi_oggetti.php
**/
$include = (isset($_GET['inc'])) ? $_GET['inc'] : false;
switch($include) {
case 1:
include_once('includi_foto.php');
break;
case 2:
include_once('includi_oggetti.php');
break;
default:
echo 'non fare il simpatico, levati dalle palle';
}
Questo ci garantisce il pieno controllo dei file inclusi e quindi la massima sicurezza.
Potrà essere incluso solo quello che abbiamo previsto sia incluso, nonostante rimanga comunque possibile decidere cosa includere dall'esterno.
Autenticazione con MD5
@mark2x sei incappato in un errore di distrazione immagino 
la query, anche in presenza di tentativi di hacking, risulterebbe simile a:
codice PHP:
SELECT * FROM tabella_utenti WHERE usr=’qualsiasi_nome’ AND pwd=’ 6f8f57715090da2632453988d9a1501b’;
e sarebbe, appunto, non vulnerabile.
visto che qualche riga più in alto avevi giustamente scritto
Inserendo in username la stringa: qualsiasi_nome’ OR 1 -- si ottiene:
codice PHP:
SELECT * FROM tabella_utenti WHERE usr=’qualsiasi_nome’ OR 1 -- AND pwd=’qualsiasi_stringa’;
E l’accesso è assicurato. “- - “ rappresenta per MySQL il delimitatore di commento: tutto ciò che segue viene ignorato.
Capita 
Casting del tipo
Hai suggerito di castare il tipo.
La precauzione è valida.
Volevo solo far notare che castare il tipo direttamente sulla query è rischioso perchè il risultato è imprevedibile.
Pensa a castare su una query DELETE o UPDATE.
Potresti comunque finire con il cancellare o aggiornare qualcosa che non doveva essere aggiornato o cancellato, quindi anche se fai andare in confusione l'attaccante potresti ritrovarti comunque danneggiato dal tentativo di attacco.
Io consiglio di crearsi delle piccole funzioni in grado di controllare e castare al tempo stesso.
Posto una delle funzioni che uso più spesso (così magari mi controllate pure che sia sicura eheh
).
Codice PHP:
function validId(&$str) {
if ( isset($str) && checkint($str) ) {
$str=(int) $str;
return true;
}
else {
trigger_error("Trovato un id non valido\n".debug_str(),E_USER_NOTICE);
return false;
}
}
Per chi non lo sapesse la funzione riceve la variabile come riferimento, e non come valore.
Questo comporta che ogni modifica effettuata alla variabile passata all'interno della funzione si riflette sulla variabile stessa.
Cioè, in breve
Codice PHP:
$a = 1;
piu($a);
echo $a;
function piu($a) {
$a++;
}
stampa 1
invece
Codice PHP:
$a = 1;
piu($a);
echo $a;
function piu(&$a) { //nota l'& prima della variabile
$a++;
}
stampa 2.
Precisato questo torniamo alla funzione validId.
Vedete come la funzione controlla che la variabile sia settata.
Infatti passare un valore non settato per riferimento non causa notice o warning.
Controlla poi che sia un intero o una stringa intera con la funzione checkint (non la posto per non allungare troppo) e poi se si tratta di un interno o di una stringa intera effettua comunque il casting che, se è una stringa intera si comporta coerentemente, altrimenti non si applica.
Se tutto è ok, la funzione prepara la variabile e ritorna true altrimenti false.
Risulta quindi molto comodo e soprattutto sicuro controllare la variabile.
Per esempio.
Codice PHP:
if(!validId($_GET['a'])) echo "Passato un id non valido";
else {
$sql = "DELETE FROM raffreddamento_reattore_nucleare_1 WHERE id = '".$_GET['a']."';";
}
Il principio è applicabile a qualsiasi altro tipo di controllo.
Escape per SQL
Bisogna fare attenzione anche alla codifica dei caratteri che si usa quando si fa l'escaping.
Infatti mysql_escape non è sicura al 100%, anche eliminando i caratteri che non considera, poichè non presta attenzione alla codifica delle stringhe che riceve in input ed alla codifica della connessione con il db.
Un apice in qualche codifica potrebbe essere differente dall'apice che normalmente viene neutralizzato da mysql_escape.
Quindi è sempre conveniente, se non altro per togliersi ogni dubbio, utilizzare mysql_real_escape_string.
Inoltre se stiamo lavorando con codifiche multi-byte, come può essere utf8, è utile utilizzare per sostituzioni o lavorazioni sulle stringhe delle funzioni apposite con il supporto multi-byte/multi-codifica, come può essere mb_ereg.
Naturalmente è difficile che un script-kiddie sia in grado di sfruttare debolezze di questo genere. Ma è giusto considerare che c'è qualcuno in grado di farlo e quindi anche se il software sarà difficilmente attaccabile, è comunque vulnerabile.
Chiudo
Chiudo ricordando un altro aspetto che può essere scontato ma è bene ricordare.
Eliminate ogni genere di error_reporting sugli script in produzione.
Codice PHP:
error_reporting(0);
Ricordate che disabilitare la visualizzazione degli errori non costituisce mai un problema e non significa rinunciare al debug.
Anzi, è utile per avere un controllo maggiore sul proprio software.
Per controllare gli errori sugli script causati da voi o da terzi sullo script potete usare l'error_handler definendo una vostra funzione per catturare gli errori.
Potete decidere di farvi inviare una mail ad ogni errore che si verifica, salvare tutto su un file di log, entrambi, o qualsiasi alta cosa vi aggradi.
L'importante è che con l'error_reporting a 0 gli unici a sapere cosa succede sarete VOI.
Un attaccante quando trova l'error_reporting a 0 può anche causare errori e passare su alcune falle ma se non ha riscontri può credere che la falla o l'errore non esiste perchè voi l'avete gestita correttamente.
Questo non significa fregarsene della sicurezza.. perchè appena uno insiste può bucare comunque. Ma questo sistema è utile quando nonostante tutti i vostri sforzi per ottenere la massima sicurezza qualcosa è scappato.

Bene.. spero di non avervi annoiati! 