Sicurezza nella personalizzazione e gestione di un sito WordPress

← ritorno a WordPress

PHP gestisce i metodi HTTP GET e POST e i file con array superglobali. Quando dobbiamo manipolare un input esterno, dobbiamo sempre validare e filtrare i dati in ingresso. In altre parole dobbiamo considerare qualsiasi forma di input come potenzialmente pericoloso ed agire di conseguenza.

Quindi sarà necessario:

  • filtrare le query SQL con il metodo prepare() della classe wpdb;
  • filtrare ogni variabile veicolata tramite GET o POST da inserire nel flusso di output con le funzioni di escape (con prefisso esc_) di WordPress;
  • validare i dati inseriti dagli utenti secondo il formato dati richiesto (numero, stringa, indirizzo e-mail.. );
  • se si gestiscono file (upload, manipolazione, inclusione..) verificare sempre che il file sia del tipo corretto e che l’operazione che l’utente vuole eseguire sia consentita;
  • se si usano le sessioni di PHP, validare sempre la sessione corrente tramite token o autenticazione utente.

Si analizzi il codice seguente:

global $wpdb;
$reservation_id = $_GET[‘res_id’];
$reservation = $wpdb->get_results( “SELECT * FROM reservations WHERE id = $reservation_id” );

Tendenzialmente la variabile GET potrebbe contenere qualsiasi valore ed è una porta spalancata per un tentativo di SQL injection. Sappiamo che ci dobbiamo aspettare un valore numerico intero. Quindi come validazione preliminare e grossolana potremmo scrivere:

$sane_res_id = 0;
if( is_numeric( $reservation_id ) ) {
  $r_id = intval( $reservation_id );
  if( filter_var( $r_id, FILTER_VALIDATE_INT ) ) {
    $sane_res_id = $r_id;
  }
}

Tale accorgimento è sufficiente ai fini della sicurezza? Assolutamente no. Quindi usiamo wpdb::prepare():

$prepared = $wpdb->prepare( “SELECT * FROM reservations WHERE id = %d”, array( $sane_res_id ) );
$reservation = $wpdb->get_results( $prepared );

Sulla base di quanto già detto, consideriamo ora quest’altro esempio:

$title = $_GET[‘title’];
echo ‘<h1>’ . $title . ‘</h1>’;

Questo è un esempio tipico in cui può verificarsi un attacco di tipo XSS (Cross-site scripting), ossia l’inserimento nella pagina di codice JavaScript malevolo. Per prevenirlo dobbiamo fare in modo che la stringa passata non contenga appunto tale codice:

$raw_title = wp_kses( $title );
echo esc_html( $raw_title );

Qui abbiamo semplicemente combinato le funzioni di WordPress wp_kses() ed esc_html() per essere sicuri che la stringa non contenga nessun tag HTML.

Abbiamo visto come i malintenzionati cerchino di eseguire il codice dei file da remoto. Per impedire questa pratica possiamo utilizzare una costante PHP globale nei nostri plugin:

define( ‘MY_PLUGIN_C’, ‘1’ );

Quindi nei nostri file eseguiamo questa verifica sempre all’inizio:

if( !defined( ‘MY_PLUGIN_C’ ) ) {
  exit();
}

In questo modo il nostro codice potrà essere eseguito solo all’interno del contesto del plugin.

Osserviamo quindi il codice mostrato di seguito:

if( isset( $_GET['pdf'] ) ) {
	$pdf_base_url = 'http://sito.com/wp-content/uploads/pdf/';
	$pdf_base_path = $_SERVER['DOCUMENT_ROOT'] . '/wp-content/uploads/pdf/';
	$pdf = $_GET['pdf'];
	$pdf_full_url = $pdf_base_url . $pdf;
	$pdf_full_path = $pdf_base_path . $pdf;
	if( preg_match( '/\.pdf$/', $pdf ) ) {
	  if( file_exists( $pdf_full_path ) ) {
		$finfo = finfo_open( FILEINFO_MIME_TYPE );
		$mimetype = finfo_file( $finfo, $pdf_full_path );
		finfo_close( $finfo );
		if( $mimetype == 'application/pdf' ) {
			header( 'Content-type: application/pdf' );
			header( 'Content-Disposition: attachment; filename="' . $pdf . '"' );
			readfile( $pdf_full_url );
		}
	  }
	}
}

Una variabile GET contiene il nome di un file PDF da scaricare. I PDF nel nostro esempio sono stati uploadati tramite FTP perché troppo pesanti per la Media Library. Nell'esempio abbiamo verificato che il nome del file sia valido in prima istanza. Quindi abbiamo controllato che il file richiesto sia effettivamente un file PDF verificando il suo MIME Type. Se non avessimo eseguito questa operazione, si sarebbe potuto scaricare qualsiasi tipo di file.

La verifica del MIME Type si rivela fondamentale anche nella gestione degli upload. In questo caso è inutile implementare un sistema di upload personalizzato, sarebbe meglio invece cercare di utilizzare sempre la Media Library e le sue API per la gestione degli allegati e dei file.

Ora, si ipotizzi il caso di un carrello per un e-commerce. Quando l'utente sceglie un prodotto, questo viene aggiunto all'array superglobale $_SESSION. Ma come facciamo a sapere che sia sempre lo stesso utente ad effettuare tali operazioni?

In questo caso dobbiamo impostare un token di sessione:

if( !isset( $_SESSION['my-token'] ) ) {
	$token = md5( $_SERVER['HTTP_USER_AGENT'] );
	$_SESSION['my-token'] = $token;
}

Quindi ad ogni cambio pagina va verificato il token creato:

if( isset( $_SESSION['my-token'] ) ) {
	$token = md5( $_SERVER['HTTP_USER_AGENT'] );
	if( $_SESSION['my-token'] == $token ) {
		//...
	}
}

Alla fine della procedura d'acquisto la sessione dovrà essere distrutta e con essa il token di sessione.