클라이언트 측에서 HTML 삭제 / 재 작성


82

도메인 간 요청을 통해로드 된 외부 리소스를 표시하고 " 안전한 "콘텐츠 만 표시해야 합니다.

Prototype의 String # stripScripts 를 사용하여 스크립트 블록을 제거 할 수 있습니다 . 그러나 onclick또는 같은 핸들러 onerror는 여전히 존재합니다.

적어도 할 수있는 도서관이 있습니까?

  • 스트립 스크립트 블록,
  • DOM 핸들러를 죽이고,
  • 검은 색으로 나열된 태그를 제거합니다 (예 : embed또는 object).

그렇다면 JavaScript 관련 링크와 예제가 있습니까?


12
정규 표현식하여이 작업을 수행 할 수있는 답변 신뢰하지 마십시오 stackoverflow.com/questions/1732348/...
미코 Ohtamaa


이게 어떻게 안전한가요? 사용자가 페이지의 자바 스크립트를 편집 할 수 없습니까?
Daniel 말한다 Reinstate Monica

예, 단순히 신뢰할 수있는 사용자의 실수를 방지하려는 것이 아니라면 '안전'하지 않습니다.
스콧

답변:


112

2016 년 업데이트 : 이제 Caja 새니 타이 저를 기반으로 하는 Google Closure 패키지가 있습니다.

더 깨끗한 API를 가지고 있으며 최신 브라우저에서 사용 가능한 API를 고려하여 재 작성되었으며 Closure Compiler와 더 잘 상호 작용합니다.


뻔뻔한 플러그 : 철저하게 검토 된 클라이언트 측 html 새니 타이 저에 대해서는 caja / plugin / html-sanitizer.js 를 참조하십시오 .

블랙리스트가 아닌 화이트리스트에 있지만 화이트리스트는 CajaWhitelists에 따라 구성 할 수 있습니다.


모든 태그를 제거하려면 다음을 수행하십시오.

var tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';

var tagOrComment = new RegExp(
    '<(?:'
    // Comment body.
    + '!--(?:(?:-*[^->])*--+|-?)'
    // Special "raw text" elements whose content should be elided.
    + '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
    + '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
    // Regular name
    + '|/?[a-z]'
    + tagBody
    + ')>',
    'gi');
function removeTags(html) {
  var oldHtml;
  do {
    oldHtml = html;
    html = html.replace(tagOrComment, '');
  } while (html !== oldHtml);
  return html.replace(/</g, '&lt;');
}

사람들은 당신이 요소를 생성하고 할당 할 수 있음을 알려드립니다 innerHTML다음을 얻을 innerText또는 textContent점에서 엔티티를하고 탈출. 그거 하지마. 노드가 DOM에 연결되지 않은 경우에도 핸들러 <img src=bogus onerror=alert(1337)>를 실행 하므로 XSS 주입에 취약합니다 onerror.



3
Caja HTML 새니 타이 저 코드는 멋져 보이지만 글루 코드 (근접 cssparser.js하지만 더 중요한 것은 html4객체)가 필요합니다. 또한 전역 window속성을 오염시킵니다 . 이 코드의 웹 버전이 있습니까? 그렇지 않다면 별도의 프로젝트를 만드는 것보다 하나를 생산하고 유지하는 더 좋은 방법이 있습니까?
phihag 2012-07-03

1
@phihag, google-caja-discuss 에서 물어 보면 패키지화 된 것을 가리킬 수 있습니다. 창 개체 오염은 이전 버전과의 호환성을위한 것이라고 생각하므로 새 패키지 버전에는 필요하지 않을 수 있습니다.
Mike Samuel

1
이미 웹 브라우저 용 패키지가 있다는 것이 밝혀졌습니다 .
phihag

2
@phihag 해당 패키지는 브라우저가 아닌 nodejs 용입니다.
Jeffery To

40

