Piensa, haz y fluye - LIBRO
Validación de datos de entrada, desarrollo seguro, .net
Auditoría de código,  Ciberseguridad,  Desarrollo seguro,  Vulnerabilidades

Validación de datos de entrada, con ejemplos en tecnología .Net

Dentro del desarrollo seguro, uno de los principios de seguridad esenciales es el de validación de los datos de entrada. Mediante este se establece un control de seguridad a todo dato que puedan introducir los usuarios (o otros procesos) en la aplicación. Cada dato debe validarse en cuanto a su tamaño y forma, teniendo una limitación de número de caracteres y una limitación del conjunto de estos.

Verónica Hontecillas Mentor - Coach Experta en inteligencia emocional

¿Qué es el desarrollo seguro?

¿Por que validar los datos de entrada?, descripción de los beneficios

El beneficio de este control de seguridad es muy grande y si se lleva a cabo de forma correcta dando una cobertura completa, protegerá el componente software frente a multitud de ataques informáticos que para su explotación dependen de la introducción de datos maliciosos al sistema. Entre los ataques que un control de seguridad especifico de este tipo mitigará enormemente, complicando la explotación hasta reducir las probabilidades y prácticamente impedirlos, se encuentran algunos de los más graves y comunes, tales como: SQL Injection (SQLi), Cross-Site Scripting (XSS) y demás inyecciones como OS Command Injection, LDAP Injection, etc… Esta protección también ofrecerá una repercusión positiva y mitigarán otros muchos ataques relacionados, en los que la introducción o tratamiento de datos no esperados o maliciosos, constituye un elemento que forma parte de la explotación: Fuzzing, DoS, Insecure Deserialization, XML External Entities (XXE), Error Handling, SSRF, etc… De esta forma una aplicación o sistema que cumpla con este principio, mejora mucho su seguridad en profundidad, aumentando la complejidad de explotación de numerosos ataques, ya se a de forma directa o indirecta.


Top 10 Web Application Security Risks, OWASP top ten project
OWASP – Top 10 Web Application Security Risks

¿Dónde aplicar esta buena práctica? Identificar puntos del código y arquitectura


Superficie de ataque

Para identificar estas interacciones con la aplicación y el tratamiento de datos que esta realiza, encontrando los lugares en la arquitectura/código donde aplicar control, se deberá realizar una revisión completa de la superficie de exposición que tiene el componente software. Es muy recomendable contemplar esta buena practica desde las fases tempranas de desarrollo y comenzar a llevarla a cabo desde el primer día de la implementación, por parte de los desarrolladores. Una vez se van identificando todas estas entradas de datos, se van organizando por tipos altamente reutilizables y se establece el mecanismo de control de seguridad que se ejecutará, preferiblemente unificando el control en un único lugar del código si esto es posible por arquitectura. De esta manera reduciremos enormemente la superficie de ataque del componente software.

Lado cliente y lado servidor (client-side, server-side, front-end, back-end)

Diagrama client-side y server-side, desarrollo seguro
Diagrama client-side y server-side, desarrollo seguro

  • Client-side input validation: Validación en lado cliente con lenguajes como JavaScript, nativa en HTML5, código de vistas .Net y Java que se ejecutan en cliente, VBScript, etc…
  • Server-side input validation: Validación en lado servidor con lenguajes como ASP .Net, JSP Java, PHP, etc…

Ejemplo de control de validación de datos de entrada en lado cliente y en lado servidor, desarrollo seguro
Ejemplo de control de validación de datos de entrada en lado cliente y en lado servidor, desarrollo seguro

Respecto de la división entre componentes software clientes y componentes software servidor, como puede ser por ejemplo el código JavaScript que ejecuta una aplicación web en tu navegador (lado cliente) y la posterior llamada y ejecución a un método HTTP POST del controlador en el servidor (lado servidor); la validación debe realizarse en ambas, aunque si hablamos de conseguir introducir datos maliciosos y preservarlos en el sistema, la verdadera superficie de ataque esta en el servidor y por lo tanto la validación de lado servidor es la más critica.

