문제에 접속하니 위와 같은 페이지가 나왔다.
아마 password를 맞추는 것이 문제인 것 같다.
하이퍼링크가 걸린 숫자들을 하나씩 눌러보자.
숫자를 누르면 no 파라미터를 통해 해당 숫자값이 전송되는 것을 확인 할 수 있었고 각각 위와 같은 결과가 나왔다.
마지막으로 3을 눌러보니 secret이라는 문구와 함께 속성이 id,no 이며 no=3의 id 값이 password라고 언급됐다.
아마 DB의 테이블의 속성이 id,no 두 가지이며 우리가 구해야하는 password는 no가 3인 레코드의 id값인듯 하다.
결론적으로 password인 no=3의 id값을 구하는게 이번 문제의 목표가 될 것이다.
no=3의 id값을 구하기 위해서는 blind sqli를 진행해야 하는데 사실 딱봐도 no 파라미터를 이용해야 할 것 같은 느낌이 들어서 이용해보기로 했다.
no 파라미터를 이용해 sqli를 하기 앞서 서버쪽 sql 쿼리를 예측해보았다.
if문을 이용하여 sqli를 진행할 수 있을 것 같아서 위와 같은 인젝션 코드를 작성해보았다. (i는 id값의 길이)
sqli가 정상적으로 되는지 테스트를 해보기 위해 i값을 5로 하여 전송해보았다.(apple의 길이가 5이기 때문이다.)
전송해보니 access denied 가 나왔다!
아마 no 파라미터 값 중 필터링에 걸리는 문자가 존재하는 것 같다. 하나씩 빼서 확인해보니 '=' 이 필터링에 걸리는 것을 확인했다.
그렇다면 '='를 대체할 함수를 찾아보아야 하는데 sql에는 like() 라는 함수가 존재하고 이것을 이용해 우회할 수 있다.
'='를 like()로 변경 후 전송해보니 내가 원하던대로 Apple이 나왔다! (참고로 i값을 5가 아닌 다른 값으로 넣으면 Apple이 안 뜬다.)
이번에는 i값을 6으로 전송하여 테스트를 한번 더 해보았다. (banana의 길이가 6자리이기 때문이다.)
????
예상과 다르게 아무값도 뜨지 않았다.... 예상대로면 이번에도 Apple이 떠야하는데......
뭔가 이상하다는 것을 느꼈고 혹시나 하는 마음에 1을 2로 바꿔보았다.
2로 값을 바꾸니 이번에는 Banana가 뜨면서 원하는 결과가 나왔다!
위의 결과들을 토대로 생각을 해보면 다음과 같은 결과를 유추할 수 있다.
※ DB에 존재하는 테이블
no id
1 Apple
2 Banana
3 ?????
DB의 테이블이 위와 같이 존재한다고 가정하고 no 파라미터를 통해 보낸값이 위 쿼리라고 가정하자.
length()를 통해 각 레코드의 id값의 길이를 no 1~3 까지 하나씩 가져와 길이가 6이 맞는지 비교 후 맞는 값이 존재하면
true값을 반환하여 if문은 최종적으로 2를 반환할 것이다.
근데 위에서 보았듯이 2인 경우에는 6자리의 값이 있는지 비교했을때 있다는 결과인 Banana가 출력값으로 나왔지만
1이였을 때는 Banana라는 6자리의 id가 존재함에도 불구하고 Apple이라는 출력값이 나오지 않았다.
왜 이러한 결과가 나올까??
id값의 길이가 6자리인 레코드. 즉, id가 Banana인 레코드가 존재하기 때문에 true를 반환할 텐데
이때, true시에 반환하는 값이 테이블의 넘버와 같은 경우에만 true값을 반환하기 때문이다! (같지않으면 false 값을 반환)
즉, 비교문에서 true로 성립된 레코드의 no가 2 인데 true일때 반환하는 값이 2로 같으면?? 2==2 임으로 최종적으로 2을 반환
비교문에서 true로 성립된 레코드의 no가 2 인데 true일때 반환하는 값이 1로 다르면?? 2!=1 임으로 최종적으로 99을 반환
따라서 결과가 위처럼 발생하기 때문에 우리의 목표인 no=3인 id를 구하기 위해서는 true로 반환되는 값에 3을 넣어줘야 값을 구할 수 있다!
이제 no=3인 id의 길이를 구해보자. 구해보니 11자리임을 알 수 있다.
id의 길이를 알았으니 이제 값을 구해보자.
값을 구하기 위해 substr()을 이용한 payload를 만들어보았다. (i는 id값에서 값을 구할 자리수 , j는 비교 값)
substr()로 반환되는 값은 16진수로 반환되기 때문에 like()의 인자로 hex값을 넣었다.
이제 payload를 이용해 값을 구해보자. (필자의 경우 python을 이용했다.)
값을 구해보니 %%%%%%%%%%%이 나왔다...? 뭔가 이상하여 %인 경우는 빼고 값을 구해보았다.
위와 같은 값이 나왔고 password로 입력해보았다.
클리어!!
● id 값을 구하기 위한 python 코드
from requests import *
s=session()
result=''
count=0
for i in range(1,12):
if count > 90: # 설정한 범위(33~127)내의 문자가 없는 경우 오류로 프로그램 종료
print('query error')
break
for j in range(33,127): # ascii 코드값 토대로 33~127은 문자,특수문자를 모두 포함한다.
url=f'https://webhacking.kr/challenge/web-09/?no=(if((substr(id,{i},1))like({hex(j)}),3,99))'
response=get(url=url)
count+=1
if response.text.find('Secret') != -1 and j!=37: # %가 답으로 뜨는 오류 때문에 %를 의미하는 37은 제외
result+=chr(j)
count=0
break
print(result.lower()) # 정답이 모두 소문자임으로 lower()
사실 위 코드로 result값을 구하면 값이 모두 대문자로 나오는데, substr()로 나온 문자가 대문자든 소문자든 구분하지않고 like(16진수)의 값과 같은 알파벳이면 참으로 인정되기 때문이다. ex) A == a , B == b로 취급
소문자가 아닌 대문자가 나오는 이유는 ascii 코드 값 순으로 오름차순으로 정렬하면 대문자가 소문자보다 먼저이기 때문이다.
모르겠으면 아스키코드표를 참고바란다.
정답은 소문자이기 때문에 마지막에 lower()를 해주었다. (사실 필자는 이거 때문에 삽질 좀 했다...ㅠㅠ)