Google Caja HTML 새니 타이 저는 웹 작업자 에 삽입하여 '웹 지원'으로 만들 수 있습니다 . 새니 타이 저에 의해 도입 된 모든 전역 변수는 작업자 내에 포함되며 자체 스레드에서 처리가 수행됩니다.

웹 워커를 지원하지 않는 브라우저의 경우 새니 타이 저가 작동 할 별도의 환경으로 iframe을 사용할 수 있습니다. Timothy Chien에는 웹 워커를 시뮬레이션하기 위해 iframe을 사용하여이 부분을 수행 하는 폴리 필 이 있습니다.

Caja 프로젝트에는 Caja를 독립형 클라이언트 측 새니 타이 저로 사용하는 방법대한 위키 페이지 가 있습니다 .

  • 소스를 확인한 다음 실행하여 빌드하십시오. ant
  • 페이지에 html-sanitizer-minified.js또는 포함html-css-sanitizer-minified.js
  • 요구 html_sanitize(...)

작업자 스크립트는 다음 지침 만 따르면됩니다.

importScripts('html-css-sanitizer-minified.js'); // or 'html-sanitizer-minified.js'

var urlTransformer, nameIdClassTransformer;

// customize if you need to filter URLs and/or ids/names/classes
urlTransformer = nameIdClassTransformer = function(s) { return s; };

// when we receive some HTML
self.onmessage = function(event) {
    // sanitize, then send the result back
    postMessage(html_sanitize(event.data, urlTransformer, nameIdClassTransformer));
};

(심 워커 라이브러리를 작동 시키려면 더 많은 코드가 필요하지만이 토론에서는 중요하지 않습니다.)

데모 : https://dl.dropbox.com/u/291406/html-sanitize/demo.html


좋은 대답입니다. Jeffrey, 웹 작업자가 삭제를 수행해야하는 이유를 설명해 주시겠습니까?
Austin Wang

@AustinWang 웹 워커가 꼭 필요한 것은 아니지만, 위생 처리는 잠재적으로 계산 비용이 많이 들고 사용자 상호 작용이 필요하지 않기 때문에 작업에 적합합니다. (나는 또한 주요 답변에서 전역 변수를 포함한다고 언급했습니다.)
Jeffery To

이 라이브러리에 대한 적절한 문서를 찾을 수 없습니다. 요소 및 속성의 화이트리스트를 어디에 / 어떻게 지정합니까?
AsGoodAsItGets

설명 @AsGoodAsItGets으로 현재 버전의 코멘트는 , nameIdClassTransformer모든 HTML 이름, 요소 ID와 클래스의 목록이라고합니다; 반환 null하면 속성이 삭제됩니다. src / com / google / caja / lang / html 에서 JSON 파일을 편집하여 허용 목록에 포함되는 요소와 속성을 사용자 정의 할 수도 있습니다.
Jeffery To

@JefferyTo 죄송합니다, 어쩌면 내가 너무 멍청하지만 이해가 안 돼요. 참조하는 JSON 파일은 위의 예제 및 데모에서 사용되지 않습니다. 브라우저에서 라이브러리를 사용하고 싶어서 데모를 보았습니다. 당신은 수정할 수 nameIdClassTranformer모두 거부 예 위의 기능을 <script>태그를 수락 <b><i>태그?
AsGoodAsItGets

20

클라이언트를 신뢰하지 마십시오. 서버 애플리케이션을 작성하는 경우 클라이언트가 항상 비위생적 인 악성 데이터를 제출한다고 가정합니다. 그것은 당신을 문제로부터 지켜줄 경험의 법칙입니다. 가능한 경우 서버 코드에서 모든 유효성 검사 및 위생 처리를 수행하는 것이 좋습니다. 서버 측 웹 애플리케이션을 클라이언트 측 코드의 프록시로 사용할 수 있습니다. 클라이언트 측 코드는 제 3 자에서 가져 와서 클라이언트 자체로 보내기 전에 위생 처리를합니다.

죄송합니다. 질문을 오해했습니다. 그러나 나는 나의 충고를지지한다. 사용자에게 보내기 전에 서버에서 살균하면 사용자가 더 안전 할 것입니다.


