이 글의 목차

크로스 사이트 스크립팅(XSS)의 원리와 방어

주의 사항

  1. 교육 목적으로만 이용해 주세요.
  2. 무단 침입, 데이터 유출, 개인 정보 침해 등 불법적인 활동은 심각한 법적 결과를 초래할 수 있습니다.
  3. 개인적인 테스트 환경을 구축해서 실습하시길 바랍니다.

1. 미리 알아두어야 할 바탕 지식

이 취약점을 제대로 이해하려면 웹 브라우저가 정보를 보호하는 기본 규칙을 먼저 알아야 합니다.

브라우저의 기본 규칙: 동일 출처 정책 (SOP, Same Origin Policy)

우리가 쓰는 인터넷 브라우저는 보안을 지키려고 한 가지 엄격한 규칙을 따릅니다. 바로 **동일 출처 정책(SOP)**입니다.

이 규칙이 하는 일: 서로 다른 주소(도메인)에서 가져온 정보나 자바스크립트는 브라우저 안에서 서로 함부로 넘나들거나 간섭할 수 없습니다.

예를 들면: 내가 은행.com에 로그인한 상태에서 해커가 만든 낚시사이트.net을 방문하더라도, 해커 사이트에서 실행되는 스크립트는 은행.com에 저장된 내 로그인 정보(쿠키나 세션 토큰)를 훔쳐 갈 수 없습니다. 브라우저가 주소가 다르다는 이유로 두 사이트를 완벽히 분리해 차단하기 때문입니다.

그렇다면 해커는 이 규칙을 어떻게 뚫을까요?

해커가 내 은행 정보를 훔치려면, 해커 사이트가 아니라 은행.com 자체에서 악성 코드가 실행되도록 만들어야 합니다. 이렇게 주소가 같은 진짜 사이트 안으로 악성 코드를 몰래 끼워 넣어 실행되게 만드는 공격이 바로 **크로스 사이트 스크립팅(XSS)**입니다.

이 공격은 웹 서버를 망가뜨리는 공격이 아닙니다. 서버가 주는 진짜 웹페이지에 악성 스크립트를 숨겨서, 결국 그 사이트를 이용하는 진짜 사용자를 공격하는 방법입니다.


2. XSS 공격이 일어나는 흐름

공격자가 사용자의 로그인 정보(쿠키)를 어떻게 빼앗아 가는지 보여주는 흐름입니다.

[해커]                                     [피해 사용자]
  │                                            │
  │ (1) 진짜 사이트 주소 뒤에                  │
  │     악성 스크립트를 붙인 링크를 보냄       │
  ├───────────────────────────────────────────>│
  │                                            │ (2) 진짜 사이트 주소인 것을 확인하고
  │                                            │     안심하며 링크를 누름
  │                                            │
  │                                [진짜 웹사이트]
  │                                      │
  │ (3) 악성 스크립트가 포함된 요청을 보냄│
  │ <────────────────────────────────────┤
  │                                      │ (4) 유효성 검사를 안 하고 그대로
  │                                      │     악성 스크립트를 화면에 얹어 보냄
  │                                      ├────> [사용자 브라우저]
  │                                                 │
  │                                                 │ (5) 브라우저가 스크립트를 실행하여
  │ (6) 사용자의 로그인 정보(세션 쿠키)를 전송 받음 │     내부 로그인 정보(쿠키)를 해커에게 보냄
  │<────────────────────────────────────────────────┤

3. XSS의 세 가지 유형

이 취약점은 악성 스크립트가 사용자에게 어떻게 전달되느냐에 따라 크게 세 가지로 나뉩니다.

① 반사형 (Reflected XSS)

어떻게 작동하나요?

내가 검색창에 입력한 글자나 주소창 뒤에 붙여 보낸 값(파라미터)이, 화면에 그대로 다시 나타날 때(반사될 때) 일어납니다.

실제 상황 예시:

어떤 인터넷 은행 사이트가 주소창 뒤에 ?lang=ko를 붙여 언어를 설정한다고 가정해 봅니다. 이 사이트가 들어온 값을 제대로 검사하지 않으면, 해커는 ?lang=<script>악성코드</script>처럼 글자 대신 스크립트를 통째로 주소 뒤에 붙여 사용자에게 보냅니다. 사용자가 이 링크를 누르면, 웹사이트는 주소에 써진 악성 스크립트를 그대로 화면에 실어 보내고, 사용자 브라우저가 이를 실행하게 됩니다.

② 저장형 (Stored XSS)

어떻게 작동하나요?

공격자가 보낸 악성 스크립트가 웹사이트 데이터베이스(DB)에 그대로 저장되어 계속 살아남는 유형입니다.

실제 상황 예시:

많은 사람이 방문하는 쇼핑몰 후기 게시판에 해커가 댓글로 <script>악성코드</script>를 적어 올립니다. 사이트가 이 댓글을 검사하지 않고 데이터베이스에 그대로 저장하면, 이후 이 글을 읽으러 들어오는 모든 사람의 브라우저에서 해커의 악성 코드가 자동으로 실행됩니다. 피싱 링크를 눌러 유도할 필요가 없어 반사형보다 훨씬 위험하며, 순식간에 수많은 사람의 계정을 탈취할 수 있습니다.

