vulnerabilidades y malas prácticas del código, desarrollo seguro
Auditoría de código,  Ciberseguridad,  Desarrollo seguro,  Vulnerabilidades

¿Qué buscar en una auditoría de código (SAST)?: vulnerabilidades y malas prácticas del código (.Net)

En este artículo vamos a repasar una lista básica de ejemplos de malas practicas del código, que resultan en debilidad para nuestra aplicación o directamente en muchos casos exponen vulnerabilidades fácilmente explotables por parte de un atacante. Será concretamente con ejemplos en entorno tecnológico «.Net». Esta lista por supuesto no es completa y simplemente recoge algunos ejemplos.

Tienes disponibles herramientas para realizar análisis estático de código (SAST) de manera automática, como por ejemplo SonarQube, que tiene versión gratuita. Este tipo de herramientas te ayudará a encontrar estas vulnerabilidades y malas prácticas en el código, aunque mediante patrones que tienen predefinidos en unas determinadas reglas, por lo que no descubrirá todos los agujeros de seguridad, se requiere una revisión manual para detectar todas.

Para llevar a cabo una auditoría de código con revisión manual, aquí te propongo unas herramientas y un procedimiento:


  1. Leer código auditando seguridad
  2. Búsqueda por patrones / expresiones regulares en el código
  3. Llevar a cabo una auditoría de código, 2ª parte: La propia revisión de código

Contenidos ocultar

Un clásico, carencia de control sobre datos de entrada: SQL inyection

Resumiendo mucho en este tipo de ataques se introducen a la aplicación caracteres especiales que pueden ser mal interpretados, estos caracteres si no se han tenido en cuenta pueden alterar la consulta SQL que el desarrollador pretende hacer a la base de datos. Por ejemplo introduciendo una comilla simple y un punto y coma, que cierre la consulta esperada por el desarrollador, y pase a ejecutar la que desea el atacante (aquí una explicación en condiciones por si no conoces ya la vulnerabilidad y su explotación):

inyección SQL, SQLi, desarrollo seguro

Para protegerte de este tipo de ataque, debes erradicar las siguientes malas practicas en tu código:

  • 1 – No hagas consultas a la base de datos que concatenen variables de texto (strings), «a pelo». Usa una librería especializada de acceso a base de datos que construya las consultas por ti, en este tipo de librerías puedes introducir las variables de una manera parametrizada, de esta forma la librería se encarga de manejar los caracteres especiales de manera literal, sin que estos puedan causar daños.

Ejemplo de código vulnerable: la variable «user» se concatena sin más, sin ningún control. Si el código anterior a este punto, tampoco ha controlado nada y la sentencia se va a ejecutar sobre la base de datos, imagínate la que puedes liar.

        public IActionResult Authenticate(string user)
        {
            string query = "SELECT * FROM Users WHERE Username = '" + user + "'";

            // an attacker can bypass authentication by setting user to this special value
            // user = "' or 1=1 or ''='";

            var userExists = false;
            if (_context.Database.ExecuteSqlCommand(query) > 0) // Noncompliant
            {
                userExists = true;
            }

            return Content(userExists ? "success" : "fail");
        }

Ejemplo de código no explotable: la variable «param» aquí, es introducida por el método nativo de C# «ExecuteSqlCommand», del objeto «System.Data.Entity.Database», los desarrolladores de esta librería ya se encargan de tratar la codificación de forma correcta, para cualquier juego de caracteres que venga.

public void Foo(DbContext context, string query, string param)
{
    context.Database.ExecuteSqlCommand("SELECT * FROM mytable WHERE mycol=@p0", param); // Compliant, it's a parametrized safe query
}

Otro ejemplo de código no explotable: en java

public User getUser(Connection con, String user) throws SQLException {

  Statement stmt1 = null;
  PreparedStatement pstmt = null;
  String query = "select FNAME, LNAME, SSN " +
                 "from USERS where UNAME=?"
  try {
    stmt1 = con.createStatement();
    ResultSet rs1 = stmt1.executeQuery("GETDATE()");

    pstmt = con.prepareStatement(query);
    pstmt.setString(1, user);  // Good; PreparedStatements escape their inputs.
    ResultSet rs2 = pstmt.executeQuery();

    //...
  }
}

*Por cierto, lo mismo para los procedimientos almacenados.

  • 2 – Validación de datos de entrada: usa esta buena práctica, que resumiendo mucho se trata de controlar para cada entrada de datos que tiene la aplicación, el tamaño y forma de estos, para que tenga sentido. Es decir, que por ejemplo en el campo teléfono no te metan esto: «javascript:alert(1)&lt;/script\x0D</p>» y tu código se ponga a trabajar con ello tranquilamente, como si tuviera sentido alguno.
  • 3 – Usa el principio de mínimo privilegio en tu arquitectura y configuración de acceso a base de datos: que tu aplicación disponga de los mínimos privilegios que necesite, es decir, que establezca conexión a base de datos con un usuario que no sea «root», y pueda ver / modificar los datos, únicamente de la base de datos que le corresponde, no de las demás. Si solo necesita leer, establece la conexión en solo lectura, etc… De esta manera si un ataque tiene éxito, comprometerá la mínima cantidad de datos posible.

Configuración insegura: secretos (credenciales y claves) en texto plano en archivo de configuración

En el proyecto no deben encontrarse secretos, como contraseñas o keys, disponibles en texto plano, ni en archivos de configuración ni harcodeadas en el código. Esta es una mala práctica, ya que cualquier usuario a partir de tener el simple permiso de lectura sobre dichos archivos de configuración o código, tendrá acceso a dichos secretos en texto plano, con la escalada de privilegios de acceso a recursos que  esto podría suponer.

No todas las personas envueltas en el desarrollo y administración del sistema deben de tener acceso a las credenciales del entorno de producción. Gestionando la seguridad de la información se aplica el principio de mínimo privilegio, este otorga a los usuarios de los sistemas de información los mínimos privilegios que necesiten para poder desarrollar sus tareas, minimizando así la probabilidad de que se produzcan incidentes de seguridad.

Tener credenciales en texto plano, no solo expone los secretos a posibles usuarios sin privilegios suficientes para acceder a ellos, que puedan abrir y leer los archivos. También expone estos a sencillamente verlos de forma rápida en la pantalla, la técnica de ingeniería social es llamada “shoulder surfing”. Una filtración de un secreto tan obvia puede producirse por ejemplo durante simples tareas de desarrollo o administración del sistema, facilitando que un atacante obtenga contraseñas y consiga acceso ilícito a la aplicación y servicios usados por esta, lo cual supone un compromiso del sistema.

Aquí aplicar una capa de protección es mucho mejor que no hacer nada y las claves estén a simple vista. La protección puede tener una gran profundidad (seguridad en profundidad) e implicar por ejemplo la gestión de las claves mediante un «Hardware Security Module» (HSM), o un menor alcance pero ofreciendo una sencilla capa de protección, como por ejemplo la siguiente para entorno tecnológico .Net: Encrypting Configuration Information Using Protected Configuration.

Ejemplo de configuración insegura: archivo de configuración «Web.config» con todos los secretos, de acceso a la base de datos y de clave de aplicación, en texto plano.

<configuration>
   <connectionStrings>
      <add name="SqlServices" connectionString="Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;" />
   </connectionStrings>
   <system.web>
     <machineKey validationKey="D61B3C89CB33A2F1422FF158AFF7320E8DB8CB5CDA1742572A487D94018787EF42682B202B746511891C1BAF47F8D25C07F6C39A104696DB51F17C529AD3CABE"
       decryptionKey="FBF50941F22D6A3B229EA593F24C41203DA6837F1122EF17" />
   </system.web>
</configuration>

