web-dev-qa-db-ja.com

DjangoのXSSが「Unicode」でエスケープするのを回避する方法はありますか?

Django(Python Webフレームワーク)は、XSS(クロスサイトスクリプティング)攻撃を防ぐために出力をエスケープします。これは'"<>& HTMLセーフバージョン。

ただし、 スライド共有に関するこのプレゼンテーション 、(具体的には スライド№1 )は次のように述べています。

問題

  1. 他のUnicodeはこのチェックをバイパスします

この申し立てを理解できません。 XSSを許可するDjangoのescape関数で置き換えられないUnicode文字はありますか?私はユニコードについて少し知っています、そして私はどのように考えることができません。

12
Rory

スライドが正確に何を指しているのか明確ではありません。 Djangoの自動エスケープは、テキストコンテンツのHTMLインジェクションや適切に引用された属性値に対しては問題ありません。

HTMLエスケープを回避できる他のUnicode 文字はありませんが、原則として、誤ったUnicodeエンコーディングであると誤解される可能性のあるバイトシーケンスがあります。

  • ブラウザがドキュメントをUTF-7として解釈することを決定した場合、+ADw-<(および&"'>の同様のシーケンス)の同義語になり、HTMLメタ文字がエスケープされないようにします。

  • 一部の東アジアのマルチバイトエンコーディングでは、マルチバイトシーケンスの末尾のバイトを0x00-0x7Fの範囲にすることができます。この場合、これらはASCII文字として解釈され、そのように処理すると誤ってエスケープされます。通常はただし、セキュリティ上の問題ではなく、テキストが壊れるだけです。

  • 無効な「overlong」UTF-8バイトシーケンスは、ASCIIとしていくつかの非常に古いブラウザ(元のIE6 SP1以前、およびOperaこれにより、<を表すバイトシーケンス0xC0 0xBCなどのHTMLメタ文字がエスケープされないようにすることができます。

これらの問題を回避するには、(a)ドキュメントを必ずUTF-8 Content-Typecharsetで提供し、(b)すべてのテキスト文字列をネイティブのUnicode文字列として内部的に保持して、無効なUTF-8シーケンスにエンコードしないでください。

Djangoアプリはすでにデフォルトでこれを行う傾向があるため、Djangoテンプレートの自動エスケープがUnicodeの問題によって無効になることはありそうもないシナリオです。

もちろん、XSSは一般的に解決されているとは限りません。|safe、引用符のない属性、HTMLインジェクション以外の問題(JavaScript文字列、CSSプロパティ、URLパラメータなど)、HTMLコンテンツのスニッフィング、危険なURLスキーム(javascript: et al)など。しかし、テンプレートでのHTMLインジェクションに対する防御策として、それは健全なはずです。

10
bobince

はい、このXSSフィルターが失敗するインスタンスは少なくとも3つあります。 XSSは複雑であり、盲目的に文字を置き換えてもこの問題は解決されません。最も明白なのは、スクリプトタグ内で記述している場合です。

<script>
var x = alert(1);
</script>

Hrefまたはiframe srcを記述している場合は、javascript: URIを使用できます。

<a href=javascript:alert(1)>alert</a>

DOMイベント内に書き込む の場合、xssに対しても脆弱です。

<a href="doSomethingCool('userInput%27);sendHaxor(document.cookie);//');">Cool Link</a>

ブラウザは、JavaScriptイベントを実行する前に、%27(およびその他のエンコード方法)を自動的にデコードします。

7
rook

DjangoはXSSへの露出を減らすために賢明なことをします。

DjangoはデフォルトでどこでもUnicodeとUTF-8エンコーディングを使用し、ユーザーが任意のHTML要素を挿入するのを防ぐために、すべてのテンプレート変数(デフォルトで行われる)を置き換える前に、Unicodeエンコーディングを強制します。 Djangoは、開発者がDEFAULT_CHARSET設定でエンコードを変更できるようにしますが、アプリケーション全体でそのエンコードを強制し、デフォルトでContent-Type: text/html; charset=utf-8 HTTP応答ヘッダーを挿入します( 'text/html'および 'utf-8 '別のcontent_typeを返すか、文字セットを変更した場合は変更します。さらに、Django=ページは、ベーステンプレートとその管理ページで<meta http-equiv="content-type" content="text/html; charset=utf-8">も設定しますが、開発者に使用しないオプションを提供しますそれらのベーステンプレート(およびdevsカスタムの書かれたテンプレートはメタタグで文字セットを定義しないか、悪いことに間違った文字セットを使用する可能性があります。) bobinceの素晴らしい答え ユーザーの<の代わりの&lt;のいくつかの欠点をリストしましたエンコーディングの問題を介して入力; Djangoデフォルトではこれらを適切に処理します。

100%フールプルーフですか?いいえ、それでも開発者には、onclickアクションへのユーザー入力の挿入、(mark_safe()関数またはテンプレートでの{{ user_input|safe }}による)自動エスケープのバイパス、または安全でない場所:リンク、または評価されたJavaScript内。各テンプレートの集中的なコンパイル/セマンティック分析なしでは、これ以上のことを行うことはほぼ不可能であることは当然です。

興味のある人にとっては、エスケープコードは Django/utils/html.py で非常に読みやすくなっています。 (私のリンクは現在の開発バージョンに移動しますが、コピーの貼り付けはDjango 1.2からです。開発バージョンと1.2バージョンの主な違いは、名前がforce_unicodeからforce_textに変更されていることです(py3ではすべてのテキストがユニコードです) )そして、python 3(6へのすべての参照)と互換性があります。)

基本的に、エスケープ関数は、テンプレートにレンダリングされるすべての変数で実行され、まず適切にエンコードできることを確認してから、文字&<>'"をHTMLエスケープされた同等の文字に置き換えます。 JSをエスケープする関数もありますが、{{ variable|escapejs }}のようなテンプレートで手動で呼び出す必要があると思います。

def escape(html):
    """
    Returns the given HTML with ampersands, quotes and angle brackets encoded.
    """
    return mark_safe(force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))
escape = allow_lazy(escape, unicode)

_base_js_escapes = (
    ('\\', r'\u005C'),
    ('\'', r'\u0027'),
    ('"', r'\u0022'),
    ('>', r'\u003E'),
    ('<', r'\u003C'),
    ('&', r'\u0026'),
    ('=', r'\u003D'),
    ('-', r'\u002D'),
    (';', r'\u003B'),
    (u'\u2028', r'\u2028'),
    (u'\u2029', r'\u2029')
)

# Escape every ASCII character with a value less than 32.
_js_escapes = (_base_js_escapes +
               Tuple([('%c' % z, '\\u%04X' % z) for z in range(32)]))

def escapejs(value):
    """Hex encodes characters for use in JavaScript strings."""
    for bad, good in _js_escapes:
        value = mark_safe(force_unicode(value).replace(bad, good))
    return value
escapejs = allow_lazy(escapejs, unicode)

def conditional_escape(html):
    """
    Similar to escape(), except that it doesn't operate on pre-escaped strings.
    """
    if isinstance(html, SafeData):
        return html
    else:
        return escape(html)

そして Django/utils/encoding.py から:

def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
    """
    Similar to smart_unicode, except that lazy instances are resolved to
    strings, rather than kept as lazy objects.

    If strings_only is True, don't convert (some) non-string-like objects.
    """
    if strings_only and is_protected_type(s):
        return s
    try:
        if not isinstance(s, basestring,):
            if hasattr(s, '__unicode__'):
                s = unicode(s)
            else:
                try:
                    s = unicode(str(s), encoding, errors)
                except UnicodeEncodeError:
                    if not isinstance(s, Exception):
                        raise
                    # If we get to here, the caller has passed in an Exception
                    # subclass populated with non-ASCII data without special
                    # handling to display as a string. We need to handle this
                    # without raising a further exception. We do an
                    # approximation to what the Exception's standard str()
                    # output should be.
                    s = ' '.join([force_unicode(arg, encoding, strings_only,
                            errors) for arg in s])
        Elif not isinstance(s, unicode):
            # Note: We use .decode() here, instead of unicode(s, encoding,
            # errors), so that if s is a SafeString, it ends up being a
            # SafeUnicode at the end.
            s = s.decode(encoding, errors)
    except UnicodeDecodeError, e:
        if not isinstance(s, Exception):
            raise DjangoUnicodeDecodeError(s, *e.args)
        else:
            # If we get to here, the caller has passed in an Exception
            # subclass populated with non-ASCII bytestring data without a
            # working unicode method. Try to handle this without raising a
            # further exception by individually forcing the exception args
            # to unicode.
            s = ' '.join([force_unicode(arg, encoding, strings_only,
                    errors) for arg in s])
    return s
3
dr jimbob