실제 사례: 2005년 소셜 네트워크 사이트 마이스페이스에서 한 사용자가 프로필에 이 취약점을 이용한 스크립트를 올려, 방문자들을 자동으로 친구 추가하고 악성 코드를 계속 복제하여 퍼뜨린 ‘새미 웜’ 사건이 있었습니다.

③ DOM 기반 (DOM-based XSS)

어떻게 작동하나요?

서버가 응답할 때 이미 코드가 섞여서 들어오는 위 두 방식과 다릅니다. 이 방식은 서버와 상관없이, 브라우저가 화면을 그리는 과정(DOM 환경)을 자바스크립트로 동적으로 바꾸는 틈을 타서 발생합니다.

실제 상황 예시:

웹페이지 안에 있는 자바스크립트가 주소창의 값을 읽어와 화면에 출력하는 함수(innerHTML 등)를 쓸 때 일어납니다. 서버를 거치지 않고 사용자 브라우저 안에서만 코드가 작동하므로 찾아내기가 더 까다롭습니다.


4. 취약점 진단과 공격 방식 (Exploit)

보안 진단을 하거나 공격 가능성을 확인할 때는 보통 다음과 같은 차례로 진행합니다.

① 입력한 값이 다시 나타나는 곳(엔드포인트) 찾기

주소창 뒤의 값이나 검색창, 입력 상자 중에서 내가 써넣은 글자가 그대로 화면에 다시 나오는 곳을 먼저 찾습니다. 이때 탐지 장치에 걸리지 않도록 TEST12564 같은 무작위 영문과 숫자로 구성한 문자열을 넣어봅니다.

② 문맥(Context) 파악하기

내가 넣은 글자가 HTML 태그 사이에 들어가는지, 입력 창 안쪽의 속성(value="...")에 들어가는지, 아니면 기존 자바스크립트 변수 안에 들어가는지 확인합니다.

③ 알맞은 공격 코드 짜기

입력 창 속성 안에 들어간다면, 큰따옴표와 꺾쇠를 써서 문맥을 깨뜨리고 탈출해야 합니다:

"> <script>alert(1)</script>

자바스크립트 변수 안에 들어간다면, 작은따옴표와 세미콜론으로 기존 명령을 끝맺고 내 코드를 덧붙여야 합니다:

'; alert(1); //

태그나 특정 키워드를 막는 보안 장치가 있다면, 우회 방법을 씁니다. 예를 들어 따옴표를 못 쓰게 막아두었다면 글자를 아스키코드 숫자로 변환하는 String.fromCharCode() 함수를 써서 보안 장치를 우회할 수 있습니다.


5. XSS 취약점을 막는 방법

이 취약점을 막는 가장 확실하고 공식적인 대책은 화면에 값을 보여줄 때 코드로 해석되지 않도록 처리하는 것입니다.

① 출력값 부호화 (Output Encoding)

가장 기본적이고 중요한 방어책입니다. 사용자가 입력한 문자 중에서 코드로 오해받을 수 있는 기호들을 일반 문자로 바꾸어 출력하는 방법입니다.

  • < 기호는 &lt;로 바꾸고, > 기호는 &gt;로 바꾸어 줍니다.

이렇게 바꾸면 브라우저는 이를 스크립트 명령어로 인식하지 않고, 화면에 그저 글자 그대로 보여주기만 하므로 안전합니다.

직접 이 규칙을 짜기보다는, 검증되고 널리 쓰이는 안전한 보안 라이브러리(OWASP 권장 라이브러리 등)를 가져다 쓰는 편이 실수를 줄이는 방법입니다.

② 들어오는 값 검사하기 (입력값 필터링 및 유효성 검증)

데이터가 들어오는 즉시 안전한지 확인하는 심층 방어 방법입니다.

예를 들어 나이를 입력받는 상자라면 오직 숫자만 들어오도록 서버에서 걸러내야 합니다. 한글이나 영문, 특수문자가 들어오면 즉시 거부합니다.

금지할 단어를 지정하는 방법(블랙리스트)보다는, 허용할 값만 지정해 두는 방법(화이트리스트)을 쓰는 쪽이 훨씬 튼튼합니다.

③ 안전한 HTTP 응답 헤더와 콘텐츠 보안 정책 (CSP) 활용하기

서버가 브라우저로 화면을 보낼 때, 브라우저가 지켜야 할 행동 지침을 헤더에 실어 보냅니다.

콘텐츠 보안 정책 (CSP, Content Security Policy):

웹 서버가 브라우저에게 “우리 사이트 안에서 실행되는 스크립트는 오직 지정한 안전한 주소에서 가져온 것만 실행하라"고 강제하는 브라우저 보안 장치입니다. 해커가 악성 스크립트를 억지로 끼워 넣더라도, 지정하지 않은 낯선 주소에서 가져온 스크립트라면 브라우저가 실행 자체를 차단하므로 훌륭한 최후의 방어선이 됩니다.

CSP 헤더 예시:

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.example.com

해석: 기본 리소스와 스크립트는 자기 도메인('self')과 지정한 CDN에서만 허용하고, 그 외 출처의 스크립트는 브라우저가 실행하지 않습니다.