19
실제로 node.js의 인기가 높아지면서 자바 스크립트 솔루션도 서버 측 솔루션이 될 수 있습니다. 그게 내가 적어도 여기까지 온 방법입니다. 그래도 이것은 살기 좋은 조언입니다.
Nicholas Flynt

15

이제 모든 주요 브라우저 샌드 박스 iframe을 지원하는, 내가하는 훨씬 간단한 방법이 생각 안전 할 수는. 이런 종류의 보안 문제에 더 익숙한 사람들이이 답변을 검토 할 수 있다면 좋겠습니다.

참고 :이 방법은 IE 9 및 이전 버전에서는 작동하지 않습니다. 샌드 박싱을 지원하는 브라우저 버전 은 이 표 를 참조하십시오 . (참고 : 표에는 Opera Mini에서 작동하지 않는다고 표시되어 있지만 방금 시도했지만 작동했습니다.)

아이디어는 JavaScript가 비활성화 된 숨겨진 iframe을 만들고 신뢰할 수없는 HTML을 붙여넣고 파싱하도록하는 것입니다. 그런 다음 DOM 트리를 살펴보고 안전한 것으로 간주되는 태그와 속성을 복사 할 수 있습니다.

여기에 표시된 화이트리스트는 예시 일뿐입니다. 화이트리스트에 가장 좋은 것은 애플리케이션에 따라 다릅니다. 태그 및 속성의 화이트리스트보다 더 정교한 정책이 필요한 경우,이 예제 코드는 아니지만이 방법으로 수용 할 수 있습니다.

var tagWhitelist_ = {
  'A': true,
  'B': true,
  'BODY': true,
  'BR': true,
  'DIV': true,
  'EM': true,
  'HR': true,
  'I': true,
  'IMG': true,
  'P': true,
  'SPAN': true,
  'STRONG': true
};

var attributeWhitelist_ = {
  'href': true,
  'src': true
};

function sanitizeHtml(input) {
  var iframe = document.createElement('iframe');
  if (iframe['sandbox'] === undefined) {
    alert('Your browser does not support sandboxed iframes. Please upgrade to a modern browser.');
    return '';
  }
  iframe['sandbox'] = 'allow-same-origin';
  iframe.style.display = 'none';
  document.body.appendChild(iframe); // necessary so the iframe contains a document
  iframe.contentDocument.body.innerHTML = input;

  function makeSanitizedCopy(node) {
    if (node.nodeType == Node.TEXT_NODE) {
      var newNode = node.cloneNode(true);
    } else if (node.nodeType == Node.ELEMENT_NODE && tagWhitelist_[node.tagName]) {
      newNode = iframe.contentDocument.createElement(node.tagName);
      for (var i = 0; i < node.attributes.length; i++) {
        var attr = node.attributes[i];
        if (attributeWhitelist_[attr.name]) {
          newNode.setAttribute(attr.name, attr.value);
        }
      }
      for (i = 0; i < node.childNodes.length; i++) {
        var subCopy = makeSanitizedCopy(node.childNodes[i]);
        newNode.appendChild(subCopy, false);
      }
    } else {
      newNode = document.createDocumentFragment();
    }
    return newNode;
  };

  var resultElement = makeSanitizedCopy(iframe.contentDocument.body);
  document.body.removeChild(iframe);
  return resultElement.innerHTML;
};

여기서 시도해 볼 수 있습니다 .

이 예제에서는 스타일 속성과 태그를 허용하지 않습니다. 허용했다면 CSS를 구문 분석하고 목적에 맞는지 확인하고 싶을 것입니다.

여러 최신 브라우저 (Chrome 40, Firefox 36 베타, IE 11, Android 용 Chrome)와 이전 브라우저 (IE 8)에서이를 테스트하여 스크립트를 실행하기 전에 구제되었는지 확인했습니다. 나는 그것에 문제가있는 브라우저가 있는지, 아니면 내가 간과하고있는 어떤 엣지 케이스가 있는지 알고 싶다.


