¿Hay alguna manera de evitar que el XSS de Django se escape con "unicode"?

12

Django (el marco web de Python) escapa la salida para evitar ataques XSS (Cross Site Scripting). Reemplaza a ' , " , < , > , & con sus versiones seguras de HTML.

Sin embargo, esta presentación sobre la diapositiva compartida , (específicamente slide № 13 ), dice:

  

Problemas

     
  1. Cualquier otro Unicode omitirá esta comprobación
  2.   

No puedo entender esta queja. ¿Hay algún carácter Unicode que no será reemplazado por la función escape de Django que permitirá un XSS? Sé un poco sobre Unicode, y no puedo pensar cómo.

    
pregunta Rory 10.04.2013 - 12:54
fuente

3 respuestas

10

No está claro a qué se refiere exactamente la diapositiva. El auto-escape de Django debería estar bien contra la inyección de HTML en el contenido de texto y los valores de atributo correctamente citados.

No hay otros caracteres de Unicode que puedan evadir el escape de HTML, pero en principio hay secuencias de bytes que podrían malinterpretarse como si estuvieran en la codificación Unicode incorrecta:

  • Si el navegador decide interpretar un documento como UTF-7, +ADw- se convierte en un sinónimo de < (y secuencias similares para &"'> ), permitiendo que los metacaracteres HTML eviten que se escapen.

  • Algunas codificaciones multibyte del este de Asia permiten que los bytes finales en una secuencia multibyte estén en el rango de 0x00-0x7F donde podrían interpretarse como caracteres ASCII, y se pueden realizar errores de escape si se manejan de esa manera. Sin embargo, generalmente eso solo llevaría a un texto roto en lugar de un problema de seguridad.

  • Las secuencias de bytes UTF-8 'demasiado largas' no válidas pueden interpretarse como ASCII en algunos navegadores muy antiguos (el IE6 pre-SP1 original y Opera aproximadamente al mismo tiempo). Esto podría permitir que los metacaracteres HTML eviten que se escapen, como la secuencia de bytes 0xC0 0xBC que representa < .

Para evitar estos problemas, debería (a) asegurarse de servir sus documentos con un UTF-8 Content-Type charset , y (b) mantener todas sus cadenas de texto como cadenas nativas de Unicode internamente para que nunca puedan codificarse a secuencias UTF-8 inválidas.

Ya que las aplicaciones Django tienden a hacer esto de manera predeterminada, no es un escenario probable que los escapes automáticos de Django sean derrotados por los problemas de Unicode.

Eso no quiere decir que XSS se resuelva en general, por supuesto, aún debe evitar el uso incorrecto de |safe , atributos no citados, problemas de inyección no HTML (como cadenas de JavaScript, propiedades de CSS, parámetros de URL), detección de contenido HTML , esquemas de URL peligrosos ( javascript: et al), y así sucesivamente. Pero como defensa contra la inyección de HTML en las plantillas, debería ser sólida.

    
respondido por el bobince 10.04.2013 - 21:27
fuente
8

Sí, hay al menos tres instancias en las que este filtro XSS falla. XSS es complejo, y reemplazar los caracteres a ciegas no resuelve este problema. Lo más obvio es si está escribiendo dentro de una etiqueta de script:

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

Si estás escribiendo un href o iframe src puedes usar el javascript: URI:

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

También es vulnerable a xss si escribe dentro de un evento DOM

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

El navegador decodificará automáticamente el %27 (así como otros métodos de codificación) antes de ejecutar el evento de JavaScript.

    
respondido por el rook 10.04.2013 - 20:48
fuente
3

Django hace las cosas sensatas para reducir la exposición a XSS.

Django usa la codificación Unicode y UTF-8 en todas partes de manera predeterminada, y obliga sensiblemente a la codificación Unicode antes de realizar la sustitución en todas las variables de la plantilla (de forma predeterminada) para evitar que los usuarios inserten elementos HTML arbitrarios. Django permite a los desarrolladores cambiar la codificación con la configuración DEFAULT_CHARSET , pero forzará esa codificación en toda la aplicación e insertará los encabezados de respuesta HTTP Content-Type: text/html; charset=utf-8 de forma predeterminada (con 'text / html' y 'utf-8' cambiando si está devolver un tipo de contenido diferente o cambiar el conjunto de caracteres). Además, las páginas de django también establecerán <meta http-equiv="content-type" content="text/html; charset=utf-8"> en sus plantillas base y en sus páginas de administración, pero nuevamente les da a los desarrolladores la opción de no usar sus plantillas base (y las plantillas escritas personalizadas de los desarrolladores no pueden definir un conjunto de caracteres en la etiqueta meta) usar el juego de caracteres incorrecto). Entonces, mientras que la gran respuesta de Bobince enumera algunas deficiencias del sustituto < para &lt; en la entrada del usuario a través de problemas de codificación; django por defecto los manejará adecuadamente.

¿Es 100% infalible? No, aún le dan al programador la capacidad de configuración suficiente para hacer cosas inseguras, como insertar la entrada del usuario en una acción de clic, omitir el escape automático (a través de la función mark_safe() o {{ user_input|safe }} en la plantilla), o permitir la entrada del usuario en una ubicación insegura : por ejemplo, un enlace o dentro de un javascript evaluado. Por supuesto, sería casi imposible hacer mucho más sin una compilación / análisis semántico intensivo de cada plantilla.

Para las personas interesadas, el código de escape es bastante legible en django / utils / html. py . (Mi enlace va a la versión dev actual; pero mi copia pegada es de django 1.2. La principal diferencia entre la versión dev y la versión 1.2 es que se les cambió el nombre de force_unicode a force_text (en py3, todo el texto es unicode) y lo hizo compatible con python 3 (todas las referencias a seis).)

Básicamente, la función de escape se ejecuta en cada variable que se va a representar en la plantilla y primero verifica que se pueda codificar correctamente y luego reemplaza los caracteres: &<>'" con sus equivalentes de escape HTML. También hay funciones para escapar de JS, aunque creo que se debe llamar manualmente en la plantilla como {{ 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)

y de 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
    
respondido por el dr jimbob 10.04.2013 - 23:04
fuente

Lea otras preguntas en las etiquetas