Un control de validación de datos de entrada en cliente puede saltarse, uno en lado servidor no. ¿Por que? Pues un atacante no tiene por que usar tu aplicación web desde su navegador, la puede usar de forma directa desarrollando su propio código o de forma más simple controlando la ejecución y tratamiento de datos a su antojo con un proxy especializado en pruebas de ciberseguridad como es Burp Suite (por cierto una herramienta excelente incluso para el desarrollo). Cualquier ejecución de código que se ejecuta en la capa o lado cliente, se ejecuta en una zona de no confianza, el usuario puede alterar el código JavaScript también desde su navegador con las herramientas del desarrollador, saltándose (bypass) cualquier protección que deposite su confianza en el lado servidor.

Si por ejemplo el código JavaScript que se ejecuta en el navegador, me impide seleccionar una contraseña demasiado sencilla en la creación de un nuevo usuario (comprobando longitud mínima, caracteres especiales, mayúsculas, etc…), pero sin embargo después puedo llamar directamente al método HTTP del servidor especificando la contraseña «árbol» y el lado servidor no volverá a comprobar esto, en la practica la protección, para esta casuística es como si no existiera. También se puede interceptar con un proxy la petición HTTP y cambiar sobre la marcha la contraseña que si cumplió estas características, justo antes de que salga hacia el servidor.


CWE-20 - Improper Input Validation, desarrollo seguro, CWE
Código de debilidad en framework del Mitre CWE: CWE-20 – Improper Input Validation, desarrollo seguro

Por esta razón antes expuesta el control de seguridad de validación de datos de entrada, se encontrara en el lado servidor en primer lugar, esta es la superficie de exposición y de ataque más critica de la arquitectura del sistema. Se empezará prestando atención a tener un control de seguridad completo y correcto en el lado servidor, para continuar haciendo lo mismo, y de forma totalmente alineada en la capa o lado cliente. Una validación de datos de entrada correcta y completa en la capa cliente, también tiene su importancia, por que tiene el efecto de complicar el descubrimiento y explotación de vulnerabilidades; ofreciendo una mejor usabilidad y calidad en la experiencia a los usuarios, menor consumo de tiempo, proceso del server y ancho de banda, y la sensación de buena construcción de la aplicación y dificultad de reconocimiento de errores y vulnerabilidades que disuade de intentos de ataque a los cibercriminales.

Es importante remarcar que el lado cliente también es una parte relevante de nuestras aplicaciones y sistema, y representa también su propia superficie de exposición y ataque, multitud de tipos de ataque solo necesitan comprometer esta capa cliente para realizar un engaño, modificación o robo de datos, etc… Pero concretamente para la validación de datos de entrada se debe hacer énfasis en el lado servidor no confiando únicamente en controles en cliente, es muy habitual el desconocimiento y la permisividad en este ámbito de lado servidor, al ser menos visible, haciéndose muy relevante y grave en servicios web.


CWE-20 - Improper Input Validation, desarrollo seguro
CWE-20 – Improper Input Validation, desarrollo seguro

¿Cómo llevarlo a cabo?

La elección del mecanismo y como llevarlo a cabo depende del contexto y los requisitos que debe satisfacer la aplicación. Lo ideal es someter los diferentes tipos de datos a un estricto conjunto de caracteres, siendo estos caracteres permitidos, los mínimos posibles para que el software cumpla su funcionalidad satisfactoriamente. En este caso puede llevarse a cabo el control de seguridad mediante comprobación con listas blancas o negras de datos y caracteres. Esta medida es habitual en entornos donde la seguridad es critica como el sector bancario, puedes haberlo visto usando una aplicación bancaria a la hora de establecer un concepto sobre una transferencia bancaria, los caracteres que puedes usar al escribir el texto son bastante limitados.

La entrada debe ser validada frente a:

  • Tipo de dato concreto (string, integer, real, etc…)
  • Conjunto de caracteres validos
  • Longitud mínima y máxima
  • Posibilidad de valor nulo, o no permisión de este
  • Rango numérico
  • Permisión de valores duplicados
  • Parámetros requeridos/obligatorios
  • Especificación concreta de lista de unos valores posibles
  • Patrones personalizados/Expresiones regulares

Cuando no te queda otra que optar por el plan B

Cuando no puedes restringir elementos o caracteres, por que por ejemplo tu aplicación usa un campo comentario de mil caracteres de longitud y por requisitos funcionales se quiere que el usuario pueda escribir cualquier cosa, haciendo uso de cualquier carácter, se presenta un contexto mucho más molesto y complejo en cuanto a seguridad. En este caso no podrás realmente establecer una validación de datos de entrada segura, la validación será muy permisiva y dejará entrar contenido problemático, es entonces cuando debes prestar mucha atención a todo el flujo de ejecución y tratamiento de datos, codificando correctamente estos datos problemáticos y escapando sus caracteres especiales. No te queda otra que revisar cada una de las representaciones y salidas de estos datos por parte de la aplicación, para confirmar como correcto su escapado de caracteres en cada contexto de presentación de estos. Por ejemplo si este campo comentario que nos permite escribir lo que queramos se introduce a un XML, habrá que tener en cuenta un tratamiento, si se presenta en una página HTML, habrá que tener cuidado con otros aspectos, como por ejemplo que no termine siendo código JavaScript ejecutable, etc…

Comprobación mediante lista blanca frente a lista negra

Reduciendo la validación de datos de entrada, lo que estamos haciendo es dejar o no dejar entrar al sistema un input, para decidir si algo es seguro y puede entrar al sistema o si por otro lado no es confiable y rechazarlo, existen dos aproximaciones, trabajar mediante lista blanca o hacerlo mediante lista negra. Cuando lo haces por lista negra estableces que elemento o caracteres no son validos, por ejemplo para proteger al sistema frente a XSS estableces una lista de caracteres especiales que no deben entrar al sistema formando parte de un nombre de usuario o un campo comentario: < > ! * ‘ ( ) ; : @ & = + $ , / ? % # [ ]

  • Ejemplo aplicación lista negra: dejo entrar todo excepto lo que contenga elementos especificados como no seguros.

Sin embargo la aproximación por lista blanca es justo lo contrario, estableces que elementos o caracteres son los que si pueden entrar, y todos los demás son rechazados. Por ejemplo para proteger al sistema frente a XSS estableces una lista de caracteres permitidos que son lo único que entra al sistema, formando parte de un nombre de usuario o un campo comentario: a-z, A-Z, 0-9 (de la a a la z, mayúsculas de la A a la Z y números del 0 al 9).

  • Ejemplo aplicación lista blanca: no dejo entrar nada, excepto lo que contenga únicamente elementos especificados como seguros.

En igualdad de condiciones se escoge la lista blanca ya que aporta más seguridad, aunque normalmente su aplicación conlleva más esfuerzo e implicaciones. La lista negra es fácil de empezar a usar, pero es muy difícil de mantener y hacer completa; se compone de unos elementos que ya conoces previamente que pueden suponer un problema de seguridad, pero abre al atacante la posibilidad y el juego de encontrar una casuística nueva que esquive el control de seguridad con otros nuevos elementos, además ante un caso no esperado o conocido deja entrar. La lista blanca en cambio es mucho más restrictiva y segura, puesto que por defecto rechaza todo, excepto únicamente los elementos permitidos; esto puede ser una desventaja en cuanto a presentar problemas de usabilidad a usuarios que están realizando un uso legitimo de la aplicación, por lo que su implementación suele tener más repercusiones a nivel de proyecto, también suele presentar a nivel técnico un mayor consumo de procesamiento.

Aplicación y ejemplos: buenas prácticas de código seguro

A continuación se exponen ejemplos de implementación de la buena practica de validación de datos de entrada, para el entorno tecnológico «.Net». Cada una representa una buena practica por separado, para su correcta exposición y análisis, pero se trata de combinar y sumar la mayoría, estableciendo con ello una seguridad en profundidad:

Filtro de entrada: control de validación en entrada


Filtrado de datos de entrada mediante expresión regular C#

Comencemos por las aplicación de expresiones regulares, ya que ofrecen una forma muy personalizable y flexible a la vez, para confirmar si cualquier tipo de datos de entrada es valido. Pueden usarse fácilmente en cualquier lugar del código, aunque la buena practica es que estas expresiones regulares estén centralizadas y sean reutilizables, disponiendo las mínimas posibles de forma común a todo el proyecto. Hay que tener especial cuidado en seleccionar expresiones regulares que sean conocidas y contrastadas en la industria, obteniéndolas desde fuentes fiables como son instituciones gubernamentales de ciberseguridad y estándares internacionales como el proyecto OWASP.

El método Regex.IsMatch se usa para validar una entrada de texto frente a un patrón establecido.

Filtrado de datos de entrada mediante expresión regular
Uso de una expresión regular común para confirmar mediante su ejecución que el dato de entrada de tipo correo electrónico es valido.

Validación de datos de entrada nativa de HTML (solo para el lado cliente)

