Secuinside CTF Prequal 2014 - Simple login (web200) write up
I actually did not want to write a write-up for this challenge. But the only write-up I found so far used a brute-force attack against this challenge. So here is our (FluxFingers) write-up with a much more elegant way.
The task was to log in as admin to a website. The source could be downloaded and analyzed. If you looked at "index.php" you saw this:
<?
include "config.php";
include "common.php";
$common = new common;
if($common->islogin()){
if($common->isadmin()) $f = "Flag is : ".__FLAG__;
else $f = "Hello, Guest!";
//[...]
So obviously we have to pass the "islogin()" and isadmin()" function. Let us take a look at the common class:
<?
class common{
public function getidx($id){
$id = mysql_real_escape_string($id);
$info = mysql_fetch_array(mysql_query("select idx from member where id='".$id."'"));
return $info[0];
}
public function getpasswd($id){
$id = mysql_real_escape_string($id);
$info = mysql_fetch_array(mysql_query("select password from member where id='".$id."'"));
return $info[0];
}
public function islogin(){
if( preg_match("/[^0-9A-Za-z]/", $_COOKIE['user_name']) ){
exit("cannot be used Special character");
}
if( $_COOKIE['user_name'] == "admin" ) return 0;
$salt = file_get_contents("../../long_salt.txt");
if( hash('crc32',$salt.'|'.(int)$_COOKIE['login_time'].'|'.$_COOKIE['user_name']) == $_COOKIE['hash'] ){
return 1;
}
return 0;
}
public function autologin(){
}
public function isadmin(){
if( $this->getidx($_COOKIE['user_name']) == 1){
return 1;
}
return 0;
}
public function insertmember($id, $password){
$id = mysql_real_escape_string($id);
mysql_query("insert into member(id, password) values('".$id."', '".$password."')") or die();
return 1;
}
}
?>
We see some MySQL queries. Unfortunately for us, they are escaped. We have to pass the "isadmin()" function. So the function uses the user name from the cookie and gets the index from the MySQL DB. We assume that the index for the "admin" user is 1 (because of the function name).
Ok, in the "index.php" we have to pass also the "islogin()" function. So we see the username has to contain only alphanumeric values and it can not be "admin". Luckily for us, MySQL is case-insensitive. This means we can use "ADMIN" as a user name, pass the check in the "islogin()" function and still get the correct index from the MySQL DB.
Ok, the next obvious thing is the hash we have to give correctly. It uses "crc32" (best crypto hash ever). We know crc32 is linear with respect to XOR. So, we can sign up with an username with the same length as "ADMIN" (we used "qwert"). Log in with this username, change the username in our cookie to "ADMIN" and XOR the crc32 hash of "qwert XOR ADMIN" to the hash we send to the server. This should pass the hash check. So we tried "crc32(x) XOR crc32(y) == crc32(x XOR y)" but it failed in our tests. We searched for it on the internet and always found this formula (hell, even in some of our lecture notes this formula is given). But it does not work for us. After a while we found out that the formula has to be "crc32(x) XOR crc32(y) XOR crc32(z) == crc32(x XOR y XOR z)". With this formula the linearity works. But now we have three values to consider. We used 5 Null-bytes. This does not change the username value and for the hash we can XOR a crc32 hash of 5 Null-bytes to the hash.
To make sure, everything works we used php to create our exploit, because we do not know if php fucks something up with the crc32 hash. So here is our exploit:
<?php
// qwert is registered
$username = "qwert";
// PW: qwert123
// qwert ^ 03(;: = ADMIN <--- MySQL is not case-sensitiv
$blah = "03(;:";
$blahHash = hexdec(hash('crc32', $blah));
// original cookie value when logged in:
// Cookie: login_time=1401570086; user_name=qwert; hash=fbd7bc71
$correctHash = hexdec("fbd7bc71");
echo dechex($blahHash ^ $correctHash ^ hexdec(hash('crc32', "\x00\x00\x00\x00\x00")));
// cookie value for exploit:
// Cookie: login_time=1401570086; user_name=ADMIN; hash=4effb88a
?>
We used our new hash value and we got back:
ADMIN Logined
Flag is : fd602a942c1cd716963996cf96e87847
Let me conclude this write-up with some words to the CTF. I liked it very much. But the practice to open only some challenges and the time difference to our country sucked a lot. During the time we played the most, only three challenges were available for us (the others were already solved by us and the three not solved were afaik only solved by 2 or 3 teams in the end). Then when we slept, all the other new challenges were opened and when we got back, we had only 2-3 hours for this challenges until the end of the CTF. Nevertheless, it was a great CTF and I want to thank the organizers for their great work!
The task was to log in as admin to a website. The source could be downloaded and analyzed. If you looked at "index.php" you saw this:
<?
include "config.php";
include "common.php";
$common = new common;
if($common->islogin()){
if($common->isadmin()) $f = "Flag is : ".__FLAG__;
else $f = "Hello, Guest!";
//[...]
So obviously we have to pass the "islogin()" and isadmin()" function. Let us take a look at the common class:
<?
class common{
public function getidx($id){
$id = mysql_real_escape_string($id);
$info = mysql_fetch_array(mysql_query("select idx from member where id='".$id."'"));
return $info[0];
}
public function getpasswd($id){
$id = mysql_real_escape_string($id);
$info = mysql_fetch_array(mysql_query("select password from member where id='".$id."'"));
return $info[0];
}
public function islogin(){
if( preg_match("/[^0-9A-Za-z]/", $_COOKIE['user_name']) ){
exit("cannot be used Special character");
}
if( $_COOKIE['user_name'] == "admin" ) return 0;
$salt = file_get_contents("../../long_salt.txt");
if( hash('crc32',$salt.'|'.(int)$_COOKIE['login_time'].'|'.$_COOKIE['user_name']) == $_COOKIE['hash'] ){
return 1;
}
return 0;
}
public function autologin(){
}
public function isadmin(){
if( $this->getidx($_COOKIE['user_name']) == 1){
return 1;
}
return 0;
}
public function insertmember($id, $password){
$id = mysql_real_escape_string($id);
mysql_query("insert into member(id, password) values('".$id."', '".$password."')") or die();
return 1;
}
}
?>
We see some MySQL queries. Unfortunately for us, they are escaped. We have to pass the "isadmin()" function. So the function uses the user name from the cookie and gets the index from the MySQL DB. We assume that the index for the "admin" user is 1 (because of the function name).
Ok, in the "index.php" we have to pass also the "islogin()" function. So we see the username has to contain only alphanumeric values and it can not be "admin". Luckily for us, MySQL is case-insensitive. This means we can use "ADMIN" as a user name, pass the check in the "islogin()" function and still get the correct index from the MySQL DB.
Ok, the next obvious thing is the hash we have to give correctly. It uses "crc32" (best crypto hash ever). We know crc32 is linear with respect to XOR. So, we can sign up with an username with the same length as "ADMIN" (we used "qwert"). Log in with this username, change the username in our cookie to "ADMIN" and XOR the crc32 hash of "qwert XOR ADMIN" to the hash we send to the server. This should pass the hash check. So we tried "crc32(x) XOR crc32(y) == crc32(x XOR y)" but it failed in our tests. We searched for it on the internet and always found this formula (hell, even in some of our lecture notes this formula is given). But it does not work for us. After a while we found out that the formula has to be "crc32(x) XOR crc32(y) XOR crc32(z) == crc32(x XOR y XOR z)". With this formula the linearity works. But now we have three values to consider. We used 5 Null-bytes. This does not change the username value and for the hash we can XOR a crc32 hash of 5 Null-bytes to the hash.
To make sure, everything works we used php to create our exploit, because we do not know if php fucks something up with the crc32 hash. So here is our exploit:
<?php
// qwert is registered
$username = "qwert";
// PW: qwert123
// qwert ^ 03(;: = ADMIN <--- MySQL is not case-sensitiv
$blah = "03(;:";
$blahHash = hexdec(hash('crc32', $blah));
// original cookie value when logged in:
// Cookie: login_time=1401570086; user_name=qwert; hash=fbd7bc71
$correctHash = hexdec("fbd7bc71");
echo dechex($blahHash ^ $correctHash ^ hexdec(hash('crc32', "\x00\x00\x00\x00\x00")));
// cookie value for exploit:
// Cookie: login_time=1401570086; user_name=ADMIN; hash=4effb88a
?>
We used our new hash value and we got back:
ADMIN Logined
Flag is : fd602a942c1cd716963996cf96e87847
Let me conclude this write-up with some words to the CTF. I liked it very much. But the practice to open only some challenges and the time difference to our country sucked a lot. During the time we played the most, only three challenges were available for us (the others were already solved by us and the three not solved were afaik only solved by 2 or 3 teams in the end). Then when we slept, all the other new challenges were opened and when we got back, we had only 2-3 hours for this challenges until the end of the CTF. Nevertheless, it was a great CTF and I want to thank the organizers for their great work!
Trackbacks
The author does not allow comments to this entry
Comments
Display comments as Linear | Threaded