Per offrirti un'esperienza di navigazione sempre migliore, questo sito utilizza cookie propri e di terze parti, partner selezionati. I cookie di terze parti potranno anche essere di profilazione.
Le tue preferenze si applicheranno solo a questo sito web. Puoi modificare le tue preferenze in qualsiasi momento ritornando su questo sito o consultando la nostra informativa sulla riservatezza.
E' possibile rivedere la nostra privacy policy cliccando qui e la nostra cookie policy cliccando qui.
Per modificare le impostazioni dei cookies clicca qui
  • seguici su feed rss
  • seguici su twitter
  • seguici su linkedin
  • seguici su facebook
  • cerca

SEI GIA' REGISTRATO? EFFETTUA ADESSO IL LOGIN.



ricordami per 365 giorni

HAI DIMENTICATO LA PASSWORD? CLICCA QUI

NON SEI ANCORA REGISTRATO ? CLICCA QUI E REGISTRATI !

Come prevenire attacchi di tipo XSS (Cross-Site Scripting), in PHP

01 marzo 2025
Come prevenire attacchi di tipo XSS (Cross-Site Scripting), in PHP

Oggi ci occupiamo di come proteggere un sito realizzato in PHP, da tentativi di attacchi XSS (Cross-Site Scripting).

Gli attacchi di tipo Cross-Site Scripting consistono nell'iniettare del codice malevolo, degli script, dannosi per il nostro sito, attraverso i parmametri inviati via GET e POST: questo codice viene poi eseguito nel browser degli utenti che visitano la pagina compromessa con lo scopo di rubare informazioni riservate, o installare malware, o reindirizzare l’utente a siti di phishing.

Esistono molte tecniche per prevenire questo tipo di attacchi, ed oggi vediamo quelle implementabili via PHP che possiamo riassumere in queste voci che approfondiremo tra poco:

  • Validazione dell'input: occorre assicursi che i dati ricevuti in input siano validi e della tipologia attesa (stringhe, numeri, date, email, .... ), utilizzando la funzione filter_var()
  • Sanitizzazione dell'input: occorre convertire i caratteri speciali in entità HTML utilizzando la funzione htmlentities() che converte i caratteri speciali, come <, >, & e ", in entità HTML, impedendo così che venga poi presentato nelle pagina web esattamente come ricevuto e quindi impedendo l'esecuzione di codici script.

Come prima cosa occorre differenziare chiamate di tipo GET da quelle POST.

Nelle chiamate GET, come sicuramente saprete, i dati vengono inviati nell’URL della pagina che state aprendo, quindi sono visibili nella barra degli indirizzi del browser. 

Ad esempio: http://www.miosito.it/page?param1=value1&param2=value2

Nelle chiamate POST invece i dati non sono visibili nell’URL, ma inviati nel corpo della richiesta HTTP.

In questo articolo ci occupiamo delle chiamate POST, più esposte a rischi in quanto spesso a fronte di queste chiamate salviamo i dati su un database.

Per semplificare al massimo avremo un file chiamato "post.php" che riceverà in input i dati ricevuti da un form, e un file chiamato "functions.php", con dentro la classe "Functions", che conterrà alcuni metodi (funzioni) che useremo lato PHP per "pulire" e verificare i dati in ingresso.

Ecco il file "functions.php": nella classe "Functions" sono presenti questi metodi che tra poco spiegheremo nel dettaglio:

  • sanitizePost: usato per sanificare i dati ricevuti in input
  • checkReferer: usato per verificare la provenienza della chiamata
<?php