10
이 게시물은 명확하고 가장 간단한 해결책 인 것처럼 보이기 때문에 전문가의 관심을받을 만합니다. 정말 안전합니까?
pwray

"자바 스크립트가 비활성화 된"숨겨진 iframe을 프로그래밍 방식으로 어떻게 만들 수 있습니까? 내가 아는 한 이것은 불가능합니다. 당신이하는 순간 iframe.contentDocument.body.innerHTML = input, 거기에있는 모든 스크립트 태그가 실행될 것입니다.
AsGoodAsItGets

@AsGoodAsItGets-iframe에서 sandbox 속성을 찾습니다.
aldel

1
@aldel 사실, 나는 그것에 대해 몰랐습니다. 우리에게는 IE9의 지원이 부족하기 때문에 여전히 진행되지 않습니다. 귀하의 솔루션이 효과가 있다고 생각하지만 귀하의 응답에서 sandbox속성에 의존한다는 점을 명확히해야한다고 생각 합니다.
AsGoodAsItGets

죄송합니다. "이제 모든 주요 브라우저가 샌드 박스 처리 된 iframe을 지원합니다"라는 내용이 분명하다고 생각했습니다. 덜 미묘한 메모를 추가하겠습니다.
aldel

12

어떤 브라우저가 블랙리스트를 벗어나기 위해 어딘가에 넘어갈 수있는 모든 이상한 유형의 잘못된 마크 업을 예상 할 수는 없으므로 블랙리스트에 올리지 마십시오. 스크립트 / 임베딩 / 객체 및 처리기보다 제거해야 할 구조 가 더 많습니다 .

대신 HTML을 계층 구조의 요소 및 속성으로 구문 분석 한 다음 가능한 최소 허용 목록에 대해 모든 요소 및 속성 이름을 실행하십시오. 또한 허용 된 URL 속성을 허용 목록과 비교하여 확인하십시오 (JavaScript :보다 더 위험한 프로토콜이 있음을 기억하십시오).

입력이 잘 구성된 XHTML 인 경우 위의 첫 번째 부분이 훨씬 쉽습니다.

항상 HTML 삭제를 피할 수있는 다른 방법을 찾을 수 있다면 대신 그렇게하십시오. 많은 잠재적 인 구멍이 있습니다. 몇 년이 지난 후에도 주요 웹 메일 서비스가 여전히 악용을 발견하고 있다면 무엇이 더 잘할 수 있다고 생각합니까?


11

그래서, 2016 년이고 저는 우리 중 많은 사람들이 npm지금 우리 코드에서 모듈을 사용하고 있다고 생각 합니다. sanitize-htmlnpm의 주요 옵션처럼 보이지만 다른 옵션이 있습니다 .

이 질문에 대한 다른 답변은 자신을 롤링하는 방법에 대한 훌륭한 정보를 제공하지만 잘 테스트 된 커뮤니티 솔루션이 아마도 가장 좋은 답변 일 수있을만큼 까다로운 문제입니다.

설치하려면 명령 줄에서 다음을 실행하십시오. npm install --save sanitize-html

ES5 : var sanitizeHtml = require('sanitize-html'); // ... var sanitized = sanitizeHtml(htmlInput);

ES6 : import sanitizeHtml from 'sanitize-html'; // ... let sanitized = sanitizeHtml(htmlInput);


10
2018 년에는 너무 무겁습니다 (의존성의 절반 메가 바이트)
user1464581

2020 년 여기에서 sanitize-html은 Node 용이며 내가 알 수있는 한 브라우저에 대한 좋은 옵션은 아직 없습니다
Mick

3

[면책 조항 : 저자 중 한 명입니다]

우리는이 "웹 전용"이 오픈 소스 라이브러리 (즉, "브라우저가 필요합니다") 쓴 https://github.com/jitbit/HtmlSanitizer을 그 모두 제거 tags/attributes/styles은 "화이트리스트"을 제외한.

