web-dev-qa-db-ja.com

クライアント側でのHTMLのサニタイズ/書き換え

クロスドメインリクエストを介して読み込まれた外部リソースを表示し、「safe」コンテンツのみを表示する必要があります。

プロトタイプの String#stripScripts を使用して、スクリプトブロックを削除できます。ただし、onclickonerrorなどのハンドラーはまだ存在しています。

少なくともできるライブラリはありますか

  • スクリプトブロックの削除、
  • dOMハンドラーを殺す、
  • ブラックリストのタグを削除します(例:embedまたはobject)。

JavaScript関連のリンクや例はありますか?

70
aemkei

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)>onerrorハンドラーを実行するため、XSSインジェクションに対して脆弱です。

102
Mike Samuel

Google Caja HTML sanitizer は、 web worker に埋め込むことで「Web対応」にすることができます。サニタイザーによって導入されたグローバル変数はすべてワーカー内に含まれ、さらに処理は独自のスレッドで行われます。

Web Workersをサポートしないブラウザーの場合、iframeをサニタイザーが動作する個別の環境として使用できます。TimothyChienには polyfill があり、iframeを使用してWeb Workersをシミュレートします。その部分は私たちのために行われます。

Cajaプロジェクトには Cajaをスタンドアロンのクライアント側サニタイザーとして使用する方法 :のwikiページがあります。

  • ソースをチェックアウトし、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));
};
_

(simworkerライブラリを動作させるにはもう少しコードが必要ですが、この議論では重要ではありません。)

デモ: https://dl.dropbox.com/u/291406/html-sanitize/demo.html

38
Jeffery To

クライアントを信頼しないでください。サーバーアプリケーションを作成している場合、クライアントは常に不衛生な悪意のあるデータを送信すると想定します。それはあなたをトラブルから守る経験則です。可能であれば、サーバーコードですべての検証とサニテーションを行うことをお勧めします。サーバーコードは(ある程度は)修正されることはありません。おそらく、サーバーサイドWebアプリケーションをクライアントサイドコードのプロキシとして使用できます。これは、サードパーティからフェッチし、クライアント自体に送信する前に衛生管理を行いますか?

[編集]すみません、質問を誤解しました。しかし、私は自分のアドバイスを支持します。サーバーに送信する前にサーバーでサニタイズすれば、ユーザーはおそらくより安全になります。

16
Sean Edwards

一部のブラウザがブラックリストへの登録を回避するためにトリップする可能性のある、すべての奇妙な不正な形式のマークアップを予測することはできないため、ブラックリストに登録しないでください。 多くスクリプト/埋め込み/オブジェクトとハンドラーよりも削除する必要のある構造が多くあります。

代わりに、HTMLを階層内の要素と属性に解析し、可能な限り最小限のホワイトリストに対してすべての要素と属性名を実行しようとします。また、許可したURL属性をホワイトリストに対してチェックします(javascript:よりも危険なプロトコルがあることに注意してください)。

入力が整形式のXHTMLである場合、上記の最初の部分ははるかに簡単です。

HTMLのサニタイズと同様に、それを回避する他の方法を見つけることができる場合は、代わりにそれを行います。多くの潜在的な穴があります。この長い年月を経ても、主要なウェブメールサービスが依然としてエクスプロイトを発見している場合、どうすればより良い結果が得られると思いますか?

12
bobince

すべての主要なブラウザがサンドボックス化されたiframeをサポートするようになったので、I thinkを安全にすることができるはるかに簡単な方法があります。この種のセキュリティ問題に精通している人々がこの回答をレビューできるといいと思います。

注:このメソッドは、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 Beta、IE 11、Chrome Androidの場合)、および1つの古いブラウザー(IE 8)スクリプトを実行する前にそれが保たれていることを確認するために、問題を抱えているブラウザ、または見落としているEdgeケースがあるかどうかを知りたいです。

11
aldel

だから、2016年であり、私たちの多くは現在、コードでnpmモジュールを使用していると思います。 _sanitize-html_ はnpmの主要なオプションのようですが、 others があります。

この質問に対する他の回答は、独自の方法を提供するための優れた情報を提供しますが、これは十分にテストされたコミュニティソリューションがおそらく最良の回答であるため、トリッキーな問題です。

コマンドラインでこれを実行してインストールします:_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);

8
ericsoco
String.prototype.sanitizeHTML=function (white,black) {
   if (!white) white="b|i|p|br";//allowed tags
   if (!black) black="script|object|embed";//complete remove tags
   var e=new RegExp("(<("+black+")[^>]*>.*</\\2>|(?!<[/]?("+white+")(\\s[^<]*>|[/]>|>))<[^<>]*>|(?!<[^<>\\s]+)\\s[^</>]+(?=[/>]))", "gi");
   return this.replace(e,"");
}

-ブラックリスト->タグとコンテンツの完全な削除

-ホワイトリスト->タグを保持

-他のタグは削除されますが、タグの内容は保持されます

-ホワイトリストタグ(残りのもの)のすべての属性が削除されます

5
neu-rah

上記で提案したGoogle Cajaライブラリは、構成が複雑すぎてWebアプリケーションのプロジェクトに含めることができません(したがって、ブラウザで実行されます)。 CKEditorコンポーネントを既に使用しているため、代わりに使用したのは、組み込みのHTMLサニタイズおよびホワイトリスト機能を使用することで、これは設定がはるかに簡単です。したがって、CKEditorインスタンスを非表示のiframeにロードして、次のようなことを実行できます。

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

確かに、プロジェクトでCKEditorを使用していない場合、コンポーネント自体が約0.5メガバイト(最小化)であるため、これは少しやり過ぎかもしれませんが、ソースがある場合は、コードを分離することができますホワイトリスト(CKEDITOR.htmlParser?)を短くします。

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

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

1
AsGoodAsItGets

[免責事項:私は著者の1人です]

このための「Web専用」(つまり「ブラウザが必要」)オープンソースライブラリ https://github.com/jitbit/HtmlSanitizer を作成しました。これは、tags/attributes/stylesホワイトリストに登録された」もの。

使用法:

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

追伸ブラウザを使用してDOMを解析および操作するため、「純粋なJavaScript」ソリューションよりもはるかに高速に動作します。 「純粋なJS」ソリューションに興味がある場合は、 https://github.com/punkave/sanitize-html (提携していない)を試してください。

1
Alex

私はあなたの人生からフレームワークをカットすることをお勧めします。それは長期的にあなたにとって物事を過度に簡単にするでしょう。

cloneNode:ノードを複製すると、すべての属性とその値がコピーされますが、はコピーされます[〜#〜] not [〜#〜]イベントリスナーをコピーします

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

私はしばらくの間ツリーウォーカーを使用していますが、JavaScriptの最も過小評価されている部分の1つですが、以下はテストされていません。以下に、クロールできるノードタイプのリストを示します。通常、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.');
 }
}
0
John