설명
DOM 기반 XSS는 브라우저 내 JavaScript가 사용자 제어 입력(예: location.search, location.hash, URL 파라미터, 엘리먼트 value 등)을 검증·인코딩 없이 위험한 DOM API(innerHTML, outerHTML, insertAdjacentHTML, document.write)로 삽입할 때 발생합니다. 공격자는 특수 문자를 포함한 페이로드를 URL 등에 넣어 HTML 태그나 이벤트 핸들러를 주입하고, 결과적으로 임의의 JavaScript를 실행시킬 수 있습니다. 예를 들어, ?q=<img src=x onerror=alert(1)> 같은 입력이 innerHTML로 반영되면 스크립트가 실행됩니다. 또한 안전한 텍스트로 얻은 값을 다시 HTML 컨텍스트로 재삽입할 때도(텍스트 → innerHTML 재해석) 취약해질 수 있습니다.
잠재적 영향
- 임의 스크립트 실행: 공격자가 피해자의 브라우저에서 임의의 JavaScript를 실행
- 세션/계정 탈취: 스크립트가 읽을 수 있는 쿠키, 토큰, localStorage 등의 민감 데이터 탈취로 계정 접수 가능
- 데이터 유출 및 변조: 페이지 DOM 조작을 통해 화면 내용 변조, 폼 데이터 가로채기, 내부 정보 수집
- 사용자 피싱/사기 유도: 가짜 입력창/알림 표시로 사용자 오인 유도 및 추가 공격 연결
해결 방법
- 텍스트 출력에는 textContent/innerText를 사용하고, innerHTML/outerHTML/document.write/insertAdjacentHTML에 신뢰할 수 없는 입력을 넣지 마세요.
- 불가피하게 HTML을 삽입해야 한다면 DOMPurify, sanitize-html 등의 검증된 Sanitizer로 정화 후 사용하세요.
- 컨텍스트별 인코딩 적용: HTML 텍스트, 속성, URL, JavaScript 컨텍스트마다 적절한 이스케이프/인코딩을 적용하세요.
- 입력 파싱 시 URLSearchParams, new URL() 등 안전한 파서를 사용하고, 필요 시 허용 목록(whitelist) 검증을 적용하세요.
- 브라우저 보강: CSP(Content-Security-Policy), Trusted Types(지원 환경) 등을 활성화해 XSS 피해 범위를 줄이세요.
예시
변경 전
javascript
// 변경 전: DOM 출력 처리
(function () {
// URL 해시값을 읽어 DOM에 삽입 (실제 해석되는 값 확인)
const raw = location.hash.slice(1); // 예: #<img src=x onerror=alert(1)>
const box = document.getElementById('msg');
// BAD: 사용자 입력을 HTML 컨텍스트에 직접 삽입
box.innerHTML = '<p>결과: ' + raw + '</p>';
// 또 다른 위험: document.write로 사용자 입력 출력
const q = new URL(location.href).searchParams.get('q') || '';
document.write('<div>검색어: ' + q + '</div>');
})();
변경 후
javascript
(function () {
const params = new URL(location.href).searchParams;
const q = params.get('q') || '';
const box = document.getElementById('msg');
// 1) 텍스트 컨텍스트 사용: HTML로 해석되지 않음
box.textContent = `검색어: ${q}`;
// 2) 불가피하게 HTML이 필요할 때는 Sanitizer 사용
// 예: DOMPurify가 전역으로 로드되어 있다고 가정
const userHtml = params.get('html') || '';
const safeHtml = DOMPurify.sanitize(userHtml, {ALLOWED_TAGS: ['b','i','u']});
document.getElementById('preview').innerHTML = safeHtml;
// 3) URL 등 속성 컨텍스트에는 안전한 조합 사용
const link = document.getElementById('link');
const dest = params.get('dest') || '/';
// 허용 목록 검사 예시
const allowed = ['/', '/help', '/profile'];
link.setAttribute('href', allowed.includes(dest) ? dest : '/');
})();
설명:
- 변경 전: 사용자 제어 값을 HTML로 해석하는 API에 전달합니다.
URLSearchParams로 읽은 쿼리 값은 디코딩되지만location.hash는 읽을 때 퍼센트 디코딩되지 않으므로, 해시에 인코딩된 태그가 있다는 사실만으로 실행된다고 판단하면 안 됩니다. 실제 HTML 값이 만들어지는 경로를 확인하세요. - 변경 후: 텍스트 출력에는 textContent를 사용해 HTML로 해석되지 않도록 했고, 불가피한 HTML 삽입은 DOMPurify로 정화하여 악성 태그/속성을 제거합니다. 또한 URL 등 속성 값은 허용 목록을 통해 제한하여 DOM 기반 XSS 가능성을 줄였습니다.