Ejemplo de configuración segura: archivo de configuración «Web.config» con las credenciales de acceso a una base de datos («ConnectionString») y las claves internas («machineKey») encriptadas.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <connectionStrings configProtectionProvider="RsaProtectedConfigurationProvider">
      <EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
         <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
         <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
               <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
               <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                  <KeyName>RSA Key</KeyName>
               </KeyInfo>
               <CipherData>
                  <CipherValue>WcFEbDX8VyLfAsVK8g6hZVAG1674ZFc1kWH0BoazgOwdBfinhcAmQmnIn0oHtZ5tO2EXGl+dyh10giEmO9NemH4YZk+iMIln+ItcEay9CGWMXSen9UQLpcQHQqMJErZiPK4qPZaRWwqckLqriCl9X8x9OE7jKIsO2Ibapwj+1Jo=</CipherValue>
               </CipherData>
            </EncryptedKey>
         </KeyInfo>
         <CipherData>
            <CipherValue>OpWQgQbq2wBZEGYAeV8WF82yz6q5WNFIj3rcuQ8gT0MP97aO9SHIZWwNggSEi2Ywi4oMaHX9p0NaJXG76aoMR9L/WasAxEwzQz3fexFgFSrGPful/5txSPTAGcqUb1PEBVlB9CA71UXIGVCPTiwF7zYDu8sSHhWa0fNXqVHHdLQYy1DfhXS3cO61vW5e/KYmKOGA4mjqT0VZaXgb9tVeGBDhjPh5ZlrLMNfYSozeJ+m2Lsm7hnF6VvFm3fFMXa6+h0JTHeCXBdmzg/vQb0u3oejSGzB4ly+V9O0T4Yxkwn9KVDW58PHOeRT2//3iZfJfWV2NZ4e6vj4Byjf81o3JVNgRjmm9hr9blVbbT3Q8/j5zJ+TElCn6zPHvnuB70iG2KPJXqAj2GBzBk6cHq+WNebOQNWIb7dTPumuZK0yW1XDZ5gkfBuqgn8hmosTE7mCvieP9rgATf6qgLgdA6zYyVV6WDjo1qbCV807lczxa3bF5KzKaVUSq5FS1SpdZKAE6/kkr0Ps++CE=</CipherValue>
         </CipherData>
      </EncryptedData>
   </connectionStrings>
   <system.web>
      <machineKey configProtectionProvider="RsaProtectedConfigurationProvider">
         <EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
            <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
               <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
                  <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
                  <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                     <KeyName>RSA Key</KeyName>
                  </KeyInfo>
                  <CipherData>
                     <CipherValue>IwUopItbWX0mJdGWtAqE1LlsG3u5RBRlAXs9/GZj3HEfeUXduHVF76q6Ip88YqlfLthH+DMBYdOZAF+hCOmS2agfTo1tKUvELRGIljS/BqEYxUO+/IOz9tllAw8ZlGF7AVCzptgIejI+iLXEZfMKW7f6EMGeb5vaaKXHIkYZwcM=</CipherValue>
                  </CipherData>
               </EncryptedKey>
            </KeyInfo>
            <CipherData>
               <CipherValue>ivVyERVPNUzIb/i7/NUbRkxsxh8IG959vycwrzJO0vYWxHZ5i03SfrLbsGUV17+FxZ6lbcrVaF5FY3zVm7dRMRvQpVFwaVcL</CipherValue>
            </CipherData>
         </EncryptedData>
      </machineKey>
   </system.web>
</configuration>

Para encriptar / desencriptar se aconseja usar algún buen software, que sea profesional y ampliamente contrastado en la industria, nunca reinventar la rueda. Un ejemplo es la API de protección de datos de Microsoft (DPAPI), que ofrece unas opciones muy seguras e interesantes que puedes llegar a usar de forma sencilla.

En este punto surge una interesante pregunta, ¿y la clave para desencriptar el resto de claves donde la pongo? Llega un momento donde si no quieres depender de un ser humano para arrancar una aplicación o proceso y quieres que este arranque de forma automática, debes de guardar en algún lugar, «la llave que abre todo el resto de puertas», para que la aplicación pueda obtenerla en caso de reinicio. Si la guardas en algún lugar del código y ofuscas bien este, esta clave se podrá llegar a conseguir con un esfuerzo dado, pero no nos olvidemos que el punto de partida era regalarla en el «Web.config», por lo que hemos avanzado mucho. Una mejor solución puede configurarse por ejemplo con DPAPI, creando una encriptación especifica para un servidor Windows concreto o uno de sus usuarios del sistema. Al DPAPI usar credenciales asociadas especificas de un usuario, las cadenas encriptadas específicamente en un server, no pueden desencriptarse en otro equipo, y por lo tanto para poder desencriptarlas tienes que estar en posesión de poder ejecutar procesos en el propio servidor fijado. Este siguiente nivel de seguridad es una cuestión que podemos abordar pero de forma especifica en otro contexto, aquí empezaremos aplicando una primera capa de protección.

Ejemplo de implementación segura: ejemplo de uso de DPAPI para la encriptación / desencriptación.

/// <summary>
/// Encrypts the specified clear text.
/// </summary>
/// <param name="clearText">The clear text to encrypt</param>
/// <param name="entropy">Optional entropy key</param>
/// <returns>The encrypted text</returns>
public static string Encrypt(string clearText, byte[] entropy)
{
    if (clearText == null) throw new ArgumentNullException(nameof(clearText));
 
    byte[] clearBytes     = Encoding.UTF8.GetBytes(clearText);
    byte[] encryptedBytes = ProtectedData.Protect(clearBytes, entropy, DataProtectionScope.LocalMachine);
 
    return Convert.ToBase64String(encryptedBytes);
}

Configuración insegura: Exposición de información técnica a través de errores

El correcto manejo de errores, forma parte de la seguridad básica de la aplicación. A excepción de las películas, el ataque siempre comienza desde la etapa de reconocimiento, durante esta etapa, el atacante intentará recopilar la mayor cantidad de información técnica sobre el objetivo (generalmente nombres y números específicos de versión del entorno tecnológico), como sistema operativo, servidor de aplicaciones, framework, librerías, base de datos, particularidades, etc. .

Por lo que algo que tu aplicación no debe hacer, cuando la despliegas en un entorno que tiene una cierta exposición, por ejemplo en producción, es ofrecer más información de la cuenta. Un ejemplo clásico es el del entorno tecnológico de Microsoft, con la típica página de errores siguiente:

exposición de infrormación a través de incorrecto manejo de errores CWE200, desarrollo seguro

¿Cuál es el problema con esto? pues que un cibercriminal lo ve y se frota las manos, le estas enviando un mensaje claro: no se ha procedido a configurar y desplegar la aplicación correctamente en el entorno de producción. Esto además de darle información técnica sobre tu aplicación y como esta construida (información que le sirve para preparar un ataque), también le indica que muy probablemente la aplicación es insegura y por lo tanto podrá hackearla.

Este tipo de páginas las ofrece tu entorno tecnológico de manera muy acertada, pero únicamente para un entorno de desarrollo, un entorno donde se trabaje de forma interna con la aplicación y por lo tanto esta información técnica ayude a personal autorizado a identificar y corregir problemas antes. En ningún caso la aplicación debe estar configurada de esta manera cuando esta expuesta a usuarios, a personal no autorizado a trabajar con la aplicación de una manera invasiva.

A continuación vamos a ver un ejemplo de configuración insegura en un sistema que esta en producción, concretamente para el caso de una aplicación web de .Net C#, esto se configura en el archivo «Web.config». En esta configuración la sección de nombre «CustomError», declara con valor «On» que vamos a indicar una pagina de error personalizada que no exponga detalles a los usuarios, en lugar de su valor «Off» (inseguro) que ofrece todo el detalle técnico.

Ejemplo de configuración incorrecta: En modo «Off» tenemos exposición de información técnica. El valor «RemoteOnly» tampoco es seguro de forma completa, puesto que continuará exponiendo información de más a los usuarios que accedan desde localhost, y no en todo caso tienen por que ser personal autorizado.

<?xml version="1.0"?>
<configuration>
    <system.web>
        <customErrors mode="Off"/>
    </system.web>
</configuration>

Ejemplo de configuración segura: Con modo «On» disponemos una página personalizada, con la mínima información necesaria que ofrecer al usuario.

<configuration>
  <system.web>
    <customErrors mode="On" defaultRedirect="MyErrorPage.aspx">
      <error statusCode="500" redirect="MyInternalErrorPage.aspx"/>
      <error statusCode="404" redirect="MyPageNotFound.aspx" />
    </customErrors>
  </system.web>
</configuration>

Esto es solo un sencillo y típico ejemplo de mala configuración / olvido, aunque hay que revisar el código en profundidad para confirmar que la aplicación esta realizando un adecuado manejo de errores. Un correcto manejo de errores, revisar la respuesta de la aplicación a comportamientos inadecuados, maliciosos o no esperados, es uno de los elementos principales a revisar dentro de una auditoría de código SAST y pruebas dinámicas DAST. Esta es la cheat sheet de OWASP al efecto.

