1. 소개
이 절은 비규범적입니다.
웹 애플리케이션은 클라이언트 측에서 HTML 문자열을 다뤄야 하는 경우가 자주 있습니다.
클라이언트 측 템플릿 솔루션의 일부일 수도 있고, 사용자 생성 콘텐츠를 렌더링하는 과정의 일부일 수도 있습니다.
이를 안전한 방식으로 수행하는 것은 어렵습니다.
문자열을 단순히 이어 붙여
Element의
innerHTML
에 밀어 넣는 순진한 접근은 수많은 예상치 못한 방식으로
JavaScript 실행을 유발할 수 있어 매우 위험합니다.
[DOMPURIFY]와 같은 라이브러리는 삽입 전에 문자열을 신중히 파싱하고 정리(sanitize)하여 문제를 다루려 합니다. 즉, DOM을 구성한 다음 허용 목록을 통해 그 구성원을 필터링합니다. 하지만 이는 취약한 접근법으로 드러났습니다. 웹에 노출된 파싱 API는 실제로 문자열을 "실제" DOM으로 렌더링할 때의 브라우저 동작과 항상 합리적으로 매핑되지 않기 때문입니다. 게다가 라이브러리는 시간이 지나며 변화하는 브라우저 동작을 계속 따라가야 합니다. 한때 안전했던 것이 플랫폼 수준의 새로운 기능으로 인해 시한폭탄이 될 수 있습니다.
브라우저는 언제 코드를 실행할지에 대해 꽤 잘 알고 있습니다. 사용자 공간 라이브러리보다 더 나은 방법은, 브라우저가 임의 문자열로부터 HTML을 안전한 방식으로 렌더링하도록 가르치고, 브라우저 자체의 파서 구현이 변함에 따라 함께 유지·업데이트될 가능성이 훨씬 높은 방식으로 이를 수행하는 것입니다. 이 문서는 바로 그러한 목적을 달성하려는 API를 개략적으로 설명합니다.
1.1. 목표
-
개발자에게 사용자 제어 HTML을 처리하는 메커니즘을 제공하여, 주입 시 스크립트가 직접 실행되는 것을 방지함으로써 DOM 기반 크로스 사이트 스크립팅(XSS) 공격의 위험을 완화합니다.
-
현재 사용자 에이전트의 HTML에 대한 이해를 고려하여, 해당 사용자 에이전트 내에서 안전하게 사용할 수 있는 HTML 출력을 만듭니다.
-
개발자가 기본 요소 및 속성 집합을 재정의할 수 있도록 합니다. 특정 요소와 속성을 추가하면 스크립트 가젯 공격을 방지할 수 있습니다.
1.2. API 요약
정리자 API는 HTML을 포함하는 문자열을 DOM 트리로 파싱하고, 사용자가 제공한 구성에 따라 결과 트리를 필터링하는 기능을 제공합니다. 메서드는 두 가지 맛으로 제공됩니다:
-
안전 및 비안전: "안전" 메서드는 스크립트를 실행하는 마크업을 생성하지 않습니다. 즉, XSS로부터 안전해야 합니다. "비안전" 메서드는 요구된 대로 파싱하고 필터링합니다. 또한 § 4 보안 고려사항을 참고하세요.
-
컨텍스트: 메서드는
Element및ShadowRoot에 정의되며 이들Node의 자식을 교체합니다. 이는 대체로innerHTML과 유사합니다. 또한Document에 정적 메서드가 있으며, 이는 전체 문서를 파싱하고 대체로DOMParser.parseFromString()과 유사합니다.
2. 프레임워크
2.1. 정리자 API
Element
인터페이스는 setHTML()과
setHTMLUnsafe()
두 가지 메서드를 정의합니다.
둘 다 HTML 마크업이 담긴 DOMString과
선택적 구성을 받습니다.
partial interface Element { [CEReactions ]undefined setHTMLUnsafe ((TrustedHTML or DOMString ),html optional SetHTMLUnsafeOptions = {}); };options
partial interface Element { [CEReactions ]undefined setHTML (DOMString ,html optional SetHTMLOptions = {}); };options
Element의
setHTMLUnsafe(html, options)
메서드 단계는 다음과 같습니다:
-
compliantHTML을 신뢰된 타입 호환 문자열 얻기 알고리즘에
TrustedHTML, this의 관련 전역 객체, html, "Element setHTMLUnsafe", 그리고 "script"를 전달하여 호출한 결과로 한다. -
target을 다음으로 둡니다: this가
template요소인 경우 template contents, 그 외에는 this. -
Set and filter HTML을 target, this, compliantHTML, options, false를 인수로 하여 수행합니다.
Element의
setHTML(html, options) 메서드 단계는
다음과 같습니다:
-
target을 다음으로 둡니다: this가
template인 경우 template contents, 그 외에는 this. -
Set and filter HTML을 target, this, html, options, true를 인수로 하여 수행합니다.
partial interface ShadowRoot { [CEReactions ]undefined setHTMLUnsafe ((TrustedHTML or DOMString ),html optional SetHTMLUnsafeOptions = {}); };options
partial interface ShadowRoot { [CEReactions ]undefined setHTML (DOMString ,html optional SetHTMLOptions = {}); };options
이 메서드들은 ShadowRoot에도
동일하게 반영됩니다:
ShadowRoot의
setHTMLUnsafe(html, options)
메서드 단계는 다음과 같습니다:
-
compliantHTML을 신뢰된 타입 호환 문자열 가져오기 알고리즘에
TrustedHTML, this의 관련 전역 객체, html, "ShadowRoot setHTMLUnsafe", 그리고 "script"를 전달하여 호출한 결과로 한다. -
Set and filter HTML을 this, this의 shadow host(컨텍스트 요소로 사용), compliantHTML, options, false를 인수로 하여 수행합니다.
ShadowRoot의
setHTML(html, options) 메서드 단계는
다음과 같습니다:
-
Set and filter HTML을 this(대상)와 this(컨텍스트 요소), html, options, true를 인수로 하여 수행합니다.
Document
인터페이스는 전체 Document를
파싱하는 두 개의 새 메서드를 얻습니다:
partial interface Document {static Document parseHTMLUnsafe ((TrustedHTML or DOMString ),html optional SetHTMLUnsafeOptions = {}); };options
partial interface Document {static Document parseHTML (DOMString ,html optional SetHTMLOptions = {}); };options
parseHTMLUnsafe(html, options)
메서드 단계는 다음과 같습니다:
-
compliantHTML을 get trusted type compliant string 알고리즘에
TrustedHTML, 현재 전역 객체, html, "Document parseHTMLUnsafe", 그리고 "script"를 전달하여 호출한 결과로 한다. -
document를 새
Document로 둡니다. 이때 content type은 "text/html"입니다.참고: document에는 탐색 컨텍스트가 없으므로 스크립팅이 비활성화됩니다.
-
document의 allow declarative shadow roots를 true로 설정합니다.
-
Parse HTML from a string을 document와 compliantHTML을 인수로 하여 수행합니다.
-
sanitizer를 get a sanitizer instance from options를 options와 false로 호출한 결과로 둡니다.
-
sanitize를 document에 대해 sanitizer 및 false로 호출합니다.
-
document를 반환합니다.
parseHTML(html, options)
메서드 단계는 다음과 같습니다:
-
document를 새
Document로 둡니다. 이때 content type은 "text/html"입니다.참고: document에는 탐색 컨텍스트가 없으므로 스크립팅이 비활성화됩니다.
-
document의 allow declarative shadow roots를 true로 설정합니다.
-
Parse HTML from a string을 document와 html을 인수로 하여 수행합니다.
-
sanitizer를 get a sanitizer instance from options를 options와 true로 호출한 결과로 둡니다.
-
sanitize를 document에 대해 sanitizer 및 true로 호출합니다.
-
document를 반환합니다.
2.2. SetHTML 옵션과 구성 객체.
setHTML()
계열의
메서드는 모두 옵션 딕셔너리를 받습니다. 현재는 이 딕셔너리의 구성원 한 개만 정의되어 있습니다:
enum {SanitizerPresets };"default" dictionary { (SetHTMLOptions Sanitizer or SanitizerConfig or SanitizerPresets )= "default"; };sanitizer dictionary { (SetHTMLUnsafeOptions Sanitizer or SanitizerConfig or SanitizerPresets )= {}; };sanitizer
Sanitizer
구성 객체는 필터 구성을 캡슐화합니다.
동일한 구성은 "안전"
또는 "비안전" 메서드 모두에서 사용할 수 있으며, "안전" 메서드는 전달된 구성에 대해 묵시적으로
removeUnsafe
연산을 수행하고, 구성이 전달되지 않으면 기본 구성을 가집니다. 기본값은 "안전"과 "비안전" 메서드 간에 다릅니다:
"안전" 메서드는 기본적으로 안전하도록 목표하며 제한적인 기본값을 가지는 반면,
"비안전" 메서드는 기본적으로 제한이 없습니다. 구성 사용 의도는
페이지 수명 초기에 하나(또는 몇 개)의 구성을 구축하여 필요할 때마다 사용하는 것입니다.
이렇게 하면 구현에서 구성을 사전 처리할 수 있습니다.
구성 객체는 구성 딕셔너리를 반환하도록 질의할 수 있으며, 직접 수정할 수도 있습니다.
[Exposed =Window ]interface {Sanitizer constructor (optional (SanitizerConfig or SanitizerPresets )= "default"); // Query configuration:configuration SanitizerConfig get (); // Modify a Sanitizer's lists and fields:boolean allowElement (SanitizerElementWithAttributes );element boolean removeElement (SanitizerElement );element boolean replaceElementWithChildren (SanitizerElement );element boolean allowProcessingInstruction (SanitizerPI );pi boolean removeProcessingInstruction (SanitizerPI );pi boolean allowAttribute (SanitizerAttribute );attribute boolean removeAttribute (SanitizerAttribute );attribute boolean setComments (boolean );allow boolean setDataAttributes (boolean ); // Remove markup that executes script.allow boolean removeUnsafe (); };
Sanitizer에는
연관된
SanitizerConfig
구성이 있습니다.
constructor(configuration)
메서드 단계는 다음과 같습니다:
-
configuration이
SanitizerPresets문자열인 경우:-
configuration을 내장 안전 기본 구성으로 설정합니다.
-
valid가 false이면
TypeError를 던집니다.
get() 메서드 단계는 다음과 같습니다:
-
config를 this의 configuration으로 설정한다.
-
-
각 config["
elements"]의 element에 대해:-
만약 element["
attributes"] 존재하면:-
element["
attributes"]를 오름차순으로 정렬한 결과로 설정한다. attrA가 less than item attrB일 때.
-
-
만약 element["
removeAttributes"] 존재하면:-
element["
removeAttributes"]를 오름차순으로 정렬한 결과로 설정한다. attrA가 less than item attrB일 때.
-
-
-
config["
elements"]를 오름차순으로 정렬한 결과로 설정한다. elementA가 less than item elementB일 때.
-
-
그렇지 않으면:
-
config["
removeElements"]를 오름차순으로 정렬한 결과로 설정한다. elementA가 less than item elementB일 때.
-
-
만약 config["
replaceWithChildrenElements"] 존재하면:-
config["
replaceWithChildrenElements"]를 오름차순으로 정렬한 결과로 설정한다. elementA가 less than item elementB일 때.
-
-
만약 config["
processingInstructions"] 존재하면:-
config["
processingInstructions"]를 오름차순으로 정렬한 결과로 설정한다. piA["target"] 가 code unit less than piB["target"]일 때.
-
-
그렇지 않으면:
-
config["
removeProcessingInstructions"]를 오름차순으로 정렬한 결과로 설정한다. piA["target"] 가 code unit less than piB["target"]일 때.
-
-
만약 config["
attributes"] 존재하면:-
config["
attributes"]를 오름차순으로 정렬한 결과로 설정한다. attrA가 less than item attrB일 때.
-
-
그렇지 않으면:
-
config["
removeAttributes"]를 오름차순으로 정렬한 결과로 설정한다. attrA가 less than item attrB일 때.
-
-
config를 반환한다.
allowElement(element) 메서드 단계는 다음과 같습니다:
-
전역 허용/제거 목록이 있는지 여부, 그리고
-
해당 목록에 이미 element가 포함되어 있는지 여부.
-
configuration를 this의 configuration으로 설정한다.
-
element를 속성 포함 sanitizer 요소 정규화의 결과로 설정한다. element로서.
-
만약 configuration["
elements"] 존재하면:-
modified를 remove element from configuration["
replaceWithChildrenElements"]의 결과로 설정한다. -
코멘트: 요소별 속성이 전역 속성과 중복되지 않도록 해야 한다.
-
만약 configuration["
attributes"] 존재하면:-
만약 element["
attributes"] 존재하면:-
element["
attributes"]를 중복 제거한 결과로 설정한다. element["attributes"]에서. -
element["
attributes"]를 차집합 결과로 설정한다. element["attributes"] 와 configuration["attributes"]. -
만약 configuration["
dataAttributes"]가 true이면:-
제거 element["
attributes"]의 모든 item이 커스텀 데이터 속성인 경우 제거한다.
-
-
-
만약 element["
removeAttributes"] 존재하면:-
element["
removeAttributes"]를 중복 제거한 결과로 설정한다. -
element["
removeAttributes"]를 교집합 결과로 설정한다. element["removeAttributes"] 와 configuration["attributes"].
-
-
-
그렇지 않으면:
-
만약 element["
attributes"] 존재하면:-
element["
attributes"]를 중복 제거한 결과로 설정한다. -
element["
attributes"]를 차집합 결과로 설정한다. element["attributes"] 와 element["removeAttributes"] 기본값 « » 기준. -
제거 element["
removeAttributes"]를 제거한다. -
element["
attributes"]를 차집합 결과로 설정한다. element["attributes"] 와 configuration["removeAttributes"].
-
-
만약 element["
removeAttributes"] 존재하면:-
element["
removeAttributes"]를 중복 제거한 결과로 설정한다. -
element["
removeAttributes"]를 차집합 결과로 설정한다. element["removeAttributes"] 와 configuration["removeAttributes"].
-
-
-
코멘트: 전역 허용 목록에 이미 element가 포함된 경우.
-
current element를 configuration["
elements"] 중에서 item["name"]이 동일하고 item["namespace"] 가 동일한 element인 item으로 설정한다. -
만약 element가 동일하다면 current element와, modified를 반환한다.
-
true를 반환한다.
-
-
그렇지 않으면:
-
만약 element["
attributes"] 존재하거나, element["removeAttributes"] 기본값 « »일 때 비어있지 않으면:-
사용자 에이전트는 콘솔에 경고 표시를 할 수 있다.
-
false를 반환한다.
-
-
modified를 remove element from configuration["
replaceWithChildrenElements"]의 결과로 설정한다. -
만약 configuration["
removeElements"] 포함하지 않는다면 element를:-
코멘트: 전역 제거 목록에 element가 없는 경우.
-
modified를 반환한다.
-
-
코멘트: 전역 제거 목록에 element가 이미 포함된 경우.
-
제거 element를 configuration["
removeElements"]에서. -
true를 반환한다.
-
replaceElementWithChildren(element)
메서드 단계는 다음과 같다:
-
element를 sanitizer 요소 정규화의 결과로 설정한다, element를 인자로 한다.
-
내장 비치환 요소 목록이 포함하고 있을 경우 element를:
-
false를 반환한다.
-
-
configuration["
replaceWithChildrenElements"] 가 포함하고 있을 경우 element를:-
false를 반환한다.
-
-
제거 element를 configuration["
removeElements"] 에서. -
추가 element를 configuration["
replaceWithChildrenElements"] 에. -
true를 반환한다.
allowProcessingInstruction(pi)
메서드 단계는 다음과 같습니다:
-
configuration을 this의 configuration으로 설정한다.
-
pi를 sanitize 처리 명령문 정규화 결과로 설정한다.
-
만약 configuration["
processingInstructions"] 존재하면:-
만약 configuration["
processingInstructions"] 포함 pi라면:-
false를 반환한다.
-
-
추가 pi를 configuration["
processingInstructions"]에 추가한다. -
true를 반환한다.
-
-
그렇지 않으면:
-
만약 configuration["
removeProcessingInstructions"] 포함 pi라면:-
제거 configuration["
removeProcessingInstructions"] 에서 "target" 이 pi["target"]와 같은 항목을 제거한다. -
true를 반환한다.
-
-
false를 반환한다.
-
removeProcessingInstruction(pi)
메서드 단계는 다음과 같습니다:
-
configuration을 this의 configuration으로 설정한다.
-
pi를 sanitize 처리 명령문 정규화 결과로 설정한다.
-
만약 configuration["
processingInstructions"] 존재하면:-
만약 configuration["
processingInstructions"] 포함 pi라면:-
제거 configuration["
processingInstructions"] 에서 "target" 이 pi["target"]와 같은 항목을 제거한다. -
true를 반환한다.
-
-
false를 반환한다.
-
-
그렇지 않으면:
-
만약 configuration["
removeProcessingInstructions"] 포함 pi라면:-
false를 반환한다.
-
-
추가 pi를 configuration["
removeProcessingInstructions"]에 추가한다. -
true를 반환한다.
-
allowAttribute(attribute) 메서드 단계는 다음과
같습니다:
-
configuration을 this의 configuration으로 설정한다.
-
attribute를 sanitizer 속성 정규화 결과로 설정한다.
-
만약 configuration["
attributes"] 존재하면:-
코멘트: 전역 허용 목록이 있다면, attribute를 추가해야 한다.
-
만약 configuration["
dataAttributes"] 가 true이고 attribute가 커스텀 데이터 속성이라면, false를 반환한다. -
만약 configuration["
attributes"] 포함 attribute라면 false를 반환한다. -
코멘트: 요소별 허용/제거 목록을 수정한다.
-
만약 configuration["
elements"] 존재하면:-
각 element에 대해 configuration["
elements"]:-
만약 element["
attributes"] 기본값 « » 포함 attribute라면:-
제거 attribute를 element["
attributes"]에서.
-
-
단언: element["
removeAttributes"] 기본값 « » 이 포함하지 않는다 attribute.
-
-
-
추가 attribute를 configuration["
attributes"]에 추가한다. -
true를 반환한다.
-
-
그렇지 않으면:
-
코멘트: 전역 제거 목록이 있다면, attribute를 제거해야 한다.
-
만약 configuration["
removeAttributes"] 포함하지 않는다면 attribute를:-
false를 반환한다.
-
-
제거 attribute를 configuration["
removeAttributes"]에서. -
true를 반환한다.
-
setComments(allow) 메서드 단계는 다음과 같습니다:
setDataAttributes(allow) 메서드 단계는 다음과
같습니다:
-
configuration을 this의 configuration으로 설정한다.
-
만약 configuration["
attributes"] 존재하지 않으면, false를 반환한다. -
만약 configuration["
dataAttributes"] 가 allow와 같다면, false를 반환한다. -
만약 allow가 true라면:
-
제거 configuration["
attributes"]의 attr가 커스텀 데이터 속성인 모든 항목을 제거한다. -
만약 configuration["
elements"] 존재하면:-
각 element에 대해 configuration["
elements"]:-
만약 element["
attributes"] 존재하면:-
제거 element["
attributes"]의 attr가 커스텀 데이터 속성인 모든 항목을 제거한다.
-
-
-
-
-
configuration["
dataAttributes"]를 allow로 설정한다. -
true를 반환한다.
2.3. 구성 딕셔너리
dictionary {SanitizerElementNamespace required DOMString ;name DOMString ?= "http://www.w3.org/1999/xhtml"; }; // Used by "elements"_namespace dictionary :SanitizerElementNamespaceWithAttributes SanitizerElementNamespace {sequence <SanitizerAttribute >;attributes sequence <SanitizerAttribute >; };removeAttributes typedef (DOMString or SanitizerElementNamespace );SanitizerElement typedef (DOMString or SanitizerElementNamespaceWithAttributes );SanitizerElementWithAttributes dictionary {SanitizerProcessingInstruction required DOMString ; };target typedef (DOMString or SanitizerProcessingInstruction );SanitizerPI dictionary {SanitizerAttributeNamespace required DOMString ;name DOMString ?=_namespace null ; };typedef (DOMString or SanitizerAttributeNamespace );SanitizerAttribute dictionary {SanitizerConfig sequence <SanitizerElementWithAttributes >;elements sequence <SanitizerElement >;removeElements sequence <SanitizerElement >;replaceWithChildrenElements sequence <SanitizerPI >;processingInstructions sequence <SanitizerPI >;removeProcessingInstructions sequence <SanitizerAttribute >;attributes sequence <SanitizerAttribute >;removeAttributes boolean ;comments boolean ; };dataAttributes
2.4. 구성 불변식
구성은 개발자가 자신의 목적에 맞게 수정할 수 있으며, 그렇게 해야 합니다. 옵션으로는 처음부터 새로운 구성 딕셔너리를 작성하거나, 기존 Sanitizer의
구성을 수정자 메서드로 변경하거나, get()
메서드로 기존 Sanitizer의
구성을 딕셔너리로 받아 수정한 뒤 새로운 Sanitizer를
생성할 수 있습니다.
빈 구성은 모든 것을 허용합니다("unsafe" 메서드(setHTMLUnsafe
등)로 호출할 때).
"default" 구성은 내장 안전 기본 구성을 포함합니다.
"safe" 메서드와 "unsafe" sanitizer 메서드는 서로 다른 기본값을 가집니다.
모든 구성 딕셔너리가 유효한 것은 아닙니다. 유효한 구성은 중복(예: 동일한 요소를 두 번 허용하는 등)과 모순(예: 요소를 동시에 제거와 허용에 지정하는 등)을 피해야 합니다.
구성이 유효하려면 다음 조건들이 충족되어야 합니다:
-
글로벌 허용 목록과 제거 목록 혼용:
-
elements또는removeElements중 하나만 존재할 수 있다. 둘 다 없으면, 이는removeElements가 « »로 설정된 것과 같다. -
attributes또는removeAttributes중 하나만 존재할 수 있다. 둘 다 없으면, 이는removeAttributes가 « »로 설정된 것과 같다. -
dataAttributes는 개념적으로attributes허용 목록의 확장이다.dataAttributes속성은attributes목록이 사용될 때만 허용된다.
-
-
서로 다른 글로벌 목록 간의 중복 항목:
-
elements,removeElements, 또는replaceWithChildrenElements사이에 중복 항목(즉, 동일한 엘리먼트)이 없어야 한다. -
attributes와removeAttributes사이에 중복 항목(즉, 동일한 속성)이 없어야 한다.
-
-
동일 엘리먼트에 대해 로컬 허용/제거 목록 혼용:
-
attributes목록이 있을 때, 동일 엘리먼트에attributes와removeAttributes를 둘 다, 하나 혹은 둘 다 없음으로 둘 수 있다. -
removeAttributes목록이 있을 때, 동일 엘리먼트에attributes와removeAttributes중 하나 혹은 둘 다 없음으로 둘 수 있지만, 둘 다 존재할 수는 없다.
-
-
동일 엘리먼트 내 중복 항목:
-
동일 엘리먼트 내에서
attributes와removeAttributes사이에 중복 항목이 없어야 한다.
-
-
내장 비치환 요소 목록에 속한 엘리먼트는
replaceWithChildrenElements에 나타나지 않아야 한다. 이런 엘리먼트를 자식으로 대체하면 재파싱 문제 또는 잘못된 노드 트리가 발생할 수 있기 때문이다.
elements
요소 허용 목록은 특정 요소에 대해 속성을 허용하거나 제거하도록 지정할 수도 있다. 이는 [HTML]의 구조를 반영하기 위한 것으로,
전역 속성과 특정 요소에 적용되는 로컬 속성을
모두 인지한다. 전역 및 로컬 속성은 혼용할 수 있으나, 어떤 속성이 한 목록에서는 허용되고 다른 목록에서는 금지되는 등
모호한 구성은 일반적으로 유효하지 않음을 유의하라.
전역 attributes
| 전역 removeAttributes
| |
|---|---|---|
로컬 attributes
| 속성은 둘 중 어느 목록과 일치하면 허용된다. 중복은 허용되지 않는다. | 속성은 로컬 허용 목록에 있는 경우에만 허용된다. 전역 제거 목록과 로컬 허용 목록 사이에는 중복 항목이 허용되지 않는다. 전역 제거 목록은 이 특정 요소에 대해서는 기능하지 않지만, 로컬 허용 목록이 없는 다른 요소에는 적용될 수 있음을 유의하라. |
로컬 removeAttributes
| 속성은 전역 허용 목록에 있고 로컬 제거 목록에는 없는 경우 허용된다. 로컬 제거 목록은 전역 허용 목록의 부분집합이어야 한다. | 속성은 어느 목록에도 없을 때 허용된다. 전역 제거 목록과 로컬 제거 목록 사이에는 중복 항목이 허용되지 않는다. |
전역 목록과 요소별 목록 사이에서는 대체로 중복이 허용되지 않지만, 전역 허용 목록과 요소별 제거 목록의 조합에서는 후자가 전자에 대한 부분집합이어야 한다는 비대칭성이 있음을 유의하라. 위 표에서 중복에만 초점을 맞춘 발췌는 다음과 같다:
전역 attributes
| 전역 removeAttributes
| |
|---|---|---|
로컬 attributes
| 중복은 허용되지 않는다. | 중복은 허용되지 않는다. |
로컬 removeAttributes
| 로컬 제거 목록은 전역 허용 목록의 부분집합이어야 한다. | 중복은 허용되지 않는다. |
dataAttributes
설정은 사용자 정의 데이터 속성을 허용한다. 위 규칙은
dataAttributes
를 허용 목록으로 간주하면 사용자 정의 데이터 속성에도 쉽게 확장된다:
전역 attributes
와 dataAttributes
가 설정됨
| |
|---|---|
로컬 attributes
| 모든 사용자 정의 데이터 속성이 허용된다. 사용자 정의 데이터 속성은 중복 항목이 되므로 어떤 허용 목록에도 기재되어서는 안 된다. |
로컬 removeAttributes
| 사용자 정의 데이터 속성은 로컬 제거 목록에 기재되지 않은 한 허용된다. 중복 항목이 되므로 전역 허용 목록에는 사용자 정의 데이터 속성을 기재해서는 안 된다. |
이 규칙들을 문장으로 정리하면:
-
전역 목록과 로컬 목록 사이의 중복과 상호작용:
-
전역
attributes허용 목록이 존재하면, 모든 요소의 로컬 목록에 대해:-
로컬
attributes허용 목록이 존재하는 경우, 이들 목록 사이에는 중복 항목이 없어야 한다. -
로컬
removeAttributes제거 목록이 존재하는 경우, 그 모든 항목은 전역attributes허용 목록에도 기재되어 있어야 한다. -
dataAttributes가 true인 경우, 어떤 허용 목록에도 사용자 정의 데이터 속성을 기재해서는 안 된다.
-
-
전역
removeAttributes제거 목록이 존재하면:-
로컬
attributes허용 목록이 존재하는 경우, 이들 목록 사이에는 중복 항목이 없어야 한다. -
로컬
removeAttributes제거 목록이 존재하는 경우, 이들 목록 사이에는 중복 항목이 없어야 한다. -
로컬
attributes허용 목록과 로컬removeAttributes제거 목록이 동시에 존재해서는 안 된다. -
dataAttributes는 없어야 한다.
-
-
SanitizerConfig
config가
유효한지(valid)를 판단하려면:
참고: 입력된 구성은 이전에 구성 정규화 단계를 거쳤다고 예상합니다. 여기서는 해당 알고리즘이 보장해야 하는 조건들만 단언(assert)합니다.
-
단언: config["
elements"] 존재하거나 config["removeElements"] 존재. -
만약 config["
elements"] 존재하고, config["removeElements"] 존재한다면, false를 반환한다. -
단언: config["
processingInstructions"] 존재하거나 config["removeProcessingInstructions"] 존재. -
만약 config["
processingInstructions"] 존재하고, config["removeProcessingInstructions"] 존재한다면, false를 반환한다. -
단언: config["
attributes"] 존재하거나 config["removeAttributes"] 존재. -
만약 config["
attributes"] 존재하고, config["removeAttributes"] 존재한다면, false를 반환한다. -
단언: 모든
SanitizerElementNamespaceWithAttributes,SanitizerElementNamespace,SanitizerProcessingInstruction, 그리고SanitizerAttributeNamespace항목들은 모두 정규화되어 있어야 하며, 각각 요소 정규화, 처리 명령문 정규화, 속성 정규화를 거쳤다는 의미이다. -
그렇지 않으면:
-
만약 config["
removeElements"] 중복이 있다면, false를 반환한다.
-
-
만약 config["
replaceWithChildrenElements"] 존재하며 중복이 있다면, false를 반환한다. -
만약 config["
processingInstructions"] 존재한다면:-
만약 config["
processingInstructions"] 중복 target이 있다면, false를 반환한다.
-
-
그렇지 않으면:
-
만약 config["
removeProcessingInstructions"] 중복 target이 있다면, false를 반환한다.
-
-
만약 config["
attributes"] 존재한다면:-
만약 config["
attributes"] 중복이 있다면, false를 반환한다.
-
-
그렇지 않으면:
-
만약 config["
removeAttributes"] 중복이 있다면, false를 반환한다.
-
-
만약 config["
replaceWithChildrenElements"] 존재한다면:-
각 element에 대해 config["
replaceWithChildrenElements"]:-
만약 내장 비대체 요소 목록이 포함 element라면, false를 반환한다.
-
-
-
만약 교집합이 config["
elements"] 와 config["replaceWithChildrenElements"] 이 비어있지 않으면, false를 반환한다.
-
-
그렇지 않으면:
-
만약 교집합이 config["
removeElements"] 와 config["replaceWithChildrenElements"] 이 비어있지 않으면, false를 반환한다.
-
-
-
만약 config["
attributes"] 존재한다면:-
단언: config["
dataAttributes"] 존재. -
-
각 element에 대해 config["
elements"]:-
만약 element["
attributes"] 존재하며 element["attributes"] 중복이 있다면, false를 반환한다. -
만약 element["
removeAttributes"] 존재하고 element["removeAttributes"] 중복이 있다면, false를 반환한다. -
만약 교집합이 config["
attributes"] 와 element["attributes"] 기본값 « » 이 비어있지 않으면, false를 반환한다. -
만약 element["
removeAttributes"] 기본값 « » 이 subset이 아니라면, false를 반환한다. -
만약 config["
dataAttributes"] 가 true이고, element["attributes"] 에 커스텀 데이터 속성이 있다면, false를 반환한다.
-
-
-
만약 config["
dataAttributes"] 가 true이고, config["attributes"] 에 커스텀 데이터 속성이 있다면, false를 반환한다.
-
-
그렇지 않으면:
-
-
각 element에 대해 config["
elements"]:-
만약 element["
attributes"] 존재하며 element["removeAttributes"] 존재, false를 반환한다. -
만약 element["
attributes"] 존재 하며 element["attributes"] 중복이 있다면, false를 반환한다. -
만약 element["
removeAttributes"] 존재 하며 element["removeAttributes"] 중복이 있다면, false를 반환한다. -
만약 교집합이 config["
removeAttributes"] 와 element["attributes"] 기본값 « » 이 비어있지 않으면, false를 반환한다. -
만약 교집합이 config["
removeAttributes"] 와 element["removeAttributes"] 기본값 « » 이 비어있지 않으면, false를 반환한다.
-
-
-
만약 config["
dataAttributes"] 존재, false를 반환한다.
-
-
true를 반환한다.
참고: 구성 설정을 dictionary에서 할 경우,
약간의 정규화(normalization)가 일어납니다.
특히, 허용 목록(allow-list)과 제거 목록(remove-list)이 모두 없으면 이것을 빈 제거 목록으로 해석합니다.
따라서 {} 자체는 유효한 구성이 아니지만,
실제로 {removeElements:[], removeAttributes:[]}로 정규화되어 유효 구성이 됩니다.
이런 정규화 단계는, 없는 딕셔너리와 빈 딕셔너리를 동일하게 취급하기 위한 것이며,
setHTMLUnsafe(txt)와 setHTMLUnsafe(txt, {sanitizer: {}})
의 동작이 일치하게 해줍니다.
3. 알고리즘
Element
또는 DocumentFragment
target, Element
contextElement, 문자열 html, 딕셔너리
options, 불리언 safe가 주어진다:
-
safe이고 contextElement의 local name이 "
script"이며, contextElement의 namespace가 HTML 네임스페이스 또는 SVG 네임스페이스라면, 반환한다. -
sanitizer를 옵션에서 sanitizer 인스턴스 얻기의 결과로 options와 safe를 넘겨 호출한 값으로 둔다.
-
newChildren을 HTML 프래그먼트 파싱 알고리즘의 결과로, contextElement, html, true를 넘겨 호출한 값으로 둔다.
-
fragment를
DocumentFragment의 새 인스턴스로 두고, node document는 contextElement의 node document로 한다. -
newChildren의 각 node에 대해, fragment에 node 추가를 수행한다.
-
sanitize를 fragment, sanitizer, safe로 호출한다.
-
Replace all을 사용해 target 내에 fragment로 교체한다.
참고: 이 알고리즘은 SetHTMLOptions
와
SetHTMLUnsafeOptions
모두에 적용됩니다.
둘은 기본값만 다릅니다.
-
sanitizerSpec을 "
default"로 설정한다. -
만약 options["
sanitizer"] 존재한다면:-
sanitizerSpec을 options["
sanitizer"] 로 설정한다.
-
-
단언: sanitizerSpec은
Sanitizer인스턴스, 문자열(string)로서SanitizerPresets멤버이거나, dictionary 중 하나여야 한다. -
만약 sanitizerSpec이 문자열(string)이라면:
-
sanitizerSpec을 내장 안전 기본 구성으로 설정한다.
-
단언: sanitizerSpec은
Sanitizer인스턴스 또는 dictionary이어야 한다. -
만약 sanitizerSpec이 dictionary라면:
-
sanitizerSpec을 반환한다.
3.1. 정리(Sanitize)
ParentNode
node와
Sanitizer
sanitizer, 그리고 boolean safe가 있을 때 다음 단계를 수행한다:
-
configuration을 sanitizer의 configuration 값으로 설정한다.
-
만약 safe가 true면 configuration을 remove unsafe를 configuration에 호출한 결과로 재설정한다.
-
sanitize core를 node, configuration, 그리고 handleJavascriptNavigationUrls를 safe로 설정하여 호출한다.
ParentNode
node, SanitizerConfig
configuration, 그리고
boolean
handleJavascriptNavigationUrls로
node부터 DOM 트리를 재귀적으로 순회한다. 단계는 다음과 같다:
-
-
단언: child가 Text,
Comment,Element,ProcessingInstruction또는DocumentType을 구현한다고 단언.참고: 현재 이 알고리즘은 HTML 파서의 출력에만 호출된다(이 단언이 성립함).
DocumentType은parseHTML및parseHTMLUnsafe에만 나타난다. 이후 다른 컨텍스트에서 사용하려면 이 가정 재검토 필요. -
만약 child가 DocumentType 구현체라면, 계속.
-
만약 child가 Comment 구현체라면:
-
만약 child가 ProcessingInstruction 구현체라면:
-
piTarget을 child의 target으로 설정.
-
만약 configuration["
processingInstructions"] 존재한다면:-
configuration["
processingInstructions"] 가 piTarget을 포함하지 않으면:
-
-
그 외의 경우:
-
configuration["
removeProcessingInstructions"] 가 piTarget을 포함하면:
-
-
-
위 모두 해당하지 않으면(즉, Element):
-
elementName을 child의 로컬 이름 및 네임스페이스를 가진
SanitizerElementNamespace라고 하자. -
configuration["
replaceWithChildrenElements"]가 존재하고, configuration["replaceWithChildrenElements"]가 elementName을 포함하면:-
child에 대해 configuration 및 handleJavascriptNavigationUrls와 함께 sanitize core를 호출한다.
-
fragment를, 노드 문서가 node의 노드 문서인 새
DocumentFragment라고 하자. -
child의 자식 각각의 innerChild에 대해, innerChild를 fragment에 append한다.
-
node 내에서 child를 fragment로 교체한다.
NOTE: 여기서는 Replace가 throw되어서는 안 된다. 알고리즘의 성공적인 실행을 위한 구조적 전제조건이 충족되어 있어야 하기 때문이다.
-
계속한다.
-
그렇지 않으면:
-
configuration["
removeElements"]가 elementName을 포함하면:
-
-
elementName이 «[ "
name" → "template", "namespace" → HTML 네임스페이스 ]»와 같으면, child의 template contents에 configuration 및 handleJavascriptNavigationUrls와 함께 sanitize core를 호출한다. -
child가 shadow host이면, child의 shadow root에 configuration 및 handleJavascriptNavigationUrls와 함께 sanitize core를 호출한다.
-
elementWithLocalAttributes를 « [] »라고 하자.
-
configuration["
elements"]가 존재하고, configuration["elements"]가 elementName을 포함하면:-
elementWithLocalAttributes를 configuration["
elements"][elementName]로 설정한다.
-
-
child의 속성 목록 안의 각 attribute에 대해:
-
attrName을 attribute의 로컬 이름 및 네임스페이스를 가진
SanitizerAttributeNamespace라고 하자. -
elementWithLocalAttributes["
removeAttributes"]가 기본값 « »으로 attrName을 포함하면:-
attribute를 제거한다.
-
-
그렇지 않고, configuration["
attributes"]가 존재하면:-
configuration["
attributes"]가 attrName을 포함하지 않고, elementWithLocalAttributes["attributes"]가 기본값 « »으로도 attrName을 포함하지 않고, "data-"가 attribute의 로컬 이름의 코드 단위 접두사가 아니며, 네임스페이스가null이 아니거나 configuration["dataAttributes"]가 true가 아니면:-
attribute를 제거한다.
-
-
-
그렇지 않으면:
-
elementWithLocalAttributes["
attributes"]가 존재하고, elementWithLocalAttributes["attributes"]가 attrName을 포함하지 않으면:-
attribute를 제거한다.
-
-
그렇지 않고, configuration["
removeAttributes"]가 attrName을 포함하면:-
attribute를 제거한다.
-
-
-
handleJavascriptNavigationUrls이면:
-
«[elementName, attrName]»가 내장 navigating URL 속성 목록의 항목과 일치하고, attribute가 javascript: URL을 포함하면, attribute를 제거한다.
-
child의 네임스페이스가 MathML Namespace이고, attr의 로컬 이름이 "
href"이며, attr의 네임스페이스가null이거나 XLink 네임스페이스이고, attr이 javascript: URL을 포함하면, attribute를 제거한다. -
내장 animating URL 속성 목록이 «[elementName, attrName]»를 포함하고, attr의 값이 "
href" 또는 "xlink:href"이면, attribute를 제거한다.
-
-
-
child에 대해 configuration 및 handleJavascriptNavigationUrls와 함께 sanitize core를 호출한다.
-
-
javascript: URL을
네비게이션 상황에서만 지원한다. 네비게이션 자체는 XSS 위협이 아니기 때문에 navigation to javascript: URL일 땐
처리하지만, 일반적인 네비게이션은 모두 다루지 않는다.
선언적 네비게이션은 다음 범주로 구분된다:
-
Anchor 요소. (HTML/SVG 네임스페이스의
<a>) -
폼 요소 중 폼 action 결과로 네비게이션이 발생하는 경우.
-
[SVG11] 애니메이션.
첫 두 경우는 built-in navigating URL attributes list 로 처리된다.
MathML의 경우는 별도 규칙으로 처리되는데, 이 명세에는 "네임스페이스별 전역" 규칙을 처리할 포멀리즘이 없기 때문이다.
SVG 애니메이션은
built-in animating URL attributes list로
처리한다. 하지만 SVG 애니메이션의 실제 해석은 animation target에 따라 달라지고, sanitization 중에는
실제 target을 알 수 없으므로, sanitize
알고리즘에서는 href 속성의 animation을 모두 차단한다.
-
url이
failure라면 false를 반환한다. -
url의 scheme이 "javascript" 인지 반환한다.
3.2. 구성 수정
구성 수정 메서드는 Sanitizer의
메서드로,
해당 인스턴스의 구성을 수정합니다.
이 메서드들은 항상 유효성 기준을 유지합니다.
또한, 구성에 수정이 발생했는지 여부를 나타내는 boolean을 반환합니다.
let s= new Sanitizer({ elements: [ "div" ]}); s. allowElement( "p" ); // true를 반환 div. setHTML( "<div><p>" , { sanitizer: s}); // `<div>`와 `<p>` 허용됨
let s= new Sanitizer({ elements: [ "div" ]}); s. removeElement( "p" ); // false 반환, <p>는 기존에 허용되지 않았음. div. setHTML( "<div><p>" , { sanitizer: s}); // `<div>`만 허용됨. `<p>`는 제거됨.
SanitizerElement
element를 SanitizerConfig
configuration에서
제거하려면:
-
전역 허용/제거 목록 중 어떤 것을 가지고 있는지,
-
이미 element가 해당 목록에 들어있는지 여부.
-
element를 sanitizer 요소 정규화의 결과로 재설정한다.
-
modified를 구성에서 element 제거 결과로 설정한다. configuration["
replaceWithChildrenElements"]. -
만약 configuration["
elements"] 존재하면:-
configuration["
elements"] 가 element 포함하면:-
코멘트: 전역 허용 리스트에 element가 포함됨.
-
element를 구성에서 제거한다. configuration["
elements"]에서. -
true를 반환한다.
-
-
코멘트: 전역 허용 리스트에 element가 없음.
-
modified를 반환한다.
-
-
그 외의 경우:
-
configuration["
removeElements"] 가 element 포함하면:-
코멘트: 전역 제거 목록에 이미 포함되어 있음.
-
modified를 반환한다.
-
-
코멘트: 전역 제거 목록에 element가 없음.
-
element를 구성의 제거 목록에 추가 configuration["
removeElements"]에. -
true를 반환한다.
-
SanitizerConfig
configuration에서
제거하려면:
참고: 이 메서드는 전역 허용/제거 목록을 구분합니다. attribute를 전역 제거 목록에 추가하면, 유효성 유지 기준을 위해 요소별 허용/제거 목록도 추가 조정이 필요할 수 있습니다. 전역 허용 목록에서 attribute를 제거할 때도, 로컬 제거 목록에서 해당 속성 제거가 필요할 수 있습니다.
-
attribute를 sanitizer 속성 정규화 결과로 재설정한다.
-
만약 configuration["
attributes"] 존재한다면:-
코멘트: 전역 허용 목록일 경우 attribute 제거 필요.
-
modified를 구성에서 attribute 제거의 결과로 설정; configuration["
attributes"]에서. -
코멘트: 요소별 허용/제거 목록 추가 조정
-
만약 configuration["
elements"] 존재한다면:-
각 element에 대해 configuration["
elements"]:-
element["
attributes"] 기본값 « » 에 attribute 포함시:-
modified를 true로 설정
-
attribute 제거; element["
attributes"]에서.
-
-
element["
removeAttributes"] 기본값 « »에 attribute 포함시:-
단언: modified는 true여야 함.
-
attribute 제거; element["
removeAttributes"]에서.
-
-
-
-
modified를 반환한다.
-
-
그 외의 경우:
-
코멘트: 전역 제거 목록이면 attribute를 추가해야 함.
-
configuration["
removeAttributes"] 가 attribute 포함시 false 반환. -
코멘트: 요소별 허용 및 제거 목록 추가 조정.
-
만약 configuration["
elements"] 존재한다면:-
각 element에 대해 configuration["
elements"]:-
element["
attributes"] 기본값 « »에 attribute 포함시:-
attribute 제거; element["
attributes"]에서.
-
-
element["
removeAttributes"] 기본값 « »에 attribute 포함시:-
attribute 제거; element["
removeAttributes"]에서.
-
-
-
-
attribute를 구성의 removeAttributes에 append configuration["
removeAttributes"] -
true를 반환한다.
-
SanitizerConfig
configuration에서
remove unsafe를 수행하려면 다음과 같이 한다:
참고: 이 알고리즘이 remove unsafe라고 불리지만, 이 명세에서 "unsafe"란 용어는 문서에 삽입시 JavaScript가 실행될 수 있는 콘텐츠만 의미합니다. 즉, 이 메서드는 XSS 위험을 제거하는 역할을 합니다.
-
단언: 키 집합(내장 안전 기준 구성) 는 다음과 같다 « [ "
removeElements", "removeAttributes" ] ». -
result를 false로 설정한다.
-
각 element에 대해 내장 안전 베이스라인 구성["
removeElements"]:-
remove an element를 element에 대해 호출한다. configuration에서.
-
호출 결과가 true라면 result를 true로 설정.
-
-
각 attribute에 대해 내장 안전 베이스라인 구성["
removeAttributes"]:-
remove an attribute를 attribute에 대해 호출한다. configuration에서.
-
호출 결과가 true라면 result를 true로 설정.
-
-
각 attribute에 대해 이벤트 핸들러 content attributes 목록에서:
-
remove an attribute를 attribute에 대해 호출한다. configuration에서.
-
호출 결과가 true라면 result를 true로 설정.
-
-
result를 반환한다.
3.3. 구성 설정
Sanitizer
sanitizer:
-
configuration에 allowCommentsPIsAndDataAttributes로 정규화(Canonicalize)를 수행한다.
-
configuration이 유효(valid)하지 않으면, false를 반환한다.
-
sanitizer의 configuration을 configuration으로 설정한다.
-
true를 반환한다.
3.4. 구성 정규화
Sanitizer는
구성(configuration)을 정규화된 형태로 저장하며, 이는 여러
처리 단계를 더 쉽게 만들기 때문이다.
elements
목록 {elements: ["div"]}는
{elements: [{name: "div", namespace: "http://www.w3.org/1999/xhtml"}]로 저장된다).
SanitizerConfig
configuration을
불리언
allowCommentsPIsAndDataAttributes와 함께
설정을 정규화하려면:
Note: 우리는 configuration이
JavaScript 값을 SanitizerConfig로
변환한 [WebIDL]의
결과라고 가정한다.
-
configuration["
elements"]와 configuration["removeElements"]가 모두 존재하지 않으면, configuration["removeElements"]를 « »로 설정한다. -
configuration["
processingInstructions"]와 configuration["removeProcessingInstructions"]가 모두 존재하지 않으면:-
allowCommentsPIsAndDataAttributes가 true이면, configuration["
removeProcessingInstructions"]를 « »로 설정한다. -
그렇지 않으면, configuration["
processingInstructions"]를 « »로 설정한다.
-
-
configuration["
attributes"]와 configuration["removeAttributes"]가 모두 존재하지 않으면, configuration["removeAttributes"]를 « »로 설정한다. -
configuration["
elements"]가 존재하면:-
elements를 « »라고 하자.
-
configuration["
elements"]의 각 element에 대해 반복한다:-
element에 대해 속성을 가진 sanitizer 요소를 정규화한 결과를 elements에 append한다.
-
-
configuration["
elements"]를 elements로 설정한다.
-
-
configuration["
removeElements"]가 존재하면:-
elements를 « »라고 하자.
-
configuration["
removeElements"]의 각 element에 대해 반복한다:-
element에 대해 sanitizer 요소를 정규화한 결과를 elements에 append한다.
-
-
configuration["
removeElements"]를 elements로 설정한다.
-
-
configuration["
replaceWithChildrenElements"]가 존재하면:-
elements를 « »라고 하자.
-
configuration["
replaceWithChildrenElements"]의 각 element에 대해 반복한다:-
element에 대해 sanitizer 요소를 정규화한 결과를 elements에 append한다.
-
-
configuration["
replaceWithChildrenElements"]를 elements로 설정한다.
-
-
configuration["
processingInstructions"]가 존재하면:-
processingInstructions를 « »라고 하자.
-
configuration["
processingInstructions"]의 각 pi에 대해 반복한다:-
pi에 대해 sanitizer 처리 명령을 정규화한 결과를 processingInstructions에 append한다.
-
-
configuration["
processingInstructions"]를 processingInstructions로 설정한다.
-
-
configuration["
removeProcessingInstructions"]가 존재하면:-
processingInstructions를 « »라고 하자.
-
configuration["
removeProcessingInstructions"]의 각 pi에 대해 반복한다:-
pi에 대해 sanitizer 처리 명령을 정규화한 결과를 processingInstructions에 append한다.
-
-
configuration["
removeProcessingInstructions"]를 processingInstructions로 설정한다.
-
-
configuration["
attributes"]가 존재하면:-
attributes를 « »라고 하자.
-
configuration["
attributes"]의 각 attribute에 대해 반복한다:-
attribute에 대해 sanitizer 속성을 정규화한 결과를 attributes에 append한다.
-
-
configuration["
attributes"]를 attributes로 설정한다.
-
-
configuration["
removeAttributes"]가 존재하면:-
attributes를 « »라고 하자.
-
configuration["
removeAttributes"]의 각 attribute에 대해 반복한다:-
attribute에 대해 sanitizer 속성을 정규화한 결과를 attributes에 append한다.
-
-
configuration["
removeAttributes"]를 attributes로 설정한다.
-
-
configuration["
comments"]가 존재하지 않으면, configuration["comments"]를 allowCommentsPIsAndDataAttributes로 설정한다. -
configuration["
attributes"]가 존재하고 configuration["dataAttributes"]가 존재하지 않으면, configuration["dataAttributes"]를 allowCommentsPIsAndDataAttributes로 설정한다.
SanitizerElementWithAttributes
element인
속성을 가진 sanitizer 요소를
정규화하려면:
-
result를 element와 함께 sanitizer 요소를 정규화한 결과라고 하자.
-
element가 dictionary이면:
-
element["
attributes"]가 존재하면:-
attributes를 « »라고 하자.
-
element["
attributes"]의 각 attribute에 대해 반복한다:-
attribute와 함께 sanitizer 속성을 정규화한 결과를 attributes에 append한다.
-
-
result["
attributes"]를 attributes로 설정한다.
-
-
element["
removeAttributes"]가 존재하면:-
attributes를 « »라고 하자.
-
element["
removeAttributes"]의 각 attribute에 대해 반복한다:-
attribute와 함께 sanitizer 속성을 정규화한 결과를 attributes에 append한다.
-
-
result["
removeAttributes"]를 attributes로 설정한다.
-
-
-
result["
attributes"]와 result["removeAttributes"]가 모두 존재하지 않으면:-
result["
removeAttributes"]를 « »로 설정한다.
-
-
result를 반환한다.
SanitizerElement
element,
sanitizer name 정규화를
element와
HTML namespace를 기본 namespace로 하여 수행한 결과를 반환한다.
-
단언: pi는
DOMString또는 dictionary 중 하나이다. -
만약 pi가
DOMString라면, «[ "target" → pi ]»를 반환한다. -
단언: pi는 dictionary이며 pi["target"]가 존재한다.
-
«[ "
target" → pi["target"] ]»를 반환한다.
SanitizerAttribute
attribute,
null을 기본 namespace로 하여 sanitizer name 정규화를 수행한 결과를 반환한다.
-
단언: name은
DOMString또는 dictionary 중 하나이다. -
만약 name이
DOMString라면, «[ "name" → name, "namespace" → defaultNamespace]»를 반환한다. -
단언: name은 dictionary이며, name["name"]과 name["namespace"]가 모두 존재한다.
-
name["namespace"]가 빈 문자열이면, 이를 null로 설정한다.
-
«[
"name" → name["name"],
"namespace" → name["namespace"]
]»를 반환한다.
3.5. 지원 알고리즘
이 명세에서 사용하는 정규화된
element
및 attribute name
목록의 경우,
목록 멤버십은 "name"과 "namespace"
항목이 모두 일치하는지에 기반한다:
-
만약 list가 name을 포함한다면, 아무 작업도 하지 않고 return.
-
만약 itemA["namespace"]가 null이라면:
-
itemB["namespace"]가 null이 아니면 true 반환.
-
-
그 외:
-
itemB["namespace"]가 null이면 false 반환.
-
itemA["namespace"]가 code unit less than itemB["namespace"]이면 true 반환.
-
itemA["namespace"]가 itemB["namespace"]와 다르다면 false 반환.
-
-
마지막으로 itemA["name"]이 code unit less than itemB["name"]이면 true, 아니면 false 반환.
SanitizerElement를
포함하는 두 list
A와 B의
intersection은 set
intersection과 같지만,
set
항목이 미리 정규화된다는 점이 다르다:
-
set A를 « [] »라고 하자.
-
set B를 « [] »라고 하자.
-
A의 각 entry에 대해 반복하여, entry에 대해 sanitizer 이름을 정규화한 결과를 set A에 append한다.
-
B의 각 entry에 대해 반복하여, entry에 대해 sanitizer 이름을 정규화한 결과를 set B에 append한다.
-
set A와 set B의 intersection을 반환한다.
3.6. 내장값
다섯 가지 내장(builtin)이 있습니다:
-
내장 애니메이팅 URL 속성 목록, 그리고
내장 안전 기본 구성은 다음과 같다:
{ "elements" : [ { "name" : "math" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "merror" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mfrac" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mi" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mmultiscripts" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mn" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mo" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "fence" , "namespace" : null }, { "name" : "form" , "namespace" : null }, { "name" : "largeop" , "namespace" : null }, { "name" : "lspace" , "namespace" : null }, { "name" : "maxsize" , "namespace" : null }, { "name" : "minsize" , "namespace" : null }, { "name" : "movablelimits" , "namespace" : null }, { "name" : "rspace" , "namespace" : null }, { "name" : "separator" , "namespace" : null }, { "name" : "stretchy" , "namespace" : null }, { "name" : "symmetric" , "namespace" : null } ] }, { "name" : "mover" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "accent" , "namespace" : null } ] }, { "name" : "mpadded" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "depth" , "namespace" : null }, { "name" : "height" , "namespace" : null }, { "name" : "lspace" , "namespace" : null }, { "name" : "voffset" , "namespace" : null }, { "name" : "width" , "namespace" : null } ] }, { "name" : "mphantom" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mprescripts" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mroot" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mrow" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "ms" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mspace" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "depth" , "namespace" : null }, { "name" : "height" , "namespace" : null }, { "name" : "width" , "namespace" : null } ] }, { "name" : "msqrt" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mstyle" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "msub" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "msubsup" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "msup" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mtable" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mtd" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "columnspan" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null } ] }, { "name" : "mtext" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mtr" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "munder" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "accentunder" , "namespace" : null } ] }, { "name" : "munderover" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "accent" , "namespace" : null }, { "name" : "accentunder" , "namespace" : null } ] }, { "name" : "semantics" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "a" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "href" , "namespace" : null }, { "name" : "hreflang" , "namespace" : null }, { "name" : "type" , "namespace" : null } ] }, { "name" : "abbr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "address" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "article" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "aside" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "b" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "bdi" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "bdo" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "blockquote" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null } ] }, { "name" : "body" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "br" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "caption" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "cite" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "code" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "col" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "span" , "namespace" : null } ] }, { "name" : "colgroup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "span" , "namespace" : null } ] }, { "name" : "data" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "value" , "namespace" : null } ] }, { "name" : "dd" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "del" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null }, { "name" : "datetime" , "namespace" : null } ] }, { "name" : "dfn" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "div" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "dl" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "dt" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "em" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "figcaption" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "figure" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "footer" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h1" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h2" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h3" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h4" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h5" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h6" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "head" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "header" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "hgroup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "hr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "html" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "i" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ins" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null }, { "name" : "datetime" , "namespace" : null } ] }, { "name" : "kbd" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "li" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "value" , "namespace" : null } ] }, { "name" : "main" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "mark" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "menu" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "nav" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ol" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "reversed" , "namespace" : null }, { "name" : "start" , "namespace" : null }, { "name" : "type" , "namespace" : null } ] }, { "name" : "p" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "pre" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "q" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "rp" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "rt" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ruby" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "s" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "samp" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "search" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "section" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "small" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "span" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "strong" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "sub" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "sup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "table" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "tbody" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "td" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "colspan" , "namespace" : null }, { "name" : "headers" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null } ] }, { "name" : "tfoot" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "th" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "abbr" , "namespace" : null }, { "name" : "colspan" , "namespace" : null }, { "name" : "headers" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null }, { "name" : "scope" , "namespace" : null } ] }, { "name" : "thead" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "time" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "datetime" , "namespace" : null } ] }, { "name" : "title" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "tr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "u" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ul" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "var" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "wbr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "a" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "href" , "namespace" : null }, { "name" : "hreflang" , "namespace" : null }, { "name" : "type" , "namespace" : null } ] }, { "name" : "circle" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "cx" , "namespace" : null }, { "name" : "cy" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null }, { "name" : "r" , "namespace" : null } ] }, { "name" : "defs" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "desc" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "ellipse" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "cx" , "namespace" : null }, { "name" : "cy" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null }, { "name" : "rx" , "namespace" : null }, { "name" : "ry" , "namespace" : null } ] }, { "name" : "foreignObject" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "height" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "g" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "line" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "x1" , "namespace" : null }, { "name" : "x2" , "namespace" : null }, { "name" : "y1" , "namespace" : null }, { "name" : "y2" , "namespace" : null } ] }, { "name" : "marker" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "markerHeight" , "namespace" : null }, { "name" : "markerUnits" , "namespace" : null }, { "name" : "markerWidth" , "namespace" : null }, { "name" : "orient" , "namespace" : null }, { "name" : "preserveAspectRatio" , "namespace" : null }, { "name" : "refX" , "namespace" : null }, { "name" : "refY" , "namespace" : null }, { "name" : "viewBox" , "namespace" : null } ] }, { "name" : "metadata" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "path" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "d" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null } ] }, { "name" : "polygon" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "points" , "namespace" : null } ] }, { "name" : "polyline" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "points" , "namespace" : null } ] }, { "name" : "rect" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "height" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null }, { "name" : "rx" , "namespace" : null }, { "name" : "ry" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "svg" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "height" , "namespace" : null }, { "name" : "preserveAspectRatio" , "namespace" : null }, { "name" : "viewBox" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "text" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "dx" , "namespace" : null }, { "name" : "dy" , "namespace" : null }, { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "rotate" , "namespace" : null }, { "name" : "textLength" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "textPath" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "method" , "namespace" : null }, { "name" : "path" , "namespace" : null }, { "name" : "side" , "namespace" : null }, { "name" : "spacing" , "namespace" : null }, { "name" : "startOffset" , "namespace" : null }, { "name" : "textLength" , "namespace" : null } ] }, { "name" : "title" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "tspan" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "dx" , "namespace" : null }, { "name" : "dy" , "namespace" : null }, { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "rotate" , "namespace" : null }, { "name" : "textLength" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] } ], "processingInstructions" : [], "attributes" : [ { "name" : "alignment-baseline" , "namespace" : null }, { "name" : "baseline-shift" , "namespace" : null }, { "name" : "clip-path" , "namespace" : null }, { "name" : "clip-rule" , "namespace" : null }, { "name" : "color" , "namespace" : null }, { "name" : "color-interpolation" , "namespace" : null }, { "name" : "cursor" , "namespace" : null }, { "name" : "dir" , "namespace" : null }, { "name" : "direction" , "namespace" : null }, { "name" : "display" , "namespace" : null }, { "name" : "displaystyle" , "namespace" : null }, { "name" : "dominant-baseline" , "namespace" : null }, { "name" : "fill" , "namespace" : null }, { "name" : "fill-opacity" , "namespace" : null }, { "name" : "fill-rule" , "namespace" : null }, { "name" : "font-family" , "namespace" : null }, { "name" : "font-size" , "namespace" : null }, { "name" : "font-size-adjust" , "namespace" : null }, { "name" : "font-stretch" , "namespace" : null }, { "name" : "font-style" , "namespace" : null }, { "name" : "font-variant" , "namespace" : null }, { "name" : "font-weight" , "namespace" : null }, { "name" : "lang" , "namespace" : null }, { "name" : "letter-spacing" , "namespace" : null }, { "name" : "marker-end" , "namespace" : null }, { "name" : "marker-mid" , "namespace" : null }, { "name" : "marker-start" , "namespace" : null }, { "name" : "mathbackground" , "namespace" : null }, { "name" : "mathcolor" , "namespace" : null }, { "name" : "mathsize" , "namespace" : null }, { "name" : "opacity" , "namespace" : null }, { "name" : "paint-order" , "namespace" : null }, { "name" : "pointer-events" , "namespace" : null }, { "name" : "scriptlevel" , "namespace" : null }, { "name" : "shape-rendering" , "namespace" : null }, { "name" : "stop-color" , "namespace" : null }, { "name" : "stop-opacity" , "namespace" : null }, { "name" : "stroke" , "namespace" : null }, { "name" : "stroke-dasharray" , "namespace" : null }, { "name" : "stroke-dashoffset" , "namespace" : null }, { "name" : "stroke-linecap" , "namespace" : null }, { "name" : "stroke-linejoin" , "namespace" : null }, { "name" : "stroke-miterlimit" , "namespace" : null }, { "name" : "stroke-opacity" , "namespace" : null }, { "name" : "stroke-width" , "namespace" : null }, { "name" : "text-anchor" , "namespace" : null }, { "name" : "text-decoration" , "namespace" : null }, { "name" : "text-overflow" , "namespace" : null }, { "name" : "text-rendering" , "namespace" : null }, { "name" : "title" , "namespace" : null }, { "name" : "transform" , "namespace" : null }, { "name" : "transform-origin" , "namespace" : null }, { "name" : "unicode-bidi" , "namespace" : null }, { "name" : "vector-effect" , "namespace" : null }, { "name" : "visibility" , "namespace" : null }, { "name" : "white-space" , "namespace" : null }, { "name" : "word-spacing" , "namespace" : null }, { "name" : "writing-mode" , "namespace" : null } ], "comments" : false , "dataAttributes" : false }
참고: 포함된 [MathML] 마크업은 [SafeMathML]를 기반으로 합니다.
내장 안전 기준 구성은 스크립트 콘텐츠만 차단하도록 설계되었습니다. 내용은 다음과 같습니다:
{ "removeElements" : [ { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "embed" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "frame" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "iframe" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "object" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "use" } ], "removeAttributes" : [] }
경고: remove unsafe 알고리즘은 이벤트 핸들러 콘텐츠 속성도 추가로 제거할 것을 명시합니다. 이 속성은 [HTML]에서 정의된 것입니다. 만약 사용자 에이전트가 [HTML] 명세를 확장하여 추가적인 이벤트 핸들러 콘텐츠 속성을 정의한다면, 그러한 속성을 어떻게 처리할지는 해당 사용자 에이전트의 책임입니다. 현재의 이벤트 핸들러 콘텐츠 속성 목록을 사용할 때, 안전 기준 구성은 실제로 다음과 같은 모습입니다:
{ "removeElements" : [ { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "embed" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "frame" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "iframe" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "object" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "use" } ], "removeAttributes" : [ "onafterprint" , "onauxclick" , "onbeforeinput" , "onbeforematch" , "onbeforeprint" , "onbeforeunload" , "onbeforetoggle" , "onblur" , "oncancel" , "oncanplay" , "oncanplaythrough" , "onchange" , "onclick" , "onclose" , "oncontextlost" , "oncontextmenu" , "oncontextrestored" , "oncopy" , "oncuechange" , "oncut" , "ondblclick" , "ondrag" , "ondragend" , "ondragenter" , "ondragleave" , "ondragover" , "ondragstart" , "ondrop" , "ondurationchange" , "onemptied" , "onended" , "onerror" , "onfocus" , "onformdata" , "onhashchange" , "oninput" , "oninvalid" , "onkeydown" , "onkeypress" , "onkeyup" , "onlanguagechange" , "onload" , "onloadeddata" , "onloadedmetadata" , "onloadstart" , "onmessage" , "onmessageerror" , "onmousedown" , "onmouseenter" , "onmouseleave" , "onmousemove" , "onmouseout" , "onmouseover" , "onmouseup" , "onoffline" , "ononline" , "onpagehide" , "onpagereveal" , "onpageshow" , "onpageswap" , "onpaste" , "onpause" , "onplay" , "onplaying" , "onpopstate" , "onprogress" , "onratechange" , "onreset" , "onresize" , "onrejectionhandled" , "onscroll" , "onscrollend" , "onsecuritypolicyviolation" , "onseeked" , "onseeking" , "onselect" , "onslotchange" , "onstalled" , "onstorage" , "onsubmit" , "onsuspend" , "ontimeupdate" , "ontoggle" , "onunhandledrejection" , "onunload" , "onvolumechange" , "onwaiting" , "onwheel" ] }
javascript:" 탐색이 "unsafe"로 간주되는 내장 navigating URL 속성 목록은 다음과 같다:
«[
[
{ "name" → "a", "namespace" → HTML 네임스페이스
},
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "area", "namespace" → HTML 네임스페이스
},
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "base", "namespace" → HTML 네임스페이스
},
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "button", "namespace" → HTML 네임스페이스
},
{ "name" → "formaction", "namespace" → null }
],
[
{ "name" → "form", "namespace" → HTML 네임스페이스
},
{ "name" → "action", "namespace" → null }
],
[
{ "name" → "input", "namespace" → HTML 네임스페이스
},
{ "name" → "formaction", "namespace" → null }
],
[
{ "name" → "a", "namespace" → SVG 네임스페이스 },
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "a", "namespace" → SVG 네임스페이스 },
{ "name" → "href", "namespace" → XLink
네임스페이스 }
],
]»
내장
애니메이팅 URL 속성 목록은
[SVG11]에서 선언적으로
네비게이션 요소를 "javascript:" URL로 수정하는 데 쓸 수 있으며, 다음과 같다:
«[
[
{ "name" → "animate", "namespace" → SVG 네임스페이스 },
{ "name" → "attributeName", "namespace" → null] }
],
[
{ "name" → "animateTransform", "namespace" → SVG 네임스페이스 },
{ "name" → "attributeName", "namespace" → null }
],
[
{ "name" → "set", "namespace" → SVG 네임스페이스 },
{ "name" → "attributeName", "namespace" → null }
],
]»
내장 대체 불가 요소 목록에는 요소를 자식으로 대체해선 안 되는 요소들이 들어있으며, 그렇게 하면 재파싱 문제나 잘못된 노드 트리가 생길 수 있다.
«[
{ "name" → "html", "namespace" → HTML 네임스페이스 },
{ "name" → "svg", "namespace" → SVG 네임스페이스 },
{ "name" → "math", "namespace" → MathML
네임스페이스 },
]»
이러한 제한만으로 모든 형태의 재파싱 공격을 막을 수 있는 것은 아니다. 예를 들어:
// 잘못된 예시 let sanitizer= { replaceWithChildrenElements: [ "tr" ] }; div. setHTML( "<table><tbody><tr><td><div>" , { sanitizer}); div. innerHTML= div. innerHTML; // div.innerHTML은 이제 `<div></div><table><tbody><tr></tr></tbody></table>`
그러나 아래 예시에서 <iframe>이 재파싱되어
일반적인 HTML iframe이 되는 것은 방지된다:
4. 보안 고려사항
Sanitizer API는 주어진 HTML 콘텐츠를 순회하며 구성에 따라 요소와 속성을 제거함으로써 DOM 기반 크로스 사이트 스크립팅(XSS)을 방지하기 위한 것입니다. 이 명세의 API는 스크립트 실행이 가능한 마크업이 남게 되는 Sanitizer 객체의 생성을 지원해서는 안 되며, 그렇게 된다면 이는 위협 모델 상의 버그입니다.
그렇다 하더라도, Sanitizer API의 올바른 사용만으로는 방지할 수 없는 보안 이슈들이 있으며, 이러한 시나리오들이 다음 절에서 설명됩니다.
4.1. 서버 사이드 반사 및 저장 XSS
이 절은 규범(normative)이 아닙니다.
Sanitizer API는 DOM 내에서만 동작하며, 기존 DocumentFragment를 순회 및 필터링하는 기능을 추가합니다. Sanitizer는 서버 측 반사(reflected) 또는 저장(stored) XSS를 다루지 않습니다.
4.2. DOM 클로버링
이 절은 규범(normative)이 아닙니다.
DOM 클로버링은 악의적인 HTML이 id나 name 속성을 통해 요소 이름을 지정함으로써, HTML 요소의 children과
같은 프로퍼티가 악성 콘텐츠에 의해 가려지는 공격을 의미합니다.
Sanitizer API는 기본 상태에서는 DOM 클로버링 공격을 막지 못하지만, id 및 name 속성을 제거하도록 구성할 수 있습니다.
4.3. 스크립트 gadget을 이용한 XSS
이 절은 규범(normative)이 아닙니다.
스크립트 gadget은 공격자가 인기있는 JavaScript 라이브러리의 기존 애플리케이션 코드를 활용하여 자신의 코드를 실행하게 만드는 기법입니다. 종종 무해해 보이는 코드나 inert DOM 노드를 삽입해 프레임워크가 이를 파싱 및 해석하여 JavaScript 실행에 이용하게 하는 방식입니다.
Sanitizer API만으로는 이러한 공격을 막을 수 없으며, 페이지 저자는 일반적으로 알 수 없는 요소를 명시적으로 허용해야 하고, data- 및
slot 속성, <slot>, <template>과 같은 템플릿/프레임워크 전용 마크업에 대해서도 명시적으로
허용/제거 구성을 해야 합니다. 이러한 제한은 완전하지 않으므로, 페이지 저자에게는 사용하는 서드파티 라이브러리의 동작을 면밀히 검토할 것을 권장합니다.
4.4. Mutated XSS
이 절은 규범(normative)이 아닙니다.
Mutated XSS(mXSS)는 HTML 조각을 올바른 컨텍스트 없이 파싱할 때 발생하는 파서 컨텍스트 불일치에 기반한 공격입니다. 특히, 파싱된 HTML 프래그먼트를 문자열로 직렬화한 뒤, 다른 부모 요소에 삽입하여 재파싱하면 파싱 및 해석 결과가 동일하다고 보장할 수 없습니다. 예를 들어 foreign content나 잘못 중첩된 태그의 파싱 동작 변화에 의존하여 공격이 이루어질 수 있습니다.
Sanitizer API는 문자열을 노드 트리로 변환하는 함수만 제공합니다. 컨텍스트는 모든 sanitizer 함수에서 암묵적으로 제공됩니다:
Element.setHTML()은 현재 요소를, Document.parseHTML()은 새 문서를 사용합니다. 따라서 Sanitizer API는
mutated XSS에 직접적으로 영향을 받지 않습니다.
개발자가 정리된(sanitized) 노드 트리를 .innerHTML 등으로 문자열로 변환한 뒤, 이를 다시 파싱한다면 mutated XSS가 발생할 수 있습니다. 이러한
방식은 권장하지 않습니다. HTML을 어쩔 수 없이 문자열로 처리/전달해야 한다면, 해당 문자열은 신뢰할 수 없는 것으로 간주하고 DOM에 삽입할 때 반드시 다시 한 번 정리해야 합니다. 즉,
정리되고 직렬화된 HTML 트리는 더 이상 정리된 것으로 간주할 수 없습니다.
mXSS에 대해 보다 자세한 내용은 [MXSS]를 참고하십시오.
5. 감사의 글
이 작업은 cure53의 [DOMPURIFY],
Internet Explorer의 window.toStaticHTML()
그리고 Ben Bucksch의
[HTMLSanitizer]에서 영감을
얻었습니다. Anne van Kesteren, Krzysztof Kotowicz,
Andrew C. H. Mc Millan, Tom Schuster, Luke Warlow, Guillaume Weghsteen,
그리고 Mike West가 소중한 피드백을 주셨습니다.