Firefox 148, setHTML 도입으로 XSS 보호 강화
(hacks.mozilla.org)- 웹의 주요 취약점인 XSS 공격을 방지하기 위해 Firefox가 표준화된 Sanitizer API를 최초로 지원
- 기존의 innerHTML 대신 setHTML() 메서드를 사용하면, 신뢰할 수 없는 HTML을 DOM 삽입 전 자동 정화(sanitize) 하여 악성 스크립트를 제거
- 개발자는 기본 설정이 과하거나 부족할 경우 맞춤형 설정을 통해 허용할 요소와 속성을 제어할 수 있음
- Firefox의 이 기능은 Trusted Types와 결합해 웹 전반의 보안 수준을 높이고, 개발자가 별도 보안팀 없이도 XSS를 예방할 수 있게 함
XSS 취약점과 Firefox의 대응
-
교차 사이트 스크립팅(XSS) 은 사용자가 입력한 콘텐츠를 통해 공격자가 임의의 HTML이나 JavaScript를 삽입할 때 발생
- 공격자는 이를 이용해 사용자 상호작용을 감시하거나 데이터를 탈취할 수 있음
- XSS는 거의 10년간 상위 3대 웹 취약점(CWE-79) 으로 분류되어 왔음
- Firefox는 2009년부터 Content-Security-Policy(CSP) 표준을 주도하며 XSS 방어를 강화해 왔음
- CSP는 웹사이트가 로드 및 실행할 수 있는 리소스를 제한함
- 그러나 기존 사이트 구조 변경과 지속적인 보안 검토가 필요해 광범위한 채택에는 한계가 있었음
Sanitizer API와 setHTML()의 역할
-
Sanitizer API는 악성 HTML을 무해한 형태로 변환하는 표준화된 방법을 제공
- 예시 코드에서
<img src="x" onclick="alert('XSS')">요소는 제거되고<h1>Hello my name is</h1>만 남음
- 예시 코드에서
-
setHTML() 메서드는 HTML 삽입 시 자동으로 정화 과정을 수행해 기본적으로 안전한 동작을 보장
- 기존의
innerHTML대입을setHTML()로 교체하는 것만으로 강력한 XSS 방어 가능
- 기존의
- 개발자는 기본 설정이 너무 엄격하거나 느슨할 경우, 커스텀 설정을 통해 허용할 HTML 요소와 속성을 정의할 수 있음
- 실험을 위해 Sanitizer API playground 도구를 활용할 수 있음
Trusted Types과의 결합
-
Trusted Types API는 HTML 파싱과 삽입을 중앙에서 제어해 추가적인 보안 계층을 제공
-
setHTML()사용 시 Trusted Types 정책을 쉽게 적용할 수 있음 - 엄격한 정책은
setHTML()만 허용하고 다른 위험한 삽입 방식을 차단해 미래의 XSS 회귀 방지에 기여
-
Firefox 148의 보안 향상 효과
- Firefox 148은 Sanitizer API와 Trusted Types를 모두 지원해 기본 보안 수준을 크게 향상
- 개발자는 복잡한 보안 정책이나 별도의 보안팀 없이도 간단한 코드 변경만으로 XSS 방지 가능
- 이 표준의 도입은 모든 브라우저의 안전한 웹 환경 확산으로 이어질 것으로 기대됨
요약
- Firefox 148은 setHTML() 메서드와 Sanitizer API를 통해 웹 개발자가 손쉽게 XSS 공격을 차단할 수 있도록 지원
- 이 기능은 CSP의 한계를 보완하며, 기본적으로 안전한 HTML 삽입 방식을 웹 표준으로 정착시키는 계기임
- Trusted Types와의 결합으로 장기적인 보안 유지와 XSS 회귀 방지가 가능함
- 결과적으로, Firefox는 보안이 기본값인 웹 환경으로의 전환을 주도하고 있음
Hacker News 의견들
-
이런 종류의 기능은 항상 좀 불안함
사용자 입력을 임의로 넘겨도 안전하게 처리되는 메서드와 그렇지 않은 메서드가 섞여 있는데, 이름만 봐서는 구분이 어렵기 때문임
이상적으로는 처음부터 위험한 함수는 이름에서 명확히 드러나야 함
또, HTML을 “sanitize”한다는 개념 자체가 모호하고, 실제로 안전한지 판단하기 어려움- “안전”의 정의가 모호하다는 건 맞지만, 여기서 목표는 XSS-safe임
스크립트를 실행할 수 있는 요소나 속성을 제거하고, 이 로직이 브라우저 엔진 내부에서 동작해 문자열 기반의 sanitizer보다 정확하게 처리됨
자세한 내용은 MDN setHTML 문서 참고 - 사실 이미 명확한 구분이 있음
elementNode.textContent는 신뢰할 수 없는 입력에도 안전하고,elementNode.innerHTML은 그렇지 않음
전자는 모든 문자를 escape하고, 후자는 아무것도 escape하지 않음
“HTML sanitization”은 근본적으로 해결 불가능한 문제라는 의견도 있음
관련 논의는 이 댓글 참고
이런 API는 제안 단계에서 통과되지 말았어야 함 -
innerHTML과setHTML을 섞어 쓰는 게 아니라,innerHTML을 완전히 제거하고 예전 동작이 필요하면setHTMLUnsafe를 쓰는 식으로 설계해야 함 - 웹 개발자가 전역 설정으로
innerHTML같은 구식 API를 비활성화할 수 있으면 좋겠음
다만 그렇게 하면 오래된 브라우저에서는 사이트가 작동하지 않을 수도 있음 - 페이지를
Content-Security-Policy: require-trusted-types-for 'script'헤더와 함께 제공하면, sanitizer가 없는 메서드에 일반 문자열을 전달하는 걸 차단할 수 있음
- “안전”의 정의가 모호하다는 건 맞지만, 여기서 목표는 XSS-safe임
-
예시처럼
<h1>이나<br>같은 태그를 사용자 이름에 삽입할 수 있다면, 스크립트 실행은 막더라도 여전히 임의의 마크업 주입이 가능함
<style>태그로 CSS를 바꿔버릴 수도 있어서, 예컨대 PayPal 프로필 페이지의 디자인을 바꿔버릴 수도 있음
이런 걸 누가 원하겠는가 하는 의문이 듦- 그래도 포럼처럼 사용자가 Markdown을 쓸 수 있게 하고 싶을 때는 유용할 수 있음
Markdown으로 생성된 HTML을 sanitizer로 한 번 더 제한해 특정 태그만 허용하는 식으로 방어층을 추가할 수 있음 - 이런 경우라면
innerHTML이 아니라innerText나textContent를 써야 했음
setHTML은innerHTML의 대체용임 -
setHTML()의 기본 설정이 너무 엄격하거나 느슨하면, 개발자가 허용할 HTML 요소와 속성을 직접 정의하는 커스텀 설정을 제공할 수 있음 - CSS만으로도 보안 위험이 생길 수 있기 때문에, 여전히 주의가 필요함
관련 논의는 이 스레드 참고 - 예를 들어
이렇게 하면 모든 요소가 제거됨.setHTML("<h1>Hello</h1>", new Sanitizer({}))
결국 백엔드에서도 여전히 사용자 이름을 표준 방식으로 sanitize해야 하고, 출력 시에는 HTML escape를 적용해야 함
RFC 2119에 따르면 이는 “SHOULD” 수준의 요구사항임
- 그래도 포럼처럼 사용자가 Markdown을 쓸 수 있게 하고 싶을 때는 유용할 수 있음
-
이 기능이 등장한 건 반갑지만, 브라우저 지원이 충분히 퍼지려면 시간이 걸릴 듯함
Can I use에서 지원 현황을 확인할 수 있음- 다른 브라우저 API처럼 몇 년은 걸릴 수도 있고, 최신 버전만 대상으로 한다면 몇 달 안에 가능할 수도 있음
그동안은 polyfill로 대체할 수 있음
- 다른 브라우저 API처럼 몇 년은 걸릴 수도 있고, 최신 버전만 대상으로 한다면 몇 달 안에 가능할 수도 있음
-
제목이 약간 자극적이었음
사실innerHTML에 넘기기 전에 입력을 검사하는 함수로도 sanitization을 구현할 수 있지 않나 싶음
다만 이런 시도들이 결국 바퀴 재발명처럼 느껴짐
또, 오래된 Firefox에서는 hacks.mozilla.org가 아예 열리지 않고, Pale Moon이나 SeaMonkey에서는 MDN이 깨져 보임
마치 “브라우저 카르텔”이 웹을 망치려는 것 같음- “입력 검사 함수로 해결 가능하다”는 건 “C 언어도 버그만 없으면 메모리 안전하다”는 말과 같음
파서 간의 차이(parser differential) 문제도 고려하지 않은 주장임
- “입력 검사 함수로 해결 가능하다”는 건 “C 언어도 버그만 없으면 메모리 안전하다”는 말과 같음
-
Sanitizer API를 잘못 쓰면 footgun이 될 수 있음
특히 “remove” 모드를 쓸 때 조심해야 함
차라리setText만 쓰고, 사용자에게 HTML 추가를 전혀 허용하지 않는 게 낫다고 생각함- 허용 목록 기반(allowlist) Sanitizer를 쓰면 위험은 줄지만,
setHTML을 쓰는 한 XSS는 발생하지 않음 - 하지만 페이지 작성자가 큰 HTML 조각을 추가해야 할 때는 어떻게 할 것인가
innerHTML이 자주 쓰이는 현실을 보면, 완전히 배제하기는 어려움 - 오히려 이런 API가 “100% 안전하다”는 착각을 불러일으켜 더 위험할 수도 있음
- 허용 목록 기반(allowlist) Sanitizer를 쓰면 위험은 줄지만,
-
네트워크 접근의 모든 측면이 제대로 제어되어, 이제는 보안 체인이 코드 신뢰에서 호스트 설정 신뢰로 옮겨간 점이 인상적임
기본값도 안전하게 설정되어 있음 -
내가 진짜 원하는 건 위험한 코드를 안전하게 실행할 수 있는
<sandbox>요소임
위험한 코드를 수정하는 게 아니라, 격리된 환경에서 돌릴 수 있게 하는 것임
iframe은 DOM과 함께 흐르지 못하는 제약이 있고, AI나 동적 콘텐츠가 늘어나는 시대에는 구성 가능한 캡슐화가 필요함 -
setHTMLUnsafe라는 이름이 정말 마음에 듦
보안 기능은 개발자가 opt-in해야 하는 구조로는 실패함
대신 “위험한 경로가 위험하게 느껴지게” 만드는 게 효과적임 -
set_html()이라는 이름이inner_html보다 훨씬 직관적임
자바스크립트의 API는 정말 뒤죽박죽이라 언젠가 정리돼야 함
이번 논의는 보안 중심이지만, 새 API를 공개할 때는 설계 자체도 깔끔해야 함- 엄밀히 말하면 이건 DOM API임
DOM API는 예전부터, 그리고 지금도 “API를 만들어본 적 없는 사람들이 만든 것 같다”는 느낌을 줌
- 엄밀히 말하면 이건 DOM API임
-
90년대 개발자들:
SQL("select * from user where name = " + name);2020년대 개발자들:
div.innerHTML = "Hello " + user.name;- 2030년대 개발자들:
프롬프트 인젝션은 단지 새로운 기술 위의 같은 문제임"Summarize this email: " + email.contents
우리는 90년대에서 아무것도 배우지 못했음
- 2030년대 개발자들: