Webhacking.kr 5번 문제풀이

Hevton·2020년 9월 7일
0
post-custom-banner

5번같은 경우는, 이전 문제들과는 달리 명확히 타겟을 분석하여 흐름을 알고 푸는 문제라기 보다는.. 그냥 때려맞추는 문제인 것 같아서 의욕이 조금 떨어졌다. 뭐 모든 해킹이 그런거겠지만서도, 이 문제는 좀 개인적으로 어이가 없었다.. 끼워맞춘 느낌이다. 내가 내 스스로 못 푼 것도 있어서 분해서 그런걸 수도 있는데, 해답을 접하고도 어이가 없었다. 따라서 짧게 넘어가겠다.

5번에 들어가면 Login과 Join 버튼이 두 개있다. 조인 버튼은 누르면 alert 창으로 자바스크립트를 띄우는 기능을 갖고 있고, Login 버튼은 그냥 아이디 비밀번호 입력하는 로그인 창으로 이동시켜준다.

Join 버튼 막은 걸 보니 Join 창으로 이동하면 될 것 같아서, loign.php 대신 join.php를 쳐줬다. 그랬더니 나온 창의 소스는 아래와 같다.

<html>
<title>Challenge 5</title></head><body bgcolor=black><center>
<script>
l='a';ll='b';lll='c';llll='d';lllll='e';llllll='f';lllllll='g';llllllll='h';lllllllll='i';llllllllll='j';lllllllllll='k';llllllllllll='l';lllllllllllll='m';llllllllllllll='n';lllllllllllllll='o';llllllllllllllll='p';lllllllllllllllll='q';llllllllllllllllll='r';lllllllllllllllllll='s';llllllllllllllllllll='t';lllllllllllllllllllll='u';llllllllllllllllllllll='v';lllllllllllllllllllllll='w';llllllllllllllllllllllll='x';lllllllllllllllllllllllll='y';llllllllllllllllllllllllll='z';I='1';II='2';III='3';IIII='4';IIIII='5';IIIIII='6';IIIIIII='7';IIIIIIII='8';IIIIIIIII='9';IIIIIIIIII='0';li='.';ii='<';iii='>';lIllIllIllIllIllIllIllIllIllIl=lllllllllllllll+llllllllllll+llll+llllllllllllllllllllllllll+lllllllllllllll+lllllllllllll+ll+lllllllll+lllll;
lIIIIIIIIIIIIIIIIIIl=llll+lllllllllllllll+lll+lllllllllllllllllllll+lllllllllllll+lllll+llllllllllllll+llllllllllllllllllll+li+lll+lllllllllllllll+lllllllllllllll+lllllllllll+lllllllll+lllll;if(eval(lIIIIIIIIIIIIIIIIIIl).indexOf(lIllIllIllIllIllIllIllIllIllIl)==-1) {alert('bye');throw "stop";}if(eval(llll+lllllllllllllll+lll+lllllllllllllllllllll+lllllllllllll+lllll+llllllllllllll+llllllllllllllllllll+li+'U'+'R'+'L').indexOf(lllllllllllll+lllllllllllllll+llll+lllll+'='+I)==-1){alert('access_denied');throw "stop";}else{document.write('<font size=2 color=white>Join</font><p>');document.write('.<p>.<p>.<p>.<p>.<p>');document.write('<form method=post action='+llllllllll+lllllllllllllll+lllllllll+llllllllllllll+li+llllllllllllllll+llllllll+llllllllllllllll
+'>');document.write('<table border=1><tr><td><font color=gray>id</font></td><td><input type=text name='+lllllllll+llll+' maxlength=20></td></tr>');document.write('<tr><td><font color=gray>pass</font></td><td><input type=text name='+llllllllllllllll+lllllllllllllllllllllll+'></td></tr>');document.write('<tr align=center><td colspan=2><input type=submit></td></tr></form></table>');}
</script>
</body>
</html>

지저분하게 난독화되어있다. 크롬의 개발자도구를 이용해서 해독해봐도 되고, 일일히 눈으로 봐가며 해도 된다(이건 눈 빠질수도).
여튼 정리하면 url 인자에 mode=1이 있어야 하므로 ?를 통해서 붙여준다. -> join.php?mode=1
그리고 oldzombie라는 이름의 쿠키가 있어야 한대서 구글 확장프로그램 'EditThisCookie'를 사용해서 쿠키도 만들어준다(값은상관없음). 그러면 Join.php에 정상적으로 접속할 수 있다.

id와 비밀번호에 아무거나 넣으면 회원가입이 정상적으로 된다. 그러면 이걸 다시 Login 폼에 가서 입력하면 admin으로 로그인해야 한다고 한다. 그래서 다시 join에서 admin으로 회원가입하려고 하니까 이미 있는 id라고 말한다. 난 여기서 막혔다. 온갖 sql 인젝션을 시도했는데, 답이 안나와서 정말 헤매고 헤매고 헤매다 검색을 해봤는데 대부분의 사람들이 admin에 공백과 널문자를 넣고 회원가입을 시키고 이것으로 로그인을 하여 admin으로 로그인시켜서 문제를 풀었다는 사실 자체만 나오고, 난 이게 왜 이렇게 돌아가는지 이해가 안됐다..

일단 mysql db에서는 'admin' = 'admin ' 이라고 인식한다. 즉 공백이 있어도 같인 문자열이라고 인식한다. 단 이것도 해당 자료형이 CHAR일 때만 가능하다. CHAR는 문자열을 비교할때 두 문자열 크기가 다르면 공백을 채워서 비교하기 때문이다. (CHAR는 또한 받는 문자열 길이에 상관없이 일전에 정해진 크기만큼 공백을 채워서라도 저장하는 방식이다.)
CHAR(5)의 데이터 타입을 가진 컬럼이 존재 할 때, 'abc'와 'x' 두 데이터가 저장되게 된다면 아래와 같이 저장된다. (공백은 _로 표기하겠다)
'abc', 'x__' 이렇게 입력받은 문자열의 길이에 상관없이 항상 정해놓은 데이터 타입의 크기를 맞추려고 공백을 추가하여 저장하게 된다.
그리고 문자열을 비교할 때에도 이렇게 공백을 채워서 비교한다. 그리고 크기가 달라도 원래 CHAR에서는 문자열을 비교할 때 공백을 채워서 비교하는 방법을 사용한다. 공백 채우기 비교에서는 우선 짧은 쪽의 끝에 공백을 추가하여 2개의 데이터가 같은 길이가 되도록 한다. 그리고 앞에서부터 한 문자씩 비교한다. 그렇기 때문에 끝의 공백만 다른 문자열은 같다고 판단된다. (다만 데이터를 꺼낼 때는 공백 제거 false 설정이 없는 한 공백이 알아서 제거되어서 나온다) 하지만 이와달리 VARCHAR(5)의 경우는 아예 처음부터 '입력받은 문자열의 크기만큼만' 공간을 차지시킨다. 공백문자를 삽입하지 않는것이다.(참고로 CHAR과 VARCHAR의 공통점이 있다면, 두 데이터타입 모두 설정 크기보다 입력받은 문자열의 크기가 큰 경우는 뒤 문자를 짤라서 저장한다)

'admin' = 'admin '에 대한 자세한 내용은 엄청 똑똑하신 분이 잘 정리해주셨다..
https://woowabros.github.io/study/2018/02/26/mysql-char-comparison.html

이 부분까지는 어느정도 알고 있었다. 그치만 내가 사용하지 안한 이유는.. 일관성이 없지 않은가.. 이 취약점을 이용해서 'admin' = 'admin ' 이 되는 거라면 , 애초에 join 폼에서도 같은 문자열로 인식해서 이미 있는 아이디라고 회원가입이 안되어야 하는 것이 아닌가..
둘다 WHERE id='admin' 방식을 사용해서 SQL 쿼리를 보내고, 그 결과값으로 참 거짓을 판단하지 않겠는가.. 근데 이 문제에서는 이해가 안되지만 아마 굳이 join 에서는 php를 통한 문자열 비교를 사용하고 login에서는 쿼리를 통한 문자열 비교를 사용하나보다... php 자체에서 strcmp나 직접 비교를 통해 문자열을 비교하게 된다면 'admin' != 'admin ' 으로 인식되므로 회원가입은 되고, db에서는 CHAR의 비교방식을 사용해서 같은 문자열로 인식되는 것 같다. 뒤에 NULL 문자를 넣어도 안넣어도 크게 상관은 없겠지만, 넣은 경우는 공백을 id컬럼의 크기보다 더 크게끔 해서 넣어줘야 한다. (더 크게 넣는 경우 CHAR이던 VARCHAR이던 문자를 짤라버리고 저장합니다.)그래도 굳이 NULL문자를 넣는 것은 NULL은 문자열에서 문자의 끝을 나타내므로 php에서 비교할때 더욱 확실히 하는 방식이 되기도 할 것 같다. 대신 공백이 충분치 않으면 mysql에서 'admin' != 'admin \0'으로 인식되어 이 경우는 참이 아니게 될 것입니다. 널문자를 넣음으로써 문자열의 끝을 확실하게 표현해 줌으로써 php의 비교를 완벽히 우회할 수 있으나, 공백을 id 컬럼의 예상 크기보다 크게 넣지 않아버리면 문자열이 짤리지 않고, 널문자의 역할 그대로 문자열의 끝을 의미하기 되어 공백 하나하나까지 다 문자열의 값으로 비교하게 되어 거짓이 나오게 됩니다.
(만약 널문자를 충분히 넣으면 도중에 공백에서 짤리게 되어 널문자는 sql에서 컬럼과 비교할 때 들어가지 않겠죠). 저는 개인적으로 공백만 넣어서 풀기보다는 충분한 공백 뒤에 널문자를 넣어서 '문자열의 끝을 확실하게 표현'해주는게 공부에도 도움이 되고 확실한 방법이라고 생각합니다. 여튼 저는 Burp Suite를 이용해 admin%20%20%20%20%20%20%20%20%20%20%20%00로 인자를 넘겨 회원가입을 하고, 이를 로그인 창에 넣으면 로그인이 됩니다.

왜 Burp Suite를 이용해 공백과 널 문자를 저렇게 넘겨주냐면, 널문자를 넣어줘야 했기 때문에도 그렇고 Burp Suite로 데이터를 잡았다가 보내는게 그냥 편하기도 해서 그렇습니다. 널문자는 입력창에 \0 넣어주면 인코딩된 %00값이 나올 줄 알았는데.. \의 인코딩값인 %5C와 0이 합쳐서 나오더라구요. (그리고 Brup Suite에서 직접 \0로 넘겨주면 \0로 돌아오고.. 이건 SQL 측에서의 필터링인지 원래 이런건진 모르겠습니다.) 제가 널문자를 인코딩 없이 넣을 줄을 모르나 봅니다.. 그치만 일전에 입력창에 이것저것 넣어보면서 Brup Suite에서 값을 확인했을때 입력한 값이 url encoding이 되어 잡히는 것을 확인했습니다. 데이터가 url encoding이 되어서 서버측으로 보내지는 것이죠. 그래서 Brup Suite를 이용해 공백의 인코딩 값인 %20와 널문자의 인코딩값인 %00을 넣어줬습니다. 데이터를 넘길 때 URL encoding을 실시하므로, 서버에서 데이터를 받을 때에도 URL decoding을 실시하겠구나 생각해서 Brup Suite에서 url encoding이 된 %20과 %00으로 넘겨준 것입니다. 만약 직접 입력창에 %20%00을 넘겨준다면 이 문자가 다시 또 URL encoding이 되어서 엉뚱한 문자가 될것입니다. 여튼 넘겨주면

문제가 풀렸다고 뜹니다. 고생하셨습니다. 제가 잘하는 게 아니고 초보라서 분명히 틀린 내용이 있을거라고 생각합니다. 제 지식선에서 이해가 되지 않은 부분들로 인해 표현한 것이 좋게 보이시지 않을 분들에게 죄송합니다. 모두 제가 멍청하기 때문입니다. 문제가 안풀려서 피운 땡깡과 화풀이라고 귀엽게 봐주시면 감사드리겠습니다. 열심히 공부하겠습니다. 감사합니다.

profile
놀만큼 놀았다.
post-custom-banner

0개의 댓글