갈수록 문제가 어려워진다.

주어지는 소스코드

<?php
 if (isset($_GET['view-source'])) {
     show_source(__FILE__);
     exit();
 }
 include("./inc.php"); // database connect.
 include("../lib.php"); // include for auth_code function.

 ini_set('display_errors',false);

 function err($str){ die("<script>alert(\\"$str\\");window.location.href='./';</script>"); }
 function uniq($data){ return md5($data.uniqid());}
 function make_id($id){ return mysql_query("insert into all_user_accounts values (null,'$id','".uniq($id)."','[email protected]',2)");}
 function counting($id){ return mysql_query("insert into login_count values (null,'$id','".time()."')");}
 function pw_change($id) { return mysql_query("update all_user_accounts set ps='".uniq($id)."' where user_id='$id'"); }
 function count_init($id) { return mysql_query("delete from login_count where id='$id'"); }
 function t_table($id) { return mysql_query("create temporary table t_user as select * from all_user_accounts where user_id='$id'"); };

 if(empty($_POST['id']) || empty($_POST['pw']) || empty($_POST['type'])){
  err("Parameter Error :: missing");
 }

 $id=mysql_real_escape_string($_POST['id']);
 $ps=mysql_real_escape_string($_POST['pw']);
 $type=mysql_real_escape_string($_POST['type']); // \\x00, \\n, \\r, \\, ', " and \\x1a.
 $ip=$_SERVER['REMOTE_ADDR'];

 sleep(2); // not Bruteforcing!!

 if($id!=$ip){
  err("SECURITY : u can access with allotted id only");
 }

 $row=mysql_fetch_array(mysql_query("select 1 from all_user_accounts where user_id='$id'"));
 if($row[0]!=1){
  if(false === make_id($id)){
   err("DB Error :: create user error");
  }
 }
 
 $row=mysql_fetch_array(mysql_query("select count(*) from login_count where id='$id'"));
 $log_count = (int)$row[0];
 if($log_count >= 4){
  pw_change($id);
  count_init($id);
  err("SECURITY : bruteforcing detected - password is changed");
 }
 
 t_table($id); // don`t access the other account

 if(preg_match("/all_user_accounts/i",$type)){
  err("SECURITY : don`t access the other account");
 }

 counting($id); // limiting number of query

 if(false === $result=mysql_query("select * from t_user where user_id='$id' and ps='$ps' and type=$type")){
  err("DB Error :: ".mysql_error());
 }

 $row=mysql_fetch_array($result);
 
 if(empty($row['user_id'])){
  err("Login Error :: not found your `user_id` or `password`");
 }

 echo "welcome <b>$id</b> !! (Login count = $log_count)";
 
 $row=mysql_fetch_array(mysql_query("select ps from t_user where user_id='$id' and ps='$ps'"));

 //patched 04.22.2015
 if (empty($ps)) err("DB Error :: data not found..");

 if($row['ps']==$ps){ echo "<h2>wow! auth key is : ".auth_code("counting query")."</h2>"; }

?>

id, pw, type은 모두 mysql_real_escape_string()에 둘러싸여 있다. 아래쪽 쿼리를 보면 id와 pw는 모두 싱글쿼리(')에 갇혀있기 때문에 사용이 불가하다. 따라서 type을 집중 공략해야 하는 문제이다.

특히 id는 ip값으로 자동 설정되는데 $_SERVER['REMOTE_ADDR'] 값은 서버에서 받아들이는 내 ip 값으로 임의의 값으로 위조가 불가능하기 때문에 조작이 불가능하다.


이 문제에서 빡치는 요소가 몇 가지 있는데,

  1. log_count

    브루트포스를 방지하기 위해 동일한 id에서의 접속이 4번(?) 이상 되면, 비밀번호를 바꿔버린다. 만약 blind sql injection 기법을 사용할 경우에 4번 만에 password를 알아내라는 이야기인데, 사실상 불가능하다.

  2. temporary table

    select ps from t_user과 같이 사용하면 DB Error :: Can't reopen table: 't_user' 이라는 에러가 발생한다.

    temporary 테이블은 말 그대로 임시 테이블인데, session이 종료되거나 connection이 끊길 경우에 자동으로 reset 된다고 한다. 그와는 별개로 한 개의 쿼리에서 2번 호출하지는 못하는 것 같다.

  3. 필터링

    all_user_accounts 필터링..

    t_user은 임시테이블이라 조회도 못하는데, all_user_accounts는 필터링되어있다.

다행히 문제 진입 부에 Error based sql injection이라는 힌트를 주고 있다. 그 말은 즉, 에러메시지를 통해 ps의 값을 끌어내라는 뜻이다. 소스코드에서도 이를 확인할 수 있다. err("DB Error :: ".mysql_error());


error based sql injection

출처 : http://repository.root-me.org/Exploitation - Web/EN - FAST blind SQL Injection.pdf