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 <
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('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", '''))
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