크로스 사이트 스크립팅(XSS): 공격 벡터와 방어
XSS가 여전히 웹 애플리케이션을 괴롭히는 이유
크로스 사이트 스크립팅(XSS)은 가장 흔한 웹 취약점 중 하나로 남아 있습니다. 널리 알려져 있음에도 불구하고 OWASP Top 10에 꾸준히 등장합니다. 핵심 문제는 애플리케이션이 사용자 입력을 신뢰하고 적절한 처리 없이 렌더링한다는 점입니다. 공격자는 피해자의 브라우저에서 실행되는 악성 스크립트를 주입하여 세션 하이재킹, 데이터 탈취, 변조를 일으킵니다.
이 글에서는 XSS 공격 벡터를 분석하고 오늘 바로 구현할 수 있는 실용적인 방어책을 제공합니다.
XSS란 무엇이며 어떻게 작동하나?
XSS는 애플리케이션이 검증이나 인코딩 없이 신뢰할 수 없는 데이터를 웹 페이지에 포함할 때 발생합니다. 그러면 브라우저는 주입된 스크립트를 합법적인 사이트의 일부인 것처럼 실행합니다. 스크립트가 취약한 사이트의 컨텍스트에서 실행되므로 쿠키, 로컬 스토리지에 접근하고 사용자를 대신해 요청을 보낼 수 있습니다.
검색어를 반사하는 간단한 검색 페이지를 생각해 보세요:
<?php echo 'You searched for: ' . $_GET['q']; ?>
공격자가 search.php?q=<script>alert('XSS')</script>와 같은 URL을 만들면 피해자가 링크를 방문할 때 스크립트가 실행됩니다.
XSS의 세 가지 주요 유형
1. 반사형 XSS
악성 스크립트가 요청(예: URL 매개변수)의 일부이며 응답에 즉시 반사됩니다. 피해자는 링크를 클릭하거나 양식을 제출하도록 유도되어야 합니다. 이는 종종 피싱 캠페인에 사용됩니다.
2. 저장형 XSS
스크립트가 서버(예: 데이터베이스, 댓글 필드, 사용자 프로필)에 영구적으로 저장됩니다. 영향을 받는 페이지를 방문하는 모든 방문자가 스크립트를 실행합니다. 저장형 XSS는 페이지를 보는 것 외에 상호작용이 필요 없기 때문에 더 위험합니다.
3. DOM 기반 XSS
취약점은 신뢰할 수 없는 소스(예: location.hash)에서 데이터를 읽고 안전한 처리 없이 DOM에 쓰는 클라이언트 측 JavaScript에 존재합니다. 서버는 악성 페이로드를 전혀 보지 못할 수 있습니다.
document.getElementById('output').innerHTML = location.hash.substring(1);
해시에 <img src=x>가 포함되어 있으면 스크립트가 실행됩니다.
일반적인 공격 벡터
- HTML에서 이스케이프되지 않은 사용자 입력: HTML 콘텐츠, 속성 또는 JavaScript에 직접 주입.
- 위험한 싱크의 부적절한 사용:
innerHTML,document.write,eval, 문자열 인수를 받는setTimeout같은 함수. - URL 기반 주입:
javascript:URI로href,src,style속성 조작. - 서드파티 구성 요소: 신뢰할 수 없는 데이터를 렌더링하는 취약한 라이브러리나 위젯.
XSS 방어책
1. 출력 인코딩(컨텍스트별 이스케이프)
HTML, 속성, JavaScript, CSS 또는 URL에 렌더링하기 전에 모든 신뢰할 수 없는 데이터를 인코딩하세요. 컨텍스트에 맞는 인코딩을 사용하세요:
- HTML 엔티티 인코딩:
&,<,>,",'를 엔티티로 변환. - JavaScript 인코딩: 영숫자가 아닌 문자를 유니코드로 이스케이프.
- URL 인코딩: 쿼리 매개변수에
encodeURIComponent()사용.
대부분의 최신 프레임워크(React, Angular, Vue)는 기본적으로 자동 이스케이프하지만 dangerouslySetInnerHTML 또는 유사한 탈출구를 사용할 때는 주의하세요.
2. 입력 검증 및 정제
허용 목록을 사용하여 서버 측에서 입력을 검증하세요. 리치 텍스트의 경우 DOMPurify 같은 라이브러리로 HTML을 정제하여 위험한 태그와 속성을 제거하세요.
const clean = DOMPurify.sanitize(userInput);
클라이언트 측 검증에만 의존하지 마세요.
3. 콘텐츠 보안 정책(CSP)
CSP는 강력한 심층 방어 메커니즘입니다. 실행 가능한 스크립트, 인라인 스크립트 및 기타 리소스의 출처를 제한합니다. 엄격한 CSP는 주입이 발생하더라도 XSS를 차단할 수 있습니다.
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none';
unsafe-inline과 unsafe-eval을 피하세요. 합법적인 인라인 스크립트에는 nonce나 해시를 사용하세요.
4. 안전한 쿠키 속성
쿠키에 HttpOnly(JavaScript 접근 방지)와 Secure(HTTPS 전용)를 설정하세요. 이는 XSS를 통한 세션 하이재킹을 완화합니다.
5. 최신 프레임워크 사용 및 위험한 API 회피
React, Angular, Vue 같은 프레임워크는 데이터를 자동으로 이스케이프합니다. innerHTML을 사용한 직접 DOM 조작을 피하세요. HTML을 삽입해야 한다면 먼저 정제하세요.
6. 정기적인 보안 테스트
SAST, DAST 및 수동 침투 테스트를 통합하세요. OWASP ZAP 같은 도구가 XSS 취약점을 식별하는 데 도움이 될 수 있습니다.
XSS 유형 및 주요 방어책 비교
| XSS 유형 | 설명 | 주요 방어 |
|---|---|---|
| 반사형 | 요청에 페이로드가 있고 응답에 반사됨 | 출력 인코딩, 입력 검증 |
| 저장형 | 페이로드가 서버에 저장되어 모든 사용자에게 제공됨 | 정제, 출력 인코딩, CSP |
| DOM 기반 | 안전하지 않은 DOM API를 통한 클라이언트 측 주입 | 안전한 DOM API, CSP, eval 회피 |
단계별: XSS 방어 구현
- 모든 입력 지점 식별: 양식, URL 매개변수, 헤더, 쿠키.
- 컨텍스트별 출력 인코딩 적용: 내장 함수나 OWASP Java Encoder 같은 라이브러리 사용.
- 리치 텍스트 정제: DOMPurify 또는 유사 도구 사용.
- CSP 배포: 보고 전용 모드로 시작한 후 시행.
- HttpOnly 및 Secure 쿠키 설정.
- 개발자 교육: 안전한 코딩 관행 교육.
- 정기적 테스트: CI/CD에 보안 테스트 통합.
FAQ
XSS와 CSRF의 차이점은 무엇인가요?
XSS는 피해자의 브라우저에서 악성 스크립트를 실행하는 반면, CSRF는 사용자가 인증된 사이트로 브라우저가 무단 요청을 보내도록 속입니다. XSS는 CSRF 보호를 우회하는 데 사용될 수 있습니다.
콘텐츠 보안 정책이 XSS를 완전히 방지할 수 있나요?
아니요, CSP는 심층 방어 조치입니다. 위험을 크게 줄이지만 적절한 출력 인코딩과 입력 검증을 대체할 수 없습니다. 잘못 구성된 CSP는 여전히 일부 공격을 허용할 수 있습니다.
클라이언트 측 검증만으로 XSS를 방지할 수 있나요?
아니요. 클라이언트 측 검증은 쉽게 우회될 수 있습니다. 항상 서버 측에서 검증하고 인코딩하며 모든 클라이언트 데이터를 신뢰할 수 없는 것으로 취급하세요.
결론
XSS는 지속적인 위협이지만 출력 인코딩, 입력 검증, CSP, 안전한 쿠키를 포함한 계층적 방어 전략으로 효과적으로 완화할 수 있습니다. 경계를 늦추지 말고 프레임워크를 최신 상태로 유지하며 지속적으로 테스트하세요.
추가 보안 도구를 원하시면 서버 로그에서 의심스러운 패턴을 탐지하는 Nginx 로그 분석기를 확인해 보세요.