Puede tomarse como principio, el exponer al usuario únicamente la información que requiera conocer para poder continuar adelante, por ejemplo contactando al servicio técnico. Esta información será la mínima y más genérica posible, dentro de ayudar al diagnostico del problema, pero sin representa nunca una información sensible que pueda servir para preparar un ataque. En este sentido, de requerirse cierta información técnica sobre el problema para un contexto concreto, no es lo mismo el nivel de profundidad de esa información, a continuación un ejemplo en entorno tecnológico .Net

Ejemplo de código inseguro: se expone a la capa cliente de la aplicación todo el stack trace (informe de los elementos activos en la pila de ejecución), ofreciendo información interna de más, de manera innecesaria, puesto que un usuario medio, incluso aunque tenga buena intención, no podrá hacer nada con ella.

var mensaje = "";

try {
    MetodoQueProvocaUnaExcepcion();
} catch (Exception ex) {
    // Toda la información
    mensaje = "Error message: " + ex.ToString();

    // Información sobre la excepción interna
    if (ex.InnerException != null) {
        mensaje = mensaje + " Inner exception: " + ex.InnerException.Message;
    }

    // Dónde ha sucedido
    mensaje = mensaje + " Stack trace: " + ex.StackTrace;
}

return mensaje; 

Ejemplo de código seguro: a la capa cliente se le expone solo un breve mensaje sobre el tipo error, si es que es necesario.

var mensaje = "";

try {
    MetodoQueProvocaUnaExcepcion();
} catch (Exception ex) {
    // Devolvemos solo un breve mensaje sobre el error que ha sucedido
    mensaje = "Error message: " + ex.Message;

	// Más información al log, a tratar de forma interna
    Log(ex.ToString());
}

return mensaje; 

Anti patrón, diseño inseguro: ignorar ocurrencia de errores del código

Ignorar los errores y el comportamiento no esperado de la aplicación es una mala práctica, que aunque muy obvia puede llegar al entorno de producción por un olvido o falta de revisión de un código, que fue desarrollado rápidamente en una fase más temprana. En el caso de tener lugar un incidente de seguridad, el log de la aplicación será uno de los recursos básicos y principales para poder dar una adecuada respuesta a este mientras sucede, y para realizar un correcto análisis forense digital después.

Ejemplo de código inseguro: código inmaduro que no tiene un adecuado control y registro de errores, muy probablemente no ha sido probado en profundidad y no se conoce plenamente el comportamiento que tendrá en un entorno de producción.

int[] numbers = new int[2];
try
{
    numbers[0] = 23;
    numbers[1] = 32;
    numbers[2] = 42;

    foreach(int i in numbers)
    Console.WriteLine(i);
}
catch(Exception ex)
{
  	// No hacer nada 
}

Ejemplo de código seguro: código con una disposición de control de errores aparentemente adecuada. Controlando y registrando en primer lugar probables excepciones concretas, que han sido revisadas durante las pruebas, para después pasar a la posibilidad de un correcto registro de una no esperada y de forma más genérica.

var mensaje = "";
int[] numbers = new int[2];
try
{
    numbers[0] = 23;
    numbers[1] = 32;
    numbers[2] = 42;

    foreach(int i in numbers)
    Log.Debug(i.ToString());
}
catch(IndexOutOfRangeException ex)
{
  	mensaje = "Error message: " + ex.Message;
  	// Siempre a log todas las excepciones
    Log.Error("Filling the array - An index was out of range!: " + ex.ToString());
}
catch(Exception ex)
{
  	mensaje = "Error message: " + ex.Message;
    Log.Fatal("Some sort of not expected error occured: " + ex.ToString());
  	/* En los casos en los que debamos controlar la ocurrencia de un error, pero aún así debamos seguir adelante con el, usaremos throw sin más:
    - throw ex; MAL relanza la exception pero limpia el stack trace
    - throw; BIEN relanza la exception manteniendo el stack trace original
    */
}
finally
{
    // Si tuvieramos que liberar algún recurso o realizar alguna acción final, esta es el lugar 
}

Configuración insegura: Validación de solicitudes deshabilitada

Usando tecnología ASP .Net la variable de configuración «ValidateRequest» no debe establecerse a «false», esta variable habilita una sencilla protección mediante la cual el server aplica un control sobre los datos de entrada que detecta patrones maliciosos, por ejemplo código HTML o JavaScript que no este correctamente codificado. Si los datos enviados son detectados como maliciosos, este control interrumpe el procesamiento de estos, lanzando una excepción de tipo «HttpRequestValidationException». Esta variable por defecto tiene el valor seguro «true».

Ejemplo de configuración insegura: tener la protección deshabilitada en el «Web.config».

<configuration>
   <system.web>
      <pages validateRequest="false" />
      <httpRuntime requestValidationMode="0.0" />
   </system.web>
</configuration>

En algún caso a causa de una funcionalidad especial de la aplicación, puede hacerse necesario deshabilitar esta protección, por ejemplo por que debe guardarse a base de datos código HTML sin codificar. Primero hay que confirmar que esto es necesario, por que la mayoría de veces no es así y existe una alternativa más segura. Si fuera necesario y se deshabilita, hay que poner especial atención para procesar estos datos potencialmente maliciosos de manera literal y tener en cuenta su correcta codificación o escape de caracteres especiales, siempre antes de una salida que pueda ser explotable, por ejemplo al imprimir estos datos en una pagina web.


Carencia de control sobre peticiones automáticas o fuerza bruta: Protección anti CSRF no habilitada

Para evitar ciertos ataques como el Cross-Site Request Forgery (CSRF), One-Click attack y mitigar los que consumen de forma automatizada una petición HTTP a nuestra aplicación web, se dispone de protección anti CSRF. Esta protección consiste en que nuestra aplicación web genera unos datos aleatorios cuando carga una página web con un formulario, para después comprobar si en la petición HTTP que se envía esta viene de vuelta.

Ejemplo de código HTML que nos carga una página con este control habilitado:

<form action="/Home/Test" method="post">
    <input name="__RequestVerificationToken" type="hidden" value="rN4xA8UDIHeo8YHYFZ4Bnc16Fr6oQdjpnV0hKPNpgHcMHNe5ZyE7tCP5ouWG3OyRGI8a9ihfpoaonfd1pf5o-RcQuG7339hNX0cKSTuNE0I1" />    
    <input type="submit" value="Submit" />
</form>

Como después de cargar el formulario con este código anti CSRF, nuestro método en el controlador lo requerirá, se le complica bastante la vida al atacante que puede estar queriendo automatizar peticiones HTTP. También se impide la explotación de ataques de tipo one-click. Aquí una explicación mucho mejor.

Para habilitar esta protección y también para buscar si la aplicación auditada dispone de ella o no, a continuación un ejemplo en entorno tecnológico .Net.

Ejemplo de código seguro: este método HTTP POST establece una protección anti CSRF con la etiqueta «[ValidateAntiForgeryToken]».

[HttpPost]  
[ValidateAntiForgeryToken]  
public ActionResult CreateProduct(Product product)  
{
  if (ModelState.IsValid)  
  {
    //your logic 
  }
  return View(ModelName);
}

Esta etiqueta no tiene por que ir en cada método, se puede establecer a un controlador entero o de forma global al proyecto, también puede configurarse bajo diferentes casuísticas, por lo que para revisar y determinar si la aplicación dispone de una correcta configuración en este sentido puedes buscar el siguiente texto «Antiforgery«.


Carencia de control sobre datos de salida: ataque cross-site scripting (XSS)

Cuando vas a mostrar un texto en tu web, por ejemplo imprimir por pantalla un simple campo comentario que obtienes de base de datos, si este campo tiene datos maliciosos, como por ejemplo código HTML o JavaScript, este puede llegar a procesarse en construcción de la página, con las posibilidades de ataque esto representa.

Por esta razón al imprimir un texto del que tenemos control sobre su contenido, debemos de hacerlo codificándolo de manera segura. Por ejemplo para un texto JavaScript como el siguiente:

<script\x20type="text/javascript">javascript:alert("XSS");</script>

Queremos que termine en la página web impreso de manera literal como se ve en la imagen a continuación:

malas prácticas, cross-site scripting XSS, desarrollo seguro

Y no queremos que este se llegue a procesar, como resultaría en la imagen siguiente:

malas prácticas, cross-site scripting XSS, desarrollo seguro 2

Esta diferencia puede conseguirse codificando correctamente la línea JavaScript, convirtiendo los caracteres interpretables en HTML a la propia codificación especifica de HTML, que se vería así:

&lt;script\x20type=&quot;text/javascript&quot;&gt;javascript:alert(1);&lt;/script&gt;

A continuación un ejemplo en entorno tecnológico .Net.

Ejemplo de código inseguro: imprimimos en nuestra vista HTML, un texto sin controlar, que puede ser a su vez código HTML.

string name = Request.QueryString["name"];
Response.Write("Hello " + name); // Noncompliant

Ejemplo de código seguro: imprimimos en nuestra vista HTML, un texto sin controlar del que no podemos confiar, pero al hacerlo mediante una llamada al método «HtmlEncode()», los caracteres especiales que pueden interpretarse como HTML estarán codificados y se imprimirán en la web de manera literal.

string name = Request.QueryString["name"];
name = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode(name, true);
Response.Write("Hello " + name);

Anti patrón, diseño inseguro: uso de método Transfer()

El método Transfer() de ASP .Net se usa para llevar al usuario de una vista a otra, pero es inseguro ya que representa lo que ha sido considerado un anti-patrón de diseño, ya que en lugar de realizar una nueva llamada HTTP, como se espera de una página web, realiza por así decirlo una «falsa carga» de esta nueva URL en la parte servidor, devolviendo el resultado al navegador sin ni siquiera cambiar la URL de la barra de direcciones:

vulnerabilidades y malas prácticas del código, desarrollo seguro 3

Esta mala práctica que por suerte es antigua y casi no se usa hoy en día, causa problemas de seguridad puesto que se puede cargar una vista protegida desde otra de menor protección; saltando con esa llamada en la parte servidor, los controles de autenticación y autorización que la segunda vista pudiera tener. Esto ocurre por que la ejecución en la parte servidor es directa, no se procesa de nuevo el ciclo de vida de la petición HTTP. Además esta forma de enviar o redireccionar al usuario a otra vista web produce confusión, tanto al usuario como desde el punto de vista de arquitectura, al no reflejarse este cambio en la parte cliente, no respetando el protocolo HTTP y sus cualidades: Stateless, Restful y el paradigma web de petición de un recurso.

Ejemplo de código inseguro: uso de Transfer().

using System;    
public partial class _Default : System.Web.UI.Page   
{    
    protected void Page_Load(object sender, EventArgs e)    
    {    
    }    
    protected void Button1_Click(object sender, EventArgs e)    
    {    
        Server.Transfer("SecondPage.aspx");
    } 
}    

Ejemplo de código seguro: uso de Redirect() en su lugar.

using System;    
public partial class _Default : System.Web.UI.Page   
{    
    protected void Page_Load(object sender, EventArgs e)    
    {    
    }    
    protected void Button1_Click(object sender, EventArgs e)    
    {    
        Response.Redirect("SecondPage.aspx");
    } 
}   

Anti patrón, diseño inseguro: configuración de sesión «Cookieless»

En .Net la propiedad de configuración «SessionStateSection.Cookieless» indica si la aplicación guarda la sesión en una cookie HTTP o en la URL, la opción de pasar el ID o token de sesión a través de la URL es una muy mala practica por razones obvias. Un token de sesión sirve para autenticarte por lo que es un dato que tiene carácter secreto, al igual que una contraseña, este no debe estar a la vista en la URL, esto es inseguro por que se expone mucho innecesariamente, puede verse en la barra de direcciones y además queda guardado en el historial del navegador. Esta propiedad de configuración tiene disponible los siguientes valores: Autodetect, UseDeviceProfile, UseUri, true, false y UseCookies. Todos son inseguros excepto los que indican usar cookies específicamente y por lo tanto aseguran su uso en todo caso: «false» o «UseCookies».

Ejemplo de código inseguro: con «true» la sesión pasará a enviarse en la propia URL.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<system.web>
		<sessionState cookieless="true" mode="InProc" />
	</system.web>
</configuration>

Ejemplo de código seguro: con «false» se usarán cookie para guardar la sesión.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<system.web>
		<sessionState cookieless="false" cookieName="ASP.NET_SessionID" mode="InProc" />
	</system.web>
</configuration>

Configuración insegura: ventanas de tiempo de duración de sesión muy grandes

Una vez nos autenticamos en la aplicación, el tiempo de duración de la sesión que se nos ofrece esta, debe ser el estrictamente necesario para usar esta de manera funcional, no más. Cuando una aplicación nos permite recordar la contraseña o nos deja abierta una sesión de unas horas, esto la expone a un uso no autorizado innecesariamente. Para establecer un tiempo de duración de la sesión, como referencia básica el proyecto OWASP propone lo siguiente:

  • Aplicaciones o usuarios, que tienen acceso a funcionalidades de alta criticidad: duración de sesión de 2-5 minutos.
  • Aplicaciones o usuarios, que no tienen acceso a funcionalidades de alta criticidad: duración de sesión de 15-30 minutos.

Ejemplos de funcionalidad crítica son acceso a datos sensibles como los personales o financieros, posibilidad de modificar el comportamiento del sistema por ejemplo interrumpiendo un proceso, etc… El tiempo siempre debe establecerse de la siguiente manera: mínimo tiempo posible sin llegar a afectar al desempeño práctico de los usuarios y procesos.

A continuación un ejemplo de configuración para entorno tecnológico .Net.

Ejemplo de código inseguro: la propiedad «timeout» del estado de la sesión se especifica en número entero de minutos, 100 es demasiado tiempo.

<configuration>
      <system.web>
         <sessionState timeout="100"></sessionState>
      </system.web>
</configuration>

Ejemplo de código seguro: valor mucho más acertado.

<configuration>
      <system.web>
         <sessionState timeout="10"></sessionState>
      </system.web>
</configuration>

Configuración insegura: cookies sin flags «Secure» y «HttpOnly»

La configuración segura para las cookies de la aplicación pasa por que todas tengan activado el flag «Secure» y casi todas (las que puedan) el flag «HttpOnly».

  • «Secure«: Con este flag establecemos que las cookies de la aplicación solo puedan enviarse si la conexión es segura y HTTPS, si alguien se conecta a la aplicación mediante HTTP las cookies no se enviarán. La configuración correcta es a true, a causa de que la propia configuración correcta de un site web es que este no pueda consumirse mediante HTTP. Con ello las cookies no se enviarán por HTTP sin cifrado, por lo que en una situación normal (si la conexión cifrada no es vulnerada), nos aseguramos que nadie pueda leerlas en texto plano durante la comunicación.
  • «HttpOnly«: Con este flag indicamos al navegador, que la cookie indicada no debe estar accesible desde la parte cliente, solo se recibirá para su posterior reenvío en la siguiente petición HTTP. Por lo tanto desde nuestro navegador y desde el código JavaScript que ejecuta la página no se puede acceder a ella. Esto nos protege de ataques como el XSS que a veces busca leer datos de cookies sensibles, como por ejemplo el token de sesión, para robar estos secretos. Si la cookie es para la parte servidor, como por ejemplo en el caso de un token de autenticación, activaremos esta opción ya que el navegador no la necesita y tenerla accesible en la parte cliente no nos ofrece nada excepto inseguridad.

Ejemplo de código seguro: establece las cookies con el flag «Secure» y «HttpOnly» a true, el primer ejemplo desde el «Web.config» y el segundo desde código.

<system.web>
    ...
    <httpCookies httpOnlyCookies="true" requireSSL="true" />
</system.web>
Response.Cookies.Add(
    new HttpCookie("key", "value")
    {
        HttpOnly = true,
        Secure = true,
    });

Configuración insegura: tener desactivado «regenerateExpiredSessionID»

Esta propiedad de configuración «regenerateExpiredSessionID» establece que cuando le envían al server un identificador de sesión que ya esta caducado, este debe iniciar una nueva sesión con un nuevo ID o token. Algo que puede parecer obvio y de hecho la configuración por defecto es correcta a «true», pero que por comodidad para el entorno de desarrollo, error u olvido puede haber pasado a producción de esta manera insegura.

Un token de sesión caducado no sirve para nada, no solo debemos autenticar desde cero al supuesto usuario, si no que además se creara un token diferente. En este caso si un atacante roba un token y este expira, no se le abrirá una posibilidad de usar una nueva sesión bajo el mismo token.

Ejemplo de código inseguro: la propiedad establecida a «false».

<configuration>
  <system.web>
    <!-- MAL las dos -->
    <sessionState cookieless="true" regenerateExpiredSessionId="false" />
  </system.web>
</configuration>

Ejemplo de configuración segura: la propiedad establecida a «true».

<configuration>
  <system.web>
    <!-- BIEN las dos -->
    <sessionState cookieless="false" regenerateExpiredSessionId="true" />
  </system.web>
</configuration>

Mala práctica: no resetear la sesión cuando el usuario cierra esta (hace log out)

Que los usuarios de nuestra aplicación cierren la sesión cuando dejen de hacer uso de ella es una buena práctica que aporta seguridad, los usuarios suelen estar familiarizados con esto, sobre todo en aplicaciones de cierta criticidad como una bancaria. Dar al botón de «log out» o cierre de sesión nos aporta seguridad en todas las aplicaciones, por ello este botón siempre debe existir y estar bien visible para facilitarle al usuario un comportamiento seguro.

Cuando ejecutamos desde el código la funcionalidad de este botón, debemos invalidar el token de sesión actual que este usando el usuario, para que no vuelva a poder usarse sin una nueva autenticación. Además es adecuado eliminar este dato por completo, por ejemplo borrándolo de la cookie, para ni siquiera dar la posibilidad de verlo a un tercero y mostrar con ello fortificación de la aplicación.

A continuación un ejemplo en entorno tecnológico .Net. Para localizar la mala práctica, deberíamos confirmar que la aplicación no dispone de la funcionalidad de cerrar sesión, o en caso de disponer de ella, su implementación no es completa.

Ejemplos de código seguro: Con «Abandon()» invalidamos la sesión actual y con «Clear()» borramos los valores de las credenciales usadas de la colección de estado de la sesión.

// ejemplo simplificado, solo para lo citado
public static void ResetWebSession() {
  System.Web.HttpContext.Current.Session.Abandon();
  System.Web.HttpContext.Current.Session.Clear();
  // Pisa la cookie con una cadena de texto vacía 
  System.Web.HttpContext.Current.Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", ""));
}

// ejemplo más completo
public void SignOut(HttpResponse response, HttpSessionState sessionState) {
  var user = GetUser(false);
  Logger.Log.Error(string.Format("User {0} sing out.", user.UserName));

  response.Cache.SetCacheability(HttpCacheability.NoCache);
  response.Cache.SetExpires(DateTime.Now);

  FormsAuthentication.SignOut();
  sessionState.Abandon();
  sessionState.Clear();
  
  // Pisa la cookie con una cadena de texto vacía 
  response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", ""));
  response.Cookies["ASP.NET_SessionId"].Expires = DateTime.Now.AddMonths(-20);

  response.Redirect(@"~/Index.aspx");
  response.End();
}

Diseño inseguro: insuficiente aleatoriedad

A veces necesitamos generar un dato que posteriormente un atacante no pueda inferir, por ejemplo pensemos en un ID de sesión. El usuario realiza una petición al proceso de autenticación, al login, y este le devuelve resultado correcto y un ID de sesión. Dicho usuario guardará este ID de sesión para posteriormente usarlo en cada petición HTTP demostrando con su posesión que es él.

malas prácticas, session ID, sesión token, desarrollo seguro 4

Respecto de un atacante, no solo debemos de preocuparnos de que no llegue a poder robar el ID de sesión, también debemos de proteger el sistema para que no pueda llegar a adivinarlo. Que no pueda mediante un ataque por fuerza bruta, a base de intentar diversas combinaciones acertar su valor; que con un ID anterior no pueda imaginar cual será el nuevo, los IDs no pueden tener relación entre ellos. Es por esto que un ID de sesión debe tener una generación lo suficientemente aleatoria, para que no pueda ser posible inferirlo y acertar su valor.

A continuación un ejemplo en entorno tecnológico .Net. Algunos valores como el que obtenemos de llamar a «System.Random()», pensamos que son aleatorios y tienen una cierta aleatoriedad, pero muy débil. Son pseudo-aleatorios, usan como semilla la marca de tiempo actual del procesador, que es predecible sobre un cierto rango. Puedes hacer la prueba con una sencilla aplicación que genere valores aleatorios, verás que durante muchas peticiones al método «System.Random.Next()», este te devuelve el mismo valor repetido, esto es a causa de se solicitan a partir de la misma marca de tiempo.

Ejemplo de código inseguro: El ID de sesión usa un GUID típico de .Net, estos solo tienen 16 bytes (128 bits que resultan en 32 caracteres, como un MD5), además la generación del valor no tiene suficiente aleatoriedad. Gran parte del valor del GUID tiene como base una marca de tiempo, la fecha y hora, y el propio identificador MAC del equipo que lo genera. Un GUID da una garantía bastante fuerte de ser único, pero no así de no poder inferirse, no debe ser usado como valor aleatorio.

HttpContext.Session["id"] = Guid.NewGuid().ToString();
var id = (string)(HttpContext.Session["id"]);
ViewBag.SessionID = id;
return View();

Ejemplo de código seguro: la clase «RNGCryptoServiceProvider» esta diseñada especialmente para ofrecer una aleatoriedad suficientemente fuerte, introduce una cierta entropía que obtiene a partir de factores variables del sistema operativo, además puedes pedirle una mayor longitud en bytes para fortificar criptográficamente aún más el token.

//RNGCryptoServiceProvider is an implementation of a random number generator.
byte[] random = new byte[100];

RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(random);
// The array is now filled with cryptographically strong random bytes.

HttpContext.Session["id"] = Convert.ToBase64String(random);
var id = (string)(HttpContext.Session["id"]);
ViewBag.SessionID = id;
return View();

Configuración insegura: no encriptación de tickets de autenticación

La autenticación basada en formularios de una aplicación ASP.NET, en su configuración debe tener habilitada la encriptación que protege la cookie de autenticación, esta debería ser legible solo para la parte servidor.

Ejemplo de código seguro: la variable de configuración «protection» establece la forma en la que se protegen los datos de la cookie que almacena el ticket de autenticación. Establecida a «None» la deja en texto plano sin protección.

<?xml version="1.0" encoding="utf-8" ?>
 <configuration>
  <system.web>
   <authentication mode="Forms">
    <forms loginUrl="login.aspx" protection="None" timeout="1000" path="/" >
    </forms>
   </authentication>
  </system.web>
 </configuration>

Ejemplo de configuración segura: la variable de configuración «protection» debe estar fijada a «All».

<?xml version="1.0" encoding="utf-8" ?>
 <configuration>
  <system.web>
   <authentication mode="Forms">
    <!-- conviene cambiar el nombre de la cookie por defecto "ASPXAUTH" (si no estableces nada) a otro, en este ejemplo establecemos "miformulario.ticket" --> 
    <forms name="miformulario.ticket" loginUrl="login.aspx" protection="All" timeout="10" path="/">
    </forms>
   </authentication>
  </system.web>
 </configuration>

Mala práctica: insuficiente criptografía, algoritmo de cifrado débil

Los algoritmos criptográficos que usa la aplicación y el sistema por extensión, deben revisarse para confirmar que son suficientemente fuertes, ejemplo de algoritmos desfasado y considerados inseguros son los siguientes: DES (56-bit key), RC4, o implementaciones propias que no han sido contrastadas en la industria y el mundo académico. Así mismo debe revisarse la longitud de la key, la mayoría de los sistemas criptográficos requieren un tamaño de clave suficiente para ser robustos contra ataques de fuerza bruta, un ejemplo es RSA implementándose con una clave muy corta como 768 bits, esta debe de ser de 1024 o más, preferiblemente 2048.

Ejemplo de configuración insegura: uso de «DES», un algoritmo criptográfico que ha quedado desfasado y es considerado muy inseguro, hay disponibles ataques que consiguen vulnerarlo.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <system.web>
      <machineKey 
                  decryptionKey="AutoGenerate,IsolateApps"
                  validationKey="AutoGenerate,IsolateApps"
                  decryption="DES" />
   </system.web>
</configuration>

Ejemplo de configuración segura: uso de «AES», un algoritmo criptográfico actualmente considerado seguro, adoptado como estándar de cifrado incluso por organismos gubernamentales para información clasificada. Sin ataques funcionales disponibles.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <system.web>
      <machineKey 
                  decryptionKey="AutoGenerate,IsolateApps"
                  validationKey="AutoGenerate,IsolateApps"
                  decryption="AES" />
   </system.web>
</configuration>

Igualmente para algoritmos hash criptográficos, como: MD2, MD4, MD5, MD6, HAVAL-128, HMAC-MD5, DSA (que usa SHA-1), RIPEMD, RIPEMD-128, RIPEMD-160, HMACRIPEMD160 y SHA-1 ya no se consideran seguros, porque es posible llegar a producir colisiones en ellos, con mayor o menor esfuerzo computacional. Estos pueden ser sustituidos por SHA-256 por ejemplo.


Carencia de control sobre datos de entrada: Redirecciones y reenvíos

Cuando se hace necesario realizar una redirección o un reenvío desde el propio código, todos los datos deben tener una validación, por ejemplo la URL debe estar controlada. A continuación un ejemplo en entorno tecnológico «.Net».

Ejemplo de código inseguro: Una redirección a una URL, a partir de un dato introducido sin ningún control y por lo tanto potencialmente malicioso.

protected void Page_Load(object sender, EventArgs e)   
{  
    string url = Request.QueryString["url"];
	Response.Redirect(url);
}

Ejemplo de código seguro: Si es completamente necesario, realizar una redirección a un URL, a partir de un dato que introduce el usuario, y por lo tanto no confiable, validamos este. No debemos dejar cabida a la posibilidad de juego con este tipo de ejecución.

protected void Page_Load(object sender, EventArgs e)   
{  
    string profileNumber = Request.QueryString["profileNumber"];
  	// validación de dato de entrada: número de perfil, cadena de texto con 8 números
   if (new Regex(@"^[0-9]{8,8}$").IsMatch(profileNumber))
    {
        Response.Redirect("http://www.miWebsite.com/" + profileNumber);
    }
    else
    {
        // Gestionar respuesta a un comportamiento anomalo, probablemente malicioso
    }
}

Configuración insegura: insuficiente fortaleza de contraseñas

Revisa en el código los puntos en los que la aplicación o el usuario establecen una contraseña inicial, y después el punto en el que la cambia. Si el código tiene un buen diseño, el método final que establece la contraseña debería se el mismo (SOLID). En este punto del código se verán las condiciones que controlan que la nueva contraseña sea valida, o en otras palabras, sus requisitos mínimos. Un ejemplo de estos requisitos, configurados de manera correcta que garantizan una contraseña de una fortaleza aceptable son los siguientes (aunque dependiendo de la criticidad del contexto estos pueden quedarse insuficientes):

  • Una longitud mínima de 8 o 9 caracteres: el usuario medio de tu aplicación o sistema la establecerá con esta longitud mínima a la que le obligues, por lo que pedir una longitud mínima que sea variable (según el momento pedirle al usuario de forma aleatoria o 8 o 9 o 10 caracteres de longitud), es una buena practica para complicar un ataque de tipo rainbow tables.
  • Un mínimo de caracteres en mayúscula y un mínimo de caracteres en minúscula: para garantizar la variación entre ambos conjuntos de caracteres, por ejemplo dos y dos.
  • Un mínimo de números y letras: para garantizar la variación entre ambos conjuntos de caracteres, por ejemplo dos y dos.
  • Un mínimo de caracteres especiales: para garantizar que al menos introducimos por ejemplo, un carácter especial.
  • Comprobación frente a una lista de contraseñas comunes: contrastar la futura contraseña con una lista predeterminada de las más comunes. Estas listas como por ejemplo el «rockyou», son de las primeras que se prueban en un ataque de fuerza bruta.

Una contraseña no debe ser potencialmente inferible, es decir, no puede imaginarse o sugerirse, a veces una contraseña que reúne todas las características anteriores puede ser inferible por el contexto. Un ejemplo claro es «S3cur17y.», que simula la palabra «security» y le añade un punto al final, esto es predecible, también si por ejemplo la aplicación va a desplegarse para una organización que se llama «contoso», la password «contoSO.2021», será fácilmente imaginable. Estos problemas se solucionan usando un generador de contraseñas aleatorias de calidad.

Ejemplo de código inseguro: no comprobación del nuevo valor de contraseña.

protected void btn_change_password_Click(object sender, EventArgs e) {
  string pass_form;
  pass_form = txt_password.Text;

  if (string.IsNullOrEmpty(pass_form) == true) {
    lbl_error.Visible = true;
  } else {
    if (u.ChangePassword(OldPasswordTextbox.Text, PasswordTextbox.Text)) {
      Msg.Text = "Password changed.";
    } else {
      Msg.Text = "Password change failed. Please re-enter your values and try again.";
    }
  }
}

Ejemplo de código seguro: ejemplo de aplicación de una expresión regular, como un primer paso para al menos revisar cumplimiento de ciertos requisitos de complejidad / fortaleza.

using System;
using System.Text.RegularExpressions;

class Program
{
    static void Main()
    {
        Regex regex = new Regex(@"^(?=.*[A-Z].*[A-Z])(?=.*[!@#$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*[a-z]).{8,30}$");

        Match match = regex.Match("contoSO.2021");

        if (match.Success)
        {
            Console.WriteLine("MATCH VALUE: " + match.Value);
        }
    }
 /*
 ^                         Start anchor
(?=.*[A-Z].*[A-Z])        Ensure string has two uppercase letters.
(?=.*[!@#$&*])            Ensure string has one special case letter.
(?=.*[0-9].*[0-9])        Ensure string has two digits.
(?=.*[a-z].*[a-z].*[a-z]) Ensure string has three lowercase letters.
.{8}                      Ensure string is of length 8.
$                         End anchor.
 */
}

Anti patrón, diseño inseguro: almacenar contraseñas en texto plano

La aplicación ni necesita, ni puede en ningún momento almacenar una contraseña suministrada por el usuario (en texto plano), solo requiere almacenar un computo hash de esta, con el que luego puede perfectamente confirmar si un nuevo envío de este valor, coincide con el original establecido. Un algoritmo hash suficientemente fuerte es SHA-256 o preferiblemente SHA-512. A continuación un ejemplo en entorno tecnológico .Net, para autenticación basada en formularios de una aplicación ASP.NET.

Ejemplo de configuración insegura: la variable de configuración «passwordFormat» establece la forma en la que se almacenan las credenciales, para el caso del valor «Clear» guarda en texto plano el valor de la contraseña suministrada por el usuario.

<authentication mode="Forms">
      <forms loginUrl ="Default.aspx" DefaultUrl="~/admin.aspx">
        <credentials passwordFormat="Clear">
          <user name="mohit" password="mohit"/> 
        </credentials>
      </forms>
</authentication>

Configuración insegura: posibilidad de conexión no segura

Una aplicación web debe de forzar la comunicación a HTTPS, debe ser funcional en modo seguro y únicamente en modo seguro, no ofreciendo la posibilidad de consumo de sus servicios web sobre HTTP. Esto se debe a que por muy segura que brinde la comunicación por HTTPS, si además la comunicación por HTTP es posible, ya sea por equivocación de algún usuario o por engaño de algún atacante a este, puede llegar a establecer conectividad de modo no seguro y por ejemplo sufrir un robo de credenciales.

Según el entorno tecnológico de la aplicación, se debe garantizar la conexión en modo seguro a varios niveles (seguridad en profundidad), por ejemplo empleando HTTP Strict Transport Security (HSTS) con la cabecera HTTP «Strict-Transport-Security». A continuación un ejemplo en entorno tecnológico .Net, para la autenticación basada en formularios de una aplicación ASP.NET.

Ejemplo de configuración insegura: a continuación un ejemplo de configuración que permite el uso de la aplicación en modo no seguro. Al establecer la propiedad «requireSSL» a «false», cuando esta debe de tener el valor «true» para usar esta comprobación que evita su carga mediante HTTP inseguro, como una protección adicional.

