Impedir el scripting entre sitios (XSS) en ASP.NET Core
Por Rick Anderson
El scripting entre sitios (XSS) es una vulnerabilidad de seguridad que permite a un atacante colocar scripts del lado cliente (normalmente JavaScript) en páginas web. Cuando otros usuarios carguen las páginas afectadas, se ejecutarán los scripts del atacante, lo que permite al atacante robar tokens de sesión y de sesión, cambiar el contenido de la página web mediante la manipulación de DOM o redirigir el explorador a otra cookie página. Las vulnerabilidades de XSS suelen producirse cuando una aplicación toma la entrada del usuario y la genera en una página sin validarla, codificarla ni escaparla.
Protección de la aplicación contra XSS
En un nivel básico, XSS ayuda a la aplicación a insertar una etiqueta en la página que se representa o inserta un evento <script> On* en un elemento . Los desarrolladores deben usar los siguientes pasos de prevención para evitar introducir XSS en su aplicación.
Nunca coloque datos que no sean de confianza en la entrada HTML, a menos que siga el resto de los pasos siguientes. Los datos que no son de confianza son los datos que puede controlar un atacante, las entradas de formulario HTML, las cadenas de consulta, los encabezados HTTP, incluso los datos procedentes de una base de datos, ya que un atacante puede vulnerar la base de datos incluso si no pueden vulnerar la aplicación.
Antes de colocar datos que no son de confianza dentro de un elemento HTML, asegúrese de que están codificados en HTML. La codificación HTML toma caracteres como < y los cambia a un formato seguro como & lt;
Antes de colocar datos que no son de confianza en un atributo HTML, asegúrese de que están codificados en HTML. La codificación de atributos HTML es un superconjunto de codificación HTML y codifica caracteres adicionales como " y ".
Antes de colocar datos que no son de confianza en JavaScript, coloque los datos en un elemento HTML cuyo contenido se recupere en tiempo de ejecución. Si esto no es posible, asegúrese de que los datos están codificados con JavaScript. La codificación de JavaScript toma caracteres peligrosos para JavaScript y los reemplaza por su hexadecimal, por ejemplo, < se codificaría como
\u003C.Antes de colocar datos que no son de confianza en una cadena de consulta de dirección URL, asegúrese de que están codificados como URL.
Codificación HTML mediante Razor
El motor usado en MVC codifica automáticamente todas las salidas procedentes de variables, a menos que trabaje realmente para Razor evitar que lo haga. Usa reglas de codificación de atributos HTML cada vez que se usa la @ directiva . Como la codificación de atributos HTML es un superconjunto de codificación HTML, esto significa que no tiene que preocuparse por si debe usar codificación HTML o codificación de atributos HTML. Debe asegurarse de que solo usa @ en un contexto HTML, no al intentar insertar entradas que no son de confianza directamente en JavaScript. Los asistentes de etiquetas también codificarán la entrada que se usa en los parámetros de etiqueta.
Haga lo Razor siguiente:
@{
var untrustedInput = "<\"123\">";
}
@untrustedInput
Esta vista genera el contenido de la variable untrustedInput. Esta variable incluye algunos caracteres que se usan en ataques XSS, es < decir, , " y > . Al examinar el origen se muestra el resultado representado codificado como:
<"123">
Advertencia
ASP.NET Core MVC proporciona una HtmlString clase que no se codifica automáticamente en la salida. Esto nunca se debe usar en combinación con la entrada que no es de confianza, ya que esto expondrá una vulnerabilidad de XSS.
Codificación de JavaScript mediante Razor
Puede haber ocasiones en las que quiera insertar un valor en JavaScript para procesarlo en la vista. Existen dos formas de hacerlo. La manera más segura de insertar valores es colocar el valor en un atributo de datos de una etiqueta y recuperarlo en JavaScript. Por ejemplo:
@{
var untrustedInput = "<script>alert(1)</script>";
}
<div id="injectedData"
data-untrustedinput="@untrustedInput" />
<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />
<script>
var injectedData = document.getElementById("injectedData");
// All clients
var clientSideUntrustedInputOldStyle =
injectedData.getAttribute("data-untrustedinput");
// HTML 5 clients only
var clientSideUntrustedInputHtml5 =
injectedData.dataset.untrustedinput;
// Put the injected, untrusted data into the scriptedWrite div tag.
// Do NOT use document.write() on dynamically generated data as it
// can lead to XSS.
document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;
// Or you can use createElement() to dynamically create document elements
// This time we're using textContent to ensure the data is properly encoded.
var x = document.createElement("div");
x.textContent = clientSideUntrustedInputHtml5;
document.body.appendChild(x);
// You can also use createTextNode on an element to ensure data is properly encoded.
var y = document.createElement("div");
y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
document.body.appendChild(y);
</script>
El marcado anterior genera el siguiente código HTML:
<div id="injectedData"
data-untrustedinput="<script>alert(1)</script>" />
<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />
<script>
var injectedData = document.getElementById("injectedData");
// All clients
var clientSideUntrustedInputOldStyle =
injectedData.getAttribute("data-untrustedinput");
// HTML 5 clients only
var clientSideUntrustedInputHtml5 =
injectedData.dataset.untrustedinput;
// Put the injected, untrusted data into the scriptedWrite div tag.
// Do NOT use document.write() on dynamically generated data as it can
// lead to XSS.
document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;
// Or you can use createElement() to dynamically create document elements
// This time we're using textContent to ensure the data is properly encoded.
var x = document.createElement("div");
x.textContent = clientSideUntrustedInputHtml5;
document.body.appendChild(x);
// You can also use createTextNode on an element to ensure data is properly encoded.
var y = document.createElement("div");
y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
document.body.appendChild(y);
</script>
El código anterior genera la salida siguiente:
<script>alert(1)</script>
<script>alert(1)</script>
<script>alert(1)</script>
Advertencia
NO concatenar la entrada que no es de confianza en JavaScript para crear elementos DOM o usar document.write() en contenido generado dinámicamente.
Use uno de los enfoques siguientes para evitar que el código se exponga a XSS basado en DOM:
createElement()y asignan valores de propiedad con métodos o propiedades adecuados, comonode.textContent=onode.InnerText=.document.CreateTextNode()y anexe en la ubicación DOM adecuada.element.SetAttribute()element[attribute]=
Acceso a codificadores en código
Los codificadores HTML, JavaScript y URL están disponibles para el código de dos maneras, puede insertarlos a través de la inserción de dependencias o puede usar los codificadores predeterminados contenidos en el espacio de System.Text.Encodings.Web nombres . Si usa los codificadores predeterminados, los que aplicó a los intervalos de caracteres para tratarse como seguros no se aplicarán; los codificadores predeterminados usan las reglas de codificación más seguras posibles.
Para usar los codificadores configurables a través de LA, los constructores deben tomar un parámetro HtmlEncoder, JavaScriptEncoder y UrlEncoder según corresponda. Por ejemplo:
public class HomeController : Controller
{
HtmlEncoder _htmlEncoder;
JavaScriptEncoder _javaScriptEncoder;
UrlEncoder _urlEncoder;
public HomeController(HtmlEncoder htmlEncoder,
JavaScriptEncoder javascriptEncoder,
UrlEncoder urlEncoder)
{
_htmlEncoder = htmlEncoder;
_javaScriptEncoder = javascriptEncoder;
_urlEncoder = urlEncoder;
}
}
Codificación de parámetros de dirección URL
Si desea compilar una cadena de consulta de dirección URL con una entrada que no es de confianza como valor, use para UrlEncoder codificar el valor. Por ejemplo,
var example = "\"Quoted Value with spaces and &\"";
var encodedValue = _urlEncoder.Encode(example);
Después de codificar la variable encodedValue contendrá %22Quoted%20Value%20with%20spaces%20and%20%26%22 . Los espacios, las comillas, los signos de puntuación y otros caracteres no seguros se codificarán de forma porcentual en su valor hexadecimal; por ejemplo, un carácter de espacio se convertirá en %20.
Advertencia
No use la entrada que no sea de confianza como parte de una ruta de dirección URL. Pase siempre una entrada que no sea de confianza como un valor de cadena de consulta.
Personalización de los codificadores
De forma predeterminada, los codificadores usan una lista segura limitada al intervalo Unicode latino básico y codifican todos los caracteres fuera de ese intervalo como sus equivalentes de código de caracteres. Este comportamiento también afecta a la representación de TagHelper y HtmlHelper, ya que usará los Razor codificadores para generar las cadenas.
El razonamiento que hay detrás de esto es protegerse frente a errores desconocidos o futuros del explorador (los errores anteriores del explorador se han activado en el análisis en función del procesamiento de caracteres que no están en inglés). Si el sitio web hace un uso pesado de caracteres no latinos, como chino, cirílico u otros, probablemente este no sea el comportamiento que desea.
Puede personalizar las listas seguras del codificador para incluir intervalos Unicode adecuados para la aplicación durante el inicio, en ConfigureServices() .
Por ejemplo, si usa la configuración predeterminada, podría usar Razor htmlhelper de este modo;
<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>
Al ver el origen de la página web, verá que se ha representado como se muestra a continuación, con el texto chino codificado;
<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>
Para ampliar los caracteres tratados como seguros por el codificador, insertaría la siguiente línea en ConfigureServices() el método en startup.cs ;
services.AddSingleton<HtmlEncoder>(
HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
UnicodeRanges.CjkUnifiedIdeographs }));
En este ejemplo se amplía la lista segura para incluir los CjkUnifiedIdeographs del intervalo Unicode. La salida representada ahora se convertiría en
<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>
Caja fuerte los intervalos de lista se especifican como gráficos de código Unicode, no como idiomas. El estándar Unicode tiene una lista de gráficos de código que puede usar para buscar el gráfico que contiene los caracteres. Cada codificador, Html, JavaScript y Url, debe configurarse por separado.
Nota
La personalización de la lista segura solo afecta a los codificadores que se han producido a través de LA. Si accede directamente a un codificador a través del valor predeterminado, se usará System.Text.Encodings.Web.*Encoder.Default basic latin only safelist.
¿Dónde debe tener lugar la codificación?
La práctica general aceptada es que la codificación tiene lugar en el punto de salida y los valores codificados nunca deben almacenarse en una base de datos. La codificación en el punto de salida permite cambiar el uso de datos, por ejemplo, de HTML a un valor de cadena de consulta. También permite buscar fácilmente los datos sin tener que codificar valores antes de buscar y le permite aprovechar los cambios o correcciones de errores realizados en los codificadores.
Validación como técnica de prevención de XSS
La validación puede ser una herramienta útil para limitar los ataques XSS. Por ejemplo, una cadena numérica que contenga solo los caracteres 0-9 no desencadenará un ataque XSS. La validación se vuelve más complicada al aceptar HTML en la entrada del usuario. El análisis de la entrada HTML es difícil, si no imposible. Markdown, junto con un analizador que elimina html insertado, es una opción más segura para aceptar entradas enriquecerdas. Nunca confíe en la validación por sí solo. Codifica siempre la entrada que no es de confianza antes de la salida, independientemente de la validación o el estado que se haya realizado.