En la capa cliente puedes limitar la introducción de datos a un usuario mediante el propio HTML de forma nativa. Por ejemplo mediante el atributo «type» puedes especificar que sea un número entero y mediante los atributos «min» y «max» el rango posible, al igual que con las fechas. Aquí un ejemplo en w3schools.

<!DOCTYPE html>
<html>
<body>

<h1>The input min and max attributes</h1>

<p>The min and max attributes specify the minimum and maximum values for an input element.</p>

<form action="/action_page.php">
  <label for="datemax">Enter a date before 1980-01-01:</label>
  <input type="date" id="datemax" name="datemax" max="1979-12-31"><br><br>

  <label for="datemin">Enter a date after 2000-01-01:</label>
  <input type="date" id="datemin" name="datemin" min="2000-01-02"><br><br>
  
  <label for="quantity">Quantity (between 1 and 5):</label>
  <input type="number" id="quantity" name="quantity" min="1" max="5"><br><br>

  <input type="submit" value="Submit">
</form>

</body>
</html>

Tambien puedes filtrar mediante expresiones regulares mediante el atributo pattern. Aquí un ejemplo en w3schools.

<!DOCTYPE html>
<html>
<body>

<h1>The input pattern attribute</h1>

<p>The pattern attribute specifies a regular expression that the input element's value is checked against.</p>

<form action="/action_page.php">
  <label for="country_code">Country code:</label>
  <input type="text" id="country_code" name="country_code" pattern="[A-Za-z]{3}" title="Three letter country code"><br><br>
  <input type="submit" value="Submit">
</form>

<p><strong>Note:</strong> The pattern attribute of the input tag is not supported in Safari 10 (or earlier).</p>

</body>
</html>

Mediante un «placeholder» puedes mostrar el formato valido, ejemplo aquí.

A nivel de modelado de datos como forma de la arquitectura del proyecto

En el espacio de nombres System.ComponentModel.DataAnnotations encontramos los siguientes atributos que podemos usar mediante etiquetas, podemos establecer una tipología de validación de datos de entrada a nivel del modelado de datos del proyecto, a sus entidades, DTOs, etc… De esta manera desde la propia definición del modelo de datos ya estamos a la vez estableciendo sus limitaciones y el control de validación de datos de entrada.

Validación de datos a nivel de proyecto con DataAnnotations, .Net
Validación de datos a nivel de proyecto completo con DataAnnotations de .Net
  • Required: marca que el miembro debe tener un campo obligatorio. Esta decoración puede ser utilizada junto a «ErrorMessage» para indicar un mensaje personalizado de error en el caso de que no se cumpla esta validación.
  • Range: marca un rango de valores entre los que debe estar comprendido el valor pasado al miembro.
  • StringLength: indica un tamaño del campo string. Esta anotación puede ir en conjunción con «MinimunLength» para indicar incluso un tamaño mínimo del campo string.
  • RegularExpression: indica una expresión regular que debe ser utilizada para validar el valor del campo.
  • DataType: indica un nombre de un tipo adicional que debe asociarse a un campo de datos.
  • CustomValidation: nos permite validar a través de validaciones personalizadas.
[StringLength(100)]
[Display(Description = "First Name")]
[RegularExpression("([a-zA-Z0-9 .&'-]+)", ErrorMessage = "Enter only alphabets and numbers of First Name")]
public string FirstName { get; set; }

Este es un ejemplo de aplicación del atributo RegularExpression

Codificación en salida de datos


Codificar caracteres especiales para salida a HTML

Si datos que hacen uso de conjuntos de caracteres permisivos, donde pueden existir caracteres especiales, van a presentarse sobre una web, estos caracteres especiales deben escaparse o codificarse para que no puedan llegar a modificar esta página HTML produciendo un potencial incidente de seguridad.

El método HttpUtility.HtmlEncode convierte los caracteres no seguros a su codificación correcta para HTML, por lo que su representación será el texto literal, a continuación unos ejemplos:

  • < es convertido a &lt;
  • > es convertido a &gt;
  • & es convertido a &amp;
  • » es convertido a &quot;

Tabla de equivalencia entre caracteres originales y su codificación segura.

Con esto, textos problemáticos como los que se emplean para pruebas de seguridad en hacking ético quedan codificados en HTML:

Entrada:

alert("XSS!!");
<IMG SRC=j&#X41vascript:alert('test2')>