용법:

var input = HtmlSanitizer.SanitizeHtml("<script> Alert('xss!'); </scr"+"ipt>");

PS는 브라우저를 사용하여 DOM을 구문 분석하고 조작하기 때문에 "순수 JavaScript"솔루션보다 훨씬 빠르게 작동합니다. "순수 JS"솔루션에 관심이 있으시면 https://github.com/punkave/sanitize-html (제휴되지 않음)을 사용해보십시오.


2

위에서 제안한 Google Caja 라이브러리는 웹 응용 프로그램 (브라우저에서 실행)을 위해 구성하고 내 프로젝트에 포함하기에는 너무 복잡했습니다. 대신 CKEditor 구성 요소를 이미 사용하고 있기 때문에 구성이 훨씬 더 쉬운 내장 HTML 삭제 및 화이트리스트 기능을 사용했습니다. 따라서 숨겨진 iframe에 CKEditor 인스턴스를로드하고 다음과 같은 작업을 수행 할 수 있습니다.

CKEDITOR.instances['myCKEInstance'].dataProcessor.toHtml(myHTMLstring)

당연히, 프로젝트에서 CKEditor를 사용하지 않는 경우 구성 요소 자체가 약 0.5MB (최소화)이기 때문에 약간의 과잉 일 수 있지만 소스가있는 경우 코드를 분리 할 수 ​​있습니다. 화이트리스트 ( CKEDITOR.htmlParser?)를 추가하고 훨씬 더 짧게 만듭니다.

http://docs.ckeditor.com/#!/api

http://docs.ckeditor.com/#!/api/CKEDITOR.htmlDataProcessor


0

나는 당신의 삶에서 틀을 없애는 것을 추천합니다. 그것은 장기적으로 당신을 위해 지나치게 쉽게 만들 것입니다.

cloneNode : 노드를 복제하면 모든 속성과 해당 값 이 복사 되지만 이벤트 리스너 복사 되지 않습니다 .

https://developer.mozilla.org/en/DOM/Node.cloneNode

다음은 내가 트리 워커를 한동안 사용해 왔지만 테스트되지 않았으며 JavaScript에서 가장 저평가 된 부분 중 하나입니다. 크롤링 할 수있는 노드 유형 목록은 다음과 같습니다. 일반적으로 SHOW_ELEMENT 또는 SHOW_TEXT를 사용 합니다.

http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal.html#Traversal-NodeFilter

function xhtml_cleaner(id)
{
 var e = document.getElementById(id);
 var f = document.createDocumentFragment();
 f.appendChild(e.cloneNode(true));

 var walker = document.createTreeWalker(f,NodeFilter.SHOW_ELEMENT,null,false);

 while (walker.nextNode())
 {
  var c = walker.currentNode;
  if (c.hasAttribute('contentEditable')) {c.removeAttribute('contentEditable');}
  if (c.hasAttribute('style')) {c.removeAttribute('style');}

  if (c.nodeName.toLowerCase()=='script') {element_del(c);}
 }

 alert(new XMLSerializer().serializeToString(f));
 return f;
}


function element_del(element_id)
{
 if (document.getElementById(element_id))
 {
  document.getElementById(element_id).parentNode.removeChild(document.getElementById(element_id));
 }
 else if (element_id)
 {
  element_id.parentNode.removeChild(element_id);
 }
 else
 {
  alert('Error: the object or element \'' + element_id + '\' was not found and therefore could not be deleted.');
 }
}

5
이 코드는 정리할 입력이 이미 구문 분석되어 문서 트리에 삽입되었다고 가정합니다. 이 경우 악성 스크립트가 이미 실행 된 것입니다. 입력은 문자열이어야합니다.
phihag

그런 다음 DOM 조각을 주어진 모양이나 형식으로 DOM에 있다고해서 실제로 실행되었음을 의미하지는 않습니다. AJAX를 통해로드한다고 가정하면 importNode와 함께 사용할 수 있습니다.
John
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.