클라이언트 측에서 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.