Salida con conversión de caracteres inseguros y no-ASCII:

alert(&#x22;XSS!!&#x22;);
&#x3C;IMG SRC=j&#x26;#X41vascript:alert(&#x27;test2&#x27;)&#x3E;

Salida con conversión estricta:

&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x22;&#x58;&#x53;&#x53;&#x21;&#x21;&#x22;&#x29;&#x3B;&#xA;&#x3C;&#x49;&#x4D;&#x47;&#x20;&#x53;&#x52;&#x43;&#x3D;&#x6A;&#x26;&#x23;&#x58;&#x34;&#x31;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3A;&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x27;&#x74;&#x65;&#x73;&#x74;&#x32;&#x27;&#x29;&#x3E;
escapado de caracteres no seguros, caracteres especiales HTML, desarrollo seguro
Conversión de caracteres no seguros, caracteres especiales HTML – Fuente CASE .Net, EC-Council, Desarrollo seguro

Codificar caracteres especiales para formar parte de una URL

Muy parecido al caso anterior pero esta vez en lugar de acabar los datos en una página HTML pasarán a formar parte de una URL, también haremos uso de un método que convertirá estos caracteres inseguros a su codificación correcta.

El método HttpServerUtility.UrlEncode convierte los caracteres no seguros a su codificación correcta para URL, por lo que su interpretación será la de los caracteres literales en forma de URL.

Codificación de caracteres no seguros, caracteres especiales en URL - Desarrollo seguro
Codificación de caracteres no seguros, caracteres especiales en URL – Fuente CASE .Net, EC-Council, Desarrollo seguro

Uso de la librería nativa AntiXss

La biblioteca anti-XSS nativa de .Net se puede agregar como el codificador predeterminado, cambiando el elemento de configuración «httpRuntime» en el archivo «Web.config». Tiene métodos para HTML, XML, URL y JavaScript.

La clase AntiXssEncoder puede usarse por ejemplo para tratar una salida a HTML como se muestra a continuación.

AntiXss c#, .net anti Cross-Site Scripting (XSS) - Fuente SonarSource Rules
AntiXss c#, .net anti Cross-Site Scripting (XSS) – Fuente SonarSource Rules

Protección ante un ataque de tipo path traversal o directory traversal

Cuando desde el código acudimos al sistema de archivos para leer, modificar o borrar un archivo, parte de la ruta de este habitualmente nos viene dada por la entrada de datos del usuario. En este sentido es peligroso no validar la entrada de datos ya que el atacante puede jugar con caracteres interpretados de forma especial como son los dos puntos seguidos «..», para acudir al directorio superior, y así recursivamente hasta salirse del ámbito para el que esta autorizado, y por ejemplo intentar obtener el contenido de un archivo del server como puede ser «/etc/passwd».

Ejemplo de payloads:

"../../../../../../etc/passwd"

../
..\
..\/
%2e%2e%2f
%252e%252e%252f
%c0%ae%c0%ae%c0%af
%uff0e%uff0e%u2215
%uff0e%uff0e%u2216

Para esto podemos establecer por un lado una variable que indique el directorio con el que queremos trabajar con su ruta absoluta, y solo dejar la parte de la ruta variable, referenciando solo nombres de archivo, haciendo uso del método Path.Combine. Esto imposibilita que la ruta completa final se salga de la ruta establecida como raíz, en cambio construir la ruta con la que finalmente se a trabajar, juntando sin más varios strings, deja mucho más margen de error.

using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationDotNetCore.Controllers
{
    public class RSPEC2083IOInjectionCompliantController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult DeleteFile(string fileName)
        {
            string destDirectory = "C:\inetpub\wwwroot\App_Data\TEMP\files\";

			// --- Uso de Path.Combine: ----------------------
            string destFileName = Path.GetFullPath(System.IO.Path.Combine(destDirectory, fileName));
            string fullDestDirPath = Path.GetFullPath(destDirectory + Path.DirectorySeparatorChar);

            if (!destFileName.StartsWith(fullDestDirPath, StringComparison.Ordinal))
            {
                System.IO.File.Delete(destFileName); // Compliant
                return Content("File " + fileName + " deleted");
            } else
            {
                return BadRequest();
            }
        }

    }
}

¿Qué es una auditoría de código?

Deja una respuesta

Tu dirección de correo electrónico no será publicada.

error

Enjoy this blog? Please spread the word :)

error: