web-dev-qa-db-ja.com

JavascriptでDOMに追加する前のユーザー入力のサニタイズ

空き時間に作業しているチャットアプリケーションのJSを書いています。ユーザーが送信したデータに応じてHTML識別子を変更する必要があります。これは通常、概念的には十分に不安定なものなので、試してさえいませんが、今回は選択の余地があまりありません。その場合、HTML IDをエスケープして、XSSやHTMLの破壊を許可しないようにする必要があります。

コードは次のとおりです。

_var user_id = escape(id)
var txt = '<div class="chut">'+
            '<div class="log" id="chut_'+user_id+'"></div>'+
            '<textarea id="chut_'+user_id+'_msg"></textarea>'+
            '<label for="chut_'+user_id+'_to">To:</label>'+
            '<input type="text" id="chut_'+user_id+'_to" value='+user_id+' readonly="readonly" />'+
            '<input type="submit" id="chut_'+user_id+'_send" value="Message"/>'+
          '</div>';
_

idをエスケープして上記の問題を回避する最良の方法は何でしょうか?ご覧のとおり、今は組み込みのescape()関数を使用していますが、これが他の選択肢と比較してどれだけ優れているかはわかりません。私は、ID自体ではなく、テキストノードに入る前に入力をサニタイズすることに主に慣れています。

Never use escape()。 HTMLエンコードとは関係ありません。これはURLエンコードに似ていますが、適切ではありません。これは、JavaScriptでのみ使用可能な奇妙な非標準のエンコードです。

HTMLエンコーダーが必要な場合は、JavaScriptが提供しないので、自分で作成する必要があります。例えば:

_function encodeHTML(s) {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
}
_

ただし、これは_user_id_を_input value_のような場所に配置するには十分ですが、IDが使用できる文字は限られているため、idには十分ではありません。 (そして_%_はその中にないので、escape()またはencodeURIComponent()さえもダメです。)

IDに任意の文字を挿入する独自のエンコードスキームを考案できます。たとえば、次のとおりです。

_function encodeID(s) {
    if (s==='') return '_';
    return s.replace(/[^a-zA-Z0-9.-]/g, function(match) {
        return '_'+match[0].charCodeAt(0).toString(16)+'_';
    });
}
_

ただし、同じ_user_id_が2回発生すると、まだ問題が発生します。そして正直に言うと、HTML文字列を投げることに関する全体的なことは通常悪い考えです。代わりにDOMメソッドを使用し、各要素へのJavaScript参照を保持するため、getElementByIdを呼び出したり、IDに任意の文字列が挿入されることを心配したりする必要はありません。

例えば。:

_function addChut(user_id) {
    var log= document.createElement('div');
    log.className= 'log';
    var textarea= document.createElement('textarea');
    var input= document.createElement('input');
    input.value= user_id;
    input.readonly= True;
    var button= document.createElement('input');
    button.type= 'button';
    button.value= 'Message';

    var chut= document.createElement('div');
    chut.className= 'chut';
    chut.appendChild(log);
    chut.appendChild(textarea);
    chut.appendChild(input);
    chut.appendChild(button);
    document.getElementById('chuts').appendChild(chut);

    button.onclick= function() {
        alert('Send '+textarea.value+' to '+user_id);
    };

    return chut;
}
_

また、便利な関数またはJSフレームワークを使用して、そこでのcreate-set-appends呼び出しの長さを削減することもできます。

ETA:

現在、jQueryをフレームワークとして使用しています

それでは、jQuery 1.4の作成ショートカットを検討してください。例:

_var log= $('<div>', {className: 'log'});
var input= $('<input>', {readOnly: true, val: user_id});
...
_

私が今抱えている問題は、JSONPを使用して要素とイベントをページに追加することです。そのため、メッセージを表示する前に要素が既に存在するかどうかがわかりません。

JavaScriptで_user_id_のルックアップを要素ノード(またはラッパーオブジェクト)に保持して、idに入力できる文字が制限されているDOM自体にその情報を保存することを節約できます。

_var chut_lookup= {};
...

function getChut(user_id) {
    var key= '_map_'+user_id;
    if (key in chut_lookup)
        return chut_lookup[key];
    return chut_lookup[key]= addChut(user_id);
}
_

(__map__プレフィックスは、JavaScriptオブジェクトがquiteで任意の文字列のマッピングとして機能しないためです。空の文字列と、IEでは一部のObjectメンバー名、混乱させるそれ。)

40
bobince

私が気に入っているもう1つのアプローチは、ネイティブDOM機能を使用することです。 http://Shebang.brandonmintern.com/foolproof-html-escaping-in-javascript

17
codecraig

これを使用できます:

function sanitize(string) {
  const map = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#x27;',
      "/": '&#x2F;',
  };
  const reg = /[&<>"'/]/ig;
  return string.replace(reg, (match)=>(map[match]));
}

OWASP XSS Prevention Cheat Sheet も参照してください。

9
SilentImp

次のように、単純な正規表現を使用して、idに許可された文字のみが含まれることをアサートできます。

if(id.match(/^[0-9a-zA-Z]{1,16}$/)){
    //The id is fine
}
else{
    //The id is illegal
}

私の例では、英数字と長さ1〜16の文字列のみを使用できます。使用するIDのタイプに合わせて変更する必要があります。

ちなみに、6行目では、valueプロパティに1組の引用符がありません。これは、2つのレベルで引用するときに犯しやすい間違いです。

コンテキストによっては、このチェックがまったく必要ない場合や、十分でない場合があるため、実際のデータフローが表示されません。適切なセキュリティレビューを行うには、さらに情報が必要です。

一般に、組み込みのエスケープ関数またはサニタイズ関数については、盲目的にそれらを信頼しないでください。あなたは彼らが何をするかを正確に知る必要があり、それが実際にあなたが必要とするものであることを確立する必要があります。それがあなたが必要とするものではない場合、あなた自身のコード、ほとんどの場合、私があなたに与えたような単純なホワイトリスト正規表現はうまく動作します。

9
aaaaaaaaaaaa

エスケープするテキストはHTML属性に表示されるため、HTMLエンティティだけでなくHTML属性も必ずエスケープする必要があります。

_var ESC_MAP = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;'
};

function escapeHTML(s, forAttribute) {
    return s.replace(forAttribute ? /[&<>'"]/g : /[&<>]/g, function(c) {
        return ESC_MAP[c];
    });
}
_

次に、エスケープコードはvar user_id = escapeHTML(id, true)になります。

詳細については、 JavascriptでのフールプルーフHTMLエスケープ を参照してください。

2
Brandon Mintern

HTML属性でユーザー指定のデータを使用する場合は、追加の予防措置を講じる必要があります。属性には、HTMLタグ内の出力よりも多くの攻撃ベクトルがあるためです。

XSS攻撃を回避する唯一の方法は、英数字以外をすべてエンコードすることです。 &#xHH;形式で256未満の値ASCII値ですべての文字をエスケープします。CSSクラスとJavaScriptを使用してこれらの要素をフェッチする場合、残念ながらシナリオで問題が発生する可能性があります。

OWASPには、HTML属性XSSを緩和する方法の説明があります。

http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.233 _-_ JavaScript_Escape_Before_Inserting_Untrusted_Data_into_HTML_JavaScript_Data_Values

1
kozmic