Meglio insicuri che erroneamente protetti

Oggi mi sono trovato a testare un form di ricerca per valutare l’eventuale presenza di vettori d’attacco. Il test era di tipo blackbox, quindi non avevo accesso ai sorgenti del file PHP, che chiameremo “cerca.php“, ma solo al file html, che chiameremo “trova.html“.

Dopo qualche test per appurare se le query fossero correttamente sanitizzate, ho iniziato ad inserire qualche tag html con lo scopo di trovare una XSS Riflessa. Da subito ho notato che i soli tag non venivano filtrati in modo corretto, infatti inserendo un semplice veniva lasciato passare da cerca.php e mostrato in trova.html. Così ho provato a inserire qualche semplice payload per vedere se si poteva effettivamente exploitare, ma all’inserimento di un <img src=c onerror=alert(1)> mi sono trovato <img srcc on<x>erroralert(1)>.

Da questo ho dedotto che il carattere = veniva rimosso e che gli eventi javascript on[qualcosa] venivano spezzati da un tag subito dopo l’on.

Così ho pensato di utilizzare un payload privo di caratteri “=” e privo di eventi on[qualcosa]: <script>alert(1)</script>. Anche questo tentativo purtroppo non ha avuto successo, infatti su trova.html ritornava <sc<x>ript>alert(1)<sc<x>ript>. Quindi il solito tag per spezzare le stringhe pericolose (in questo caso script).

Dopo di che ho cercato di capire quale fosse la gerarchia delle replace, ovvero in che ordine venissero fatti i controlli sull’input e, con queste informazioni alla mano, ho iniziato a pensare a come poteva essere strutturato il codice PHP in cerca.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

if (isset($_GET['keyword'])){ //controllo se la keyword via GET è stata settata
	//salvo la get in una variabile che verrà pulita
	$keyword = $_GET['keyword'];
	
	//cerco tutti i tag html e gli eventi javascript
	preg_match_all("/<(\/)?([\w\<\>\=\/]+)(\s+)?((href=)|(src=)|([on](\w+=)))?(\")?(')?(\w+)?(\()?(\w+)?(\))?(\")?(')?(\/)?>/",$keyword,$trovati);
	
	//ciclo i risultati della regular	
	for ($i=0;$i<count($trovati[0]);$i++) {
		
		//salvo la singola stringa per la pulizia
		$pulita = $trovati[0][$i];
		
		//contiene script?
		if (strstr(strtolower($pulita),"script")) {
			//metto il tag <x> all'interno di script e salvo la stringa "pulita"
			$pulita = str_replace("script","sc<x>ript",$pulita);
		}
		//sostituisco la stringa non pulita con quella pulita all'interno di keyword (che sarebbe il risultato finale)
		$keyword = str_replace($trovati[0][$i], $pulita, $keyword);
		
		//contiene on?
		if (strstr(strtolower($pulita),"on")) {
			//metto il tag <x> dopo on e salvo la stringa "pulita"
			$pulita = str_replace("on","on<x>",$pulita);
		}
		//sostituisco la stringa non pulita con quella pulita all'interno di keyword (che sarebbe il risultato finale)
		$keyword = str_replace($trovati[0][$i], $pulita, $keyword);
		
		//contiene =?
		if (strstr(strtolower($pulita),"=")) {
			//elimino il carattere = e salvo la stringa "pulita"
			$pulita = str_replace("=","",$pulita);
		}
		//sostituisco la stringa non pulita con quella pulita all'interno di keyword (che sarebbe il risultato finale)
		$keyword = str_replace($trovati[0][$i], $pulita, $keyword);
	}
	
	//scrivo a schermo il risultato finale "pulito"
	echo $keyword;
	
	
}

?>

Dal codice si evince che per ogni tag potenzialmente dannoso per prima cosa viene controllata la presenza della stringa script e, se presente, viene sanitizzata con il tag ; successivamente viene controllata la presenza di un evento javascript on[qualcosa] e, se presente, viene aggiungo nuovamente il tag ; infine, se presente il carattere uguale, viene semplicemente rimosso.

Grazie a tutte queste informazioni risulta estremamente semplice exploitare questa XSS, infatti è sufficiente passare un payload di questo tipo <sc=ript>alert("Shielder")</sc=ript> il quale verrà controllato con questo iter:

  • contiene la stringa “script”? No;
  • contiene on[qualcosa]? No;
  • contiene dei caratteri =? Sì, allora li elimino.

Il risultato della pulizia di cerca.php che viene mostrato in trova.html è <script>alert("Shielder")</script>

Tornando ora al fulcro di questo articolo, vorrei focalizzare l’attenzione sul danno aggiuntivo che l’erroneo codice di sanitizzazione ha generato. Molti browser hanno un sistema di protezione nativo per le XSS che viene attivato di default o da un header nella richiesta HTTP, tale funzionalità permette di bloccare tutta una serie di richieste che contengono comuni payload javascript, come il nostro <script>alert("Shielder")</script>, il quale non sarebbe stato eseguito in una situazione ordinaria. Tuttavia in questo determinato caso il nostro payload era <sc=ript>alert("Shielder")</sc=ript>, il quale non risulta affatto essere un reale payload javascript agli occhi del browser, ma dopo la “pulizia” da parte di cerca.php risulta eseguibile.

Data quest’analisi possiamo affermare che sarebbe stato meglio essere insicuri che erroneamente protetti, in quanto la parvente protezione realizzata dal programmatore non ha fatto che aumentare i soggetti potenzialmente a rischio.

3 min

Data

13 maggio 2015

Autore

smaury

Sono Abdel Adim Oisfi più conosciuto come smaury.
Lavoro: CEO, Security Researcher, Penetration Tester in Shielder.
Passioni: Hacking, autostop, tuffi e ginocchia sbucciate.