class Functions
{  
	public function sanitizePost($data){

		foreach ($data as $key => $type) {

			// se non valorizzato continuo
			if (!isset($_POST[$key]) || $_POST[$key] === '') {
				continue;
			}

			// sempre intero o decimale
			if( $type=='integer' || $type=='decimal'  ){
				
				// se non numerico
				if (!is_numeric($_POST[$key])) {
					exit(); 
				}

				// se intero
				if($type=='integer'){
					if (!filter_var($_POST[$key], FILTER_VALIDATE_INT) ) {
						exit();
					}
				}

				// se decimale
				if($type=='decimal'){
					if(!filter_var($_POST[$key], FILTER_VALIDATE_FLOAT,FILTER_FLAG_ALLOW_FRACTION )){
						exit(); 
					}
				}
			}

			// se stringa
			elseif($type=='string'){
				if(!is_string($_POST[$key])){
					exit(); 
				}

				$_POST[$key] = str_replace('../', '', $_POST[$key]);
				$_POST[$key] = htmlentities($_POST[$key] , ENT_QUOTES | ENT_DISALLOWED, 'UTF-8');
				$_POST[$key] = strip_tags($_POST[$key]);
				$_POST[$key] = trim($_POST[$key]);
			}		
		} 
	} 

    public function checkReferrer() {
        if($_SERVER['REQUEST_METHOD']!='POST'){return true;}

        $url_parts = parse_url($_SERVER['HTTP_REFERER']);
        if(strpos($url_parts['host'],"miosito.it")===false){
                return false;
         }    
        return true;
    }
}

Includiamo questo file nel "post.php", ed istanziamo la classe Functions in modo da poter poi accedere ai metodi della classe.

<?php
include "functions.php";
$func=new Functions();

Bene, abbiamo finito il lavoro preliminare. Adesso occupiamoci di applicare i singoli metodi per proteggere il nostro sito web

Validazione e sanitizzazione dei dati in ingresso

Occorre controllare che i campi ricevuti in POST siano della tipologia attesa, quindi stringhe, numeri interi, numeri decimali, date, email,...
E se sono stringhe occorrerà fare un po di pulizia della stringa (cd "sanitizzazione").

Nel file "post.php", che indico di seguito, mi aspetto solo chiamate di tipo post, quindi se così non fosse, faccio un bell'exit.

Creiamo quindi un array $campi con i campi che mi aspetto di ricevere, e per ogni campo la sua tipologia: stringa, numero intero, numero decimale, ...

Passiamo questo array al metodo "sanitizePost"

<?php
if($_SERVER['REQUEST_METHOD']!='POST'){exit;}
$campi=[
	"marca"=>"string",
	"modello"=>"string",
	"disponibili"=>"integer",
	"prezzo"=>"decimal",
];
$func->sanitizePost($campi);

In base alla tipologia di dato che mi aspetto dovrò procedere con un controllo dati specifico per tipologia, utilizzando il metodo "sanitizePost" a cui passiamo l'array di campi.

Nel nostro esempio ci aspettiamo in post i parametri "marca", "modello", ....

Ovviamente potete integrare questo metodo per controllare altri campi come date, email, numeri positivi/negativi,.... e per verificare se un campo è obbligatorio, lascio a voi l'implementazione.

Se la verifica fallisce ho messo un "exit" nel metodo, ma voi ovviamente mettete il tipo di risposta che credete, ad esempio una risposta in json.

Vediamo adesso come opera il metodo "sanitizePost": per ogni campo che mi aspetto, ricevuto e valorizzato, viene verificato se è di tipo numerico o stringa: 

Se il campo è decimale o intero, per prima cosa verifico che sia numerico utilizzando is_numeric e poi verifico che effettivamente sia del tipo che mi aspetto utilizzando filter_var, strumento di PHP per la convalida e il filtraggio dei dati.

Per verificare se il valore ricevuto è un intero utilizzo 

filter_var($_POST[$key], FILTER_VALIDATE_INT)​

Mentre per veridicare se è un decimale

filter_var($_POST[$key], FILTER_VALIDATE_FLOAT,FILTER_FLAG_ALLOW_FRACTION )

Se invece mi aspetto una stringa, dopo aver verificato che sia effettivamente lo sia utilizzando is_string, procedo con un po di pulizia della stessa effettuando le seguenti operazioni

$_POST[$key] = str_replace('../', '', $_POST[$key]);
$_POST[$key] = htmlentities($_POST[$key] , ENT_QUOTES | ENT_DISALLOWED, 'UTF-8');
$_POST[$key] = strip_tags($_POST[$key]);
$_POST[$key] = trim($_POST[$key]);
  • Rimozione di eventuali "../" che sappiamo indicano un "torna al folder precedente" e potrebbe essere usato in modo malevolo.
  • Trasformazione dei caratteri speciali (doppi apici, apici singoli,..) in entità HTML utilizzando htmlentities.
  • Rimozione dei tag html ricevuti utilizzando strip_tags
  • Eliminazione degli spazi vuoti iniziali e finali attraverso un trim, che non fa mai male

Se le nostre verifiche falliscono lo script si fermerà con un exit o con la risposta che avete implementato.

L'esempio visto in PHP può e deve ovviamente essere ulteriormente sviluppato: ad esempio, non sempre si ha l'esigenza nelle POST di togliere i tag HTML perchè può capitare che nel mio form stia usando un editor HTML e che voglia salvare i tag.

Utilizzatelo quindi come base di partenza per i vostri sviluppi.

A fondo articolo ho inserito un link github da cui scaricare questo esempio, completo di una index.php con un form, ed una index.js che effettua una post usando ajax.

Verifica del referrer

Questa non è propriamente una tecnica di prevenzione da attacchi XSS, tuttavia la verifica del referrer, e cioè che una chiamata provenga dallo stesso server da cui è partita, è sempre bene implementarla per una prima scrematura su possibili tentativi di attacco.

Per fare questo utilizziamo il metodo "checkReferrer", già presente nella nostra classe, che verifica il valore della variabile superglobale $_SERVER['HTTP_REFERER'] e lo confronta con il nome del nostro dominio

Ad esempio, se il nostro dominio è www.miosito.it, la variabile $_SERVER['HTTP_REFERER'] deve contenere il nostro dominio.

Lo usiamo solo per le chiamate di tipo POST perchè in GET non tutti i browser inviano il referrer al server, quindi se la chiamata non è una POST la considero comunque "buona".

Se la verifica è superata il metodo restituisce "true", altrimenti "false".

public function checkReferrer(){
	if($_SERVER['REQUEST_METHOD']!='POST'){return true;}

	$url_parts = parse_url($_SERVER['HTTP_REFERER']);
	if(strpos($url_parts['host'],"miosito.it")===false){
			return false;
	 }    

	return true;
}

Implementiamolo nella nostra "post.php"

<?php
if($_SERVER['REQUEST_METHOD']!='POST'){exit;}

include "functions.php";
$func=new Functions();

if(!$func->checkReferrer()){exit;}

$campi=[
	"marca"=>"string",
	"modello"=>"string",
	"disponibili"=>"integer",
	"prezzo"=>"decimal",
];
$func->sanitizePost($campi);

Se "checkReferrer" risponde "false", facciamo un exit, senza avvisi... in fondo se il referrer non è verificato sicuramente è qualche utente malevolo per cui non dobbiamo dargli spiegazioni :-)

Il controllo del referrer, tuttavia, non è infallibile: esso infatti può essere manipolato, quindi deve essere considerato solo come uno dei metodi di prevenzione da attacchi esterni.

Per ultimare questo articolo, non dimenticate di implementare nel vostro sito un sistema che protegga da da attacchi CSRF, CROSS SITE REQUEST FORGERY, e per questo vi rimando ad un precedente articolo in cui ho spiegato in modo esaustivo come verificare che le richieste di tipo POST siano state inviate intenzionalmente dall'utente che sta navigando il sito.

Ed infine, per prevenire attachi di tipo SQL Injection, quando salvate i dati nel vostro database Mysql, utilizzate sempre PDO, prepared statements e placeholders, a cui dedichererò prossimamente un articolo.

Approfondimenti

Potrebbe interessarti

 
 
 
 
pay per script

Hai bisogno di uno script PHP personalizzato, di una particolare configurazione su Linux, di una gestione dei tuoi server Linux, o di una consulenza per il tuo progetto?

x

ATTENZIONE