webhacking/sql, sql injection

wargame.kr counting_query

qkqhxla1 2014. 10. 15. 00:06

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)."','guest@nothing.null',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']);
 
$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'"));

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

?>


주요 부분. 힌트는 에러기반 인젝션이라는게 힌트이고, 들어왔을때 입력값은 id(내 아이피주소), pw, type


가 있다. id와 pw, type는 처음 추출할때 바로 mysql_real_escape_string함수를 쓰므로 '등의 특수문자


는 사용이 불가능하다. 또 브루트포싱하지말라고 sleep(2)초 뒤에 써있으니 블라인드 인젝션도


아닐것이다. 그다음줄은 진짜 내 아이피와 접속한 id를 비교해서 틀리면 틀리다고 하고 종료된다.


id부분에는 변경은 불가능하다는 소리이다. 에러체크하는부분이 나오고, 그 아랫줄은 


$row=mysql_fetch_array(mysql_query("select count(*) from login_count where id='$id'"));


내가 얼만큼 요청을 보냈는지 확인하는 부분이다. 이걸 체크해서 4번 이상 요청을 보냈으면


pw_change($id); 로 비밀번호를 변경한다. 비밀번호를 변경하는 pw_change(); 함수를 분석해보면


uniqid()함수를 쓰는데 이건 시간을 마이크로단위로 분해해서 한가지 값만 나오게 한다고한다.


여기에도 취약점은 없는듯... 그다음은 count_init($id);로 카운트를 초기화한다. 코드를 살펴보면


단순히 내 카운트를 지우는 역할이다. 이런식으로 로그인 카운트를 초기화하고 비밀번호도 초기화


한다음 아랫줄에 t_table($id);함수가 있는데 살펴보면 나만을 위한 임시 테이블을 만든다.


t_user라는 이름의 임시 테이블이며, 바로 아랫라인의 preg_match("/all_user_accounts/i",$type)로 글로벌


테이블은 공략을 하지 못하게 해놓았다. (눈치가 빠르면 이부분에서 preg_match로 뭔가를 


필터링한다는 사실만으로 type부분이 공격 포인트라는걸 알아낸다.) 


아랫줄의 counting($id);함수는 내가 요청을 보낸 횟수를 늘리는데, 코드게이트 2014의 web 500점짜리


문제풀이처럼 여러개의 세션을 만들어놓고 공격하는걸 방지하기위해서인것 같다. 무조건 요청 하나당


서버에서 카운트 하나를 늘려버린다. 카운트를 늘리는 방법도 안되는듯...(삽질 해봤는데 안됨.)


그리고 그 아래가 공격 포인트인데. 


if(false === $result=mysql_query("select * from t_user where user_id='$id' and ps='$ps' and type=$type"))


이다. 눈치빠르면 위에서 필터링한다는 사실만으로 저기가 공격포인트라는걸 알수도 있지만,


처음에 id와 ps, type등을 빼올때 mysql_real_escape_string함수로 변환을 하는데 그리되면


우리가 입력된 부분에서 공격을 하려면 홑따옴표로 안둘러쌓여있는 부분을 찾는수밖에 없다.(다른 


취약점이 없는이상) 그 부분이 type부분. 여기에 공격을 하면 되는데.


공격해서 값을 뽑아와야 할 부분은 t_user테이블이다. 맨 아래의 쿼리문


$row=mysql_fetch_array(mysql_query("select ps from t_user where user_id='$id' and ps='$ps'")); 


통해서 t_user에는 user_id컬럼과 ps컬럼이 있다는걸 알수 있는데 id는 고정이니 ps컬럼을 밝혀내면


키값이 나온다. 에러 기반인데 브루트포싱을 하지 말라고 했으므로 에러 기반으로 짰을때


그냥 바로 나오는 에러기반 인젝션일것이다.(union을 사용했을때처럼.) 거기다가 코드를 보면


mysql_error()함수로 바로 에러를 출력해준다. 바로 값이 나오는 에러 기반은 ...



1. select 1 from dual where 1=1 and ExtractValue(1,concat(0x01,version()));


2. select 1 from dual where 1=1 and UpdateXML(1,concat(0x01,version()),1);


3. select 1 from dual where 1=1 and row(1,1)>(select count(*),concat(version(),floor(rand(0)*2)) x from (select 1 union select 2 union select 3)a group by x limit 1);


그러면 그냥 출력되도록.. 이런식으로..




뒷부분 5678이 제대로 나온다. 그런데 문제는 에러기반으로 값을 뽑아올때에 테이블네임이다. 


prob는 내가 create table prob로 만든 제대로 있는 테이블이고, 우리가 공격해야하는 t_user는


임시 테이블이다. 임시 테이블은 내부에서 다시 서브쿼리로 본인을 호출하면 무조건


에러가 뜬다. (별 난리, 삽질 다해봤고 스택오버플로우에서 확인하니 안된다고 함. 


can't reopen 에러가 발생.)




일반 테이블이면 되는 쿼리인데 임시 테이블이라 저 쿼리가


작동되지 않는다. 결론적으로 글로벌 테이블은 preg_match로 막혀있고, 임시 테이블마저 서브쿼리로


못부르니 맨처음에는 테이블 이름을 우회할 목적으로 이것저것 다 해봤는데 되지 않았다.


삽질의결과 그냥 select ps from prob같은 쿼리에서 테이블 이름 우회방법은 없다는걸 깨달았다.


select ps from (select concat('t_','user')) 이런짓도 해봤는데 문법상 되기는 하지만


원하는대로 안나옴;; 관리자님에게 물어보니 애초에 쿼리문에 t_user라는 테이블이름이


있으니까 서브쿼리로 불러오지 말고 그냥 ps를 쓰라고 답변을주셨다. 


그래서 진짜로 그냥 해봤더니 됨.;


2 or row(1,1)>(select count(*),concat(ps,floor(rand(0)*2)) x from (select 1 union select 2 union select 3)a group by x limit 1)


에러기반 인젝션에 대해 잘 이해못하는 부분이 있는듯 싶다.... 


다른방법인 ExtractValue나 UpdateXML로도 어떻게 될거같긴 한데 시험기간이라 삽집을 할 시간이 


없다.시험기간이 끝나고 삽질의 결과를 또 올리도록 하겠습니다...

'webhacking > sql, sql injection' 카테고리의 다른 글

webhacking.kr 22  (0) 2014.10.23
webhacking.kr 21  (0) 2014.10.23
wargame.kr q&a  (0) 2014.10.06
wargame.kr ip_log_table  (0) 2014.10.06
wargame.kr web_chatting  (0) 2014.10.06