<authentication mode="Forms">
    <forms requireSSL="false"
        defaultUrl="Default.aspx"
        loginUrl="Login.aspx"
        path="/"
        slidingExpiration="false"
        timeout="360"
        name=".ASPXFORMSAUTH">
    </forms>
</authentication>

Configuración insegura: persistencia de cookies

La aplicación debe evitar la persistencia en el almacenamiento de las cookies, más allá de lo estrictamente necesario. Por ejemplo para la cookie de sesión recordemos lo siguiente:

Una vez nos autenticamos en la aplicación, el tiempo de duración de la sesión que se nos ofrece esta, debe ser el estrictamente necesario para usar esta de manera funcional, no más. Cuando una aplicación nos permite recordar la contraseña o nos deja abierta una sesión de unas horas, esto la expone a un uso no autorizado innecesariamente. Para establecer un tiempo de duración de la sesión, como referencia básica el proyecto OWASP propone lo siguiente:

  • Aplicaciones o usuarios, que tienen acceso a funcionalidades de alta criticidad: duración de sesión de 2-5 minutos.
  • Aplicaciones o usuarios, que no tienen acceso a funcionalidades de alta criticidad: duración de sesión de 15-30 minutos.

Para evitar la persistencia de la cookie de sesión deben buscarse los siguientes patrones, este un ejemplo en entorno tecnológico .Net, para un proyecto ASP.NET MVC.

Ejemplo de código inseguro: revisa el código donde la configuración de «DisplayRememberMe» se establezca a true, esta persiste la cookie de sesión. Debe ser «False».

<form id="form1" runat="server">
	<asp:Login ID="Acceso" runat="server" DisplayRememberMe="True" LoginButtonText="Entrar" LoginButtonType="Button"></asp:Login>
</form>

Ejemplo de código inseguro: revisa el código donde la configuración del Boolean «createPersistentCookie» se establezca a «true», esta configuración persiste la cookie de sesión. Debe ser «false».

FormsAuthentication.SetAuthCookie(txtUsername.Text, true);
Response.Redirect(@"~/Index.aspx");

Ejemplo de código inseguro: revisa el código donde la configuración del Boolean «createPersistentCookie» se establezca a «true», esta configuración persiste la cookie de sesión. Debe ser «false».

FormsAuthentication.RedirectFromLoginPage(txtUsername.Text, true);

Ejemplo de código inseguro: revisa el código donde la configuración del Boolean «createPersistentCookie» se establezca a «true», esta configuración persiste la cookie de sesión. Debe ser «false».

Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUsername.Text, true))

Ejemplo de código inseguro: revisa el código donde se establece la duración de persistencia de las cookies, para confirmar si están cumpliendo las ventanas de tiempo definidas.

//create a cookie 
HttpCookie myCookie = new HttpCookie("UserName"); 

//Add key-values in the cookie 
myCookie.Values.Add("userid", objUser.id.ToString()); 

//set cookie expiry date-time. Made it to last for next 24 hours. 
myCookie.Expires = DateTime.Now.AddHours(24); 

//Most important, write the cookie to client. 
Response.Cookies.Add(myCookie); 

Configuración insegura: parámetros de autenticación permisivos

Revisa en la configuración donde se estén estableciendo parámetros muy permisivos e inseguros, como puede ser permitir un número grande de errores en la introducción de la contraseña, permitir establecer contraseñas muy cortas, etc… La configuración debe ser lo más restrictiva posible, dentro de no afectar funcionalmente a los usuarios.

Ejemplo de código inseguro: el número de intentos de login introduciendo credenciales incorrectas es muy alto («maxInvalidPasswordAttempts»), al igual que la ventana de tiempo en minutos que se establece para contabilizarlos es muy corta («passwordAttemptWindow»). Para esta configuración a continuación, puedes probar credenciales 60 veces cada minuto.

<membership defaultProvider="SqlProvider" userIsOnlineTimeWindow="20">  
  <providers>
    <add name="SqlProvider" type="System.Web.Security.SqlMembershipProvider"
      connectionStringName="SqlServices"  
      requiresQuestionAndAnswer="false"  
      maxInvalidPasswordAttempts="60"  
      passwordAttemptWindow="1"  
      applicationName="MyApplication" />  
  </providers>  
</membership>  

Ejemplo de código inseguro: configuración muy permisiva e insegura, el número de intentos de login introduciendo credenciales incorrectas es muy alto («maxInvalidPasswordAttempts»), y la longitud permitida de contraseña es muy corta («minRequiredPasswordLength»).

 <authentication mode="Forms">
  <forms loginUrl="~/Account/LogOnCust" timeout="2880" />
 </authentication>
<membership>
  <providers>
    <clear />
    <add name="AspNetSqlMembershipProvider" 
    type="System.Web.Security.SqlMembershipProvider"
    connectionStringName="WebDBConnection" 
    maxInvalidPasswordAttempts="10" 
    minRequiredPasswordLength="6" 
    passwordAttemptWindow="2" 
    applicationName="/" />
  </providers>
</membership>

Configuración insegura: elegir el modo de cifrado ECB (Electronic Code Book Mode)

Cuando toca proteger la información en tu aplicación o sistema mediante cifrado de esta, además de elegir un algoritmo de cifrado que tenga la suficiente fuerza, como por ejemplo AES con 256 de longitud de key, debes evitar usar el modo de cifrado ECB. Independientemente del algoritmo de cifrado, el lugar en el código donde lo implementes o la librería que lo lleve a cabo, dispondrá de una configuración en cuanto al modo de operación de cifrado por bloques, este puede ser por ejemplo uno de los dos más comunes ECB o CBC.

Los datos se cifraran por bloques de 128 bits y el modo de implementar la operación repetitiva de diferenciar y tratar estos bloques es el modo de cifrado. El modo de cifrado ECB o Electronic Code Book Mode, es el más simple e inseguro por que dará el mismo producto para el mismo bloque de datos. Es decir, si le pasas la misma información original a cifrar dos veces, el resultado cifrado será idéntico, esto es una vulnerabilidad que abre superficie a varios tipos de ataque (chosen plaintext attack CPA y CPA2). Aunque un atacante no disponga de la clave secreta con la que estas procediendo a cifrar los datos, puede determinar cuales de estos están repetidos, he incluso si conoce un bloque de la información previo al cifrado y su producto cifrado (por ejemplo uno que sea común y se repita), puede comprobar claves en base a confirmar si el producto cifrado resultado es el mismo.

modos de cifrado AES, criptografía, desarrollo seguro
Ejemplo gráfico de como usando el modo ECB puede comprobarse la repetición de bloques e incluso comprometer el cifrado.

Sin embargo si usas un modo de cifrado más avanzado como por ejemplo CBC o Cipher-block chaining, este encadena un bloque tras otro, aplicando sobre estos una operación mediante un vector de inicialización que se establece para el primer bloque y después va variando, dependiendo este del bloque anterior. Por lo que cada bloque producto del cifrado es único, y además depende del bloque previo para descifrase. En la imagen anterior se muestra un ejemplo de como este sistema de encadenamiento produce una necesaria capa más de protección al producto resultado del cifrado.

Como hemos visto aunque estés usando AES con una tamaño de clave de 256 tu código puede ser vulnerable en el modo de cifrado. No debe configurarse ECB y adicionalmente debe seleccionarse un padding seguro como es PKCS7, ya que rellenar sencillamente con bytes establecidos en cero («PaddingMode.Zeros») también reduce la complejidad de la operación de cifrado por bloques y el producto cifrado resultante. A continuación un ejemplo en entorno tecnológico .Net C#.

Ejemplo de código inseguro: Elección de configuración mediante modo de cifrado ECB (línea 9) y padding mediante ceros (línea 10). En la línea 6 también se puede apreciar una longitud de clave secreta de 128 bits, la más pequeña posible según el estándar de AES, y por lo tanto la menos segura.

 public static string EncryptBySymmetricKey(string text, string sek) {
   try {
     byte[] dataToEncrypt = Convert.FromBase64String(text);
     var keyBytes = Convert.FromBase64String(sek);
     AesManaged tdes = new AesManaged();
     tdes.KeySize = 128;
     tdes.BlockSize = 128;
     tdes.Key = keyBytes;
     tdes.Mode = CipherMode.ECB;
     tdes.Padding = PaddingMode.Zeros;
     pICryptoTransform encrypt__1 = tdes.CreateEncryptor();
     byte[] deCipher = encrypt__1.TransformFinalBlock(dataToEncrypt, 0, dataToEncrypt.Length);
     tdes.Clear();
     string EK_result = Convert.ToBase64String(deCipher);
     return EK_result;
   } catch (Exception ex) {
     throw ex;
   }
 }

Ejemplo de código seguro: Elección de configuración mediante modo de cifrado CBC (línea 4) y padding PKCS7 (línea 5). En la línea 6 también se puede apreciar una longitud de clave secreta de 256 bits, la más grande posible en el estándar de AES, y por lo tanto la más segura.

// la key debe ser de byte[32]: 32 bytes = 256 bits 
private RijndaelManaged GetRijndaelManaged_SecureConfiguration(byte[] secretKeyBytes, byte[] IVInbytes) {
  var rijndaelManaged = new RijndaelManaged {
	Mode = CipherMode.CBC,
      Padding = PaddingMode.PKCS7,
      KeySize = 256,
      BlockSize = 128,
      Key = secretKeyBytes,
      IV = IVInbytes
  };
  return rijndaelManaged;
}

Configuración insegura: protección del ViewState deshabilitada

Uno de los mecanismos comúnmente encontrados en ASP.NET, para transmitir datos con la parte cliente es el «ViewState». Para proteger este campo que contiene información serializada sobre el estado de la página actual y es fuente habitual de exposición de información sensible y ataques, tenemos dos propiedades principalmente «EnableViewStateMac» y «ViewStateEncryptionMode».

La propiedad «EnableViewStateMac» le añade Message Authentication Codes (MAC) a cualquier modificación del ViewState, esto es añadirle un código de autenticación del mensaje, que consiste en un producto HASH calculado junto con una clave que únicamente posee la parte servidor y por lo tanto confirma la integridad del mensaje y su autenticidad. Es decir, la aplicación confirma que la información del ViewState no ha sido alterada durante su estancia en la parte cliente, y confirma que su última modificación tuvo lugar en la parte servidor antes del envío de estos datos. Un ataque clásico de no estar esta comprobación activada es por ejemplo cambiar el precio de un producto antes de pagarlo, mientras usas una aplicación web que tiene tienda, o algún otro dato determinante sobre la ejecución de la funcionalidad, si este dato esta almacenado en el ViewState.

Por otro lado la propiedad «ViewStateEncryptionMode», es para el caso en que la información que vayamos a introducir dentro del ViewState sea sensible y deba mantenerse secreta para la parte cliente, por lo tanto en este caso debe estar encriptada. En ASP.NET el ViewState puede parecer un campo encriptado, pero a veces sencillamente es una cadena de texto codificada en base64, fácilmente convertible a texto plano, si se requiere que este protegida de su lectura en la parte cliente debe establecerse en configuración su encriptación. El valor por defecto de esta propiedad es «Auto», y se comporta de la siguiente manera: solo cifra el estado de vista si un control lo solicita expresamente, por lo que este valor debe revisarse específicamente y debe estar establecido a «Always». Un ataque clásico de no estar esta protección habilitada, es poder leer el contenido que los desarrolladores han confiado al ViewState introduciéndolo en un decodificador especifico, como por ejemplo el que trae la herramienta de pruebas de seguridad Burp Suite.

Ejemplo de código inseguro: establecer la propiedad «EnableViewStateMac» a «false». El usuario puede modificar el contenido del ViewState en la parte cliente.

<%@ Page Language="C#" AutoEventWireup="false" Codebehind="DemoPage.aspx.cs"
Inherits="DemoAssembly.DemoPage" enableViewStateMac="False" %>
<!DOCTYPE html>

Ejemplo de configuración insegura: establecer la propiedad «EnableViewStateMac» a «false».

<configuration>
  <system.web>
    <pages enableSessionState="true" enableViewState="true" enableViewStateMac="false" enableEventValidation="false" validateRequest="false" pageParserFilterType="Microsoft.SharePoint.ApplicationRuntime.SPPageParserFilter, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=numbers" asyncTimeout="7" />
  </system.web>
</configuration>

Ejemplo de configuración segura: establecer la propiedad «EnableViewStateMac» a «true». Esto le impide al usuario / atacante modificar sus valores en la parte cliente (área de no confianza).

<configuration>
  <system.web>
    <pages enableViewStateMac="True" />
  </system.web>
</configuration>

Ejemplo de código inseguro: establecer la propiedad «ViewStateEncryptionMode» a «Never».

<%@Page enableViewStateMac="True" ViewStateEncryptionMode="Never" %>

Ejemplo de código seguro: establecer la propiedad «EnableViewStateMac» a «true» y «ViewStateEncryptionMode» a «Always». Con el valor «Always» el ViewState estará encriptado y por lo tanto su valor no será consultable desde la parte cliente (área de no confianza).

<%@Page enableViewStateMac="True" ViewStateEncryptionMode="Always" %>

Configuración insegura: tener en cuenta la ampliación configurada en el tiempo de duración de sesión

La aplicación debe establecer un tiempo de duración de sesión mínimo, respecto de ser lo más restrictivo posible sin llegar a afectar a la funcionalidad y el desempeño de los usuarios. Para establecer un tiempo de duración de la sesión, como referencia básica el proyecto OWASP propone lo siguiente:

  • Aplicaciones o usuarios, que tienen acceso a funcionalidades de alta criticidad: duración de sesión de 2-5 minutos.
  • Aplicaciones o usuarios, que no tienen acceso a funcionalidades de alta criticidad: duración de sesión de 15-30 minutos.

Una variable muy importante en este sentido es «SlidingExpiration» ya que afecta significativamente a la duración de la sesión, alargando esta según el usuario se mantenga activo. Funciona de la siguiente manera:

  • A «true» habilita la ampliación de la sesión: esto en la practica es que cada vez que el cliente realice una nueva solicitud de la página (petición HTTP) transcurrido más de la mitad del intervalo de tiempo de sesión, la sesión renovará su tiempo de caducidad, es decir, volverá a comenzar la cuenta atrás para cerrar su sesión. De esta manera preparando una automatización que realice una llamada al server, mediante por ejemplo una simple recarga de la página, la duración de la sesión se mantendrá activa de manera indeterminada. Si por ejemplo la duración de sesión para un usuario administrador es de 5 minutos, pasados 2 minutos y medio, una solicitud de página reestablecerá otros nuevos 5 minutos disponibles de sesión.
  • A «false» deshabilita la ampliación de la sesión: esto en la práctica es que el tiempo de duración configurado, por ejemplo 5 minutos de duración de sesión para un usuario administrador del sistema, durará concretamente eso, aunque el usuario se mantenga en la página usando su funcionalidad, a los 5 minutos su sesión se cerrará y deberá volver a autenticarse. Cumpliéndose la limitación de tiempo de sesión que pueda estar dictada por ejemplo por una política de seguridad de la organización.

Ejemplo de configuración insegura: con «SlidingExpiration» configurado a «true» (línea 6) el tiempo de sesión se ira ampliando con cada nueva petición, pudiendo mantener esta de forma indeterminada. El resto de variables también están configuradas de manera insegura, «cookieless» configurado a true introducirá el token de sesión en la URL y «requireSSL» a false no requerirá de una comunicación segura para enviar el token, pudiendo enviar este por HTTP sin TLS.

<authentication mode="Forms">
  <forms loginUrl="member_login.aspx"
    name=".ASPXFORMSAUTH"
    cookieless="true"
    requireSSL="false"
    slidingExpiration="true"
    timeout="5" />
</authentication>

Ejemplo de configuración segura: con «SlidingExpiration» configurado a «false» (línea 6) el tiempo de sesión no será ampliado con cada nueva petición, durando la sesión el tiempo configurado a tal efecto según los requisitos de seguridad, no más tiempo. El resto de variables también están configuradas de manera segura, «cookieless» configurado a «UseCookies» mantendrá el token de sesión como valor de cookie y «requireSSL» a true requerirá de una comunicación segura para enviar el token, no pudiéndose enviar este por HTTP sin TLS.

<authentication mode="Forms">
  <forms loginUrl="member_login.aspx"
    name=".ASPXFORMSAUTH"
    cookieless="UseCookies"
    requireSSL="true"
    slidingExpiration="false"
    timeout="5" />
</authentication>

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

error: