Archive

Archive for the ‘Seguridad’ Category

Detecting and Fixing XSS using OWASP tools

Much have been written about XSS vulnerabilities scanning. In this article we will try to go a little further and show how to fix them.

To illustrate the whole process, going from initial detection to providing a fix, we will use a very simple app consisting of two JSP pages: one is a payment form for credit card transactions and contains some XSS exploitable code. The other one has such code fixed: the later is just a patched version of the former. We will see how an attacker could trick users, exploiting the present XSS vulnerability, to collect their credit card data.

(Download vulnerable app)

XSS Attacks

 

The goal of XSS attacks is to have a injected script executed by the user web browser. In most cases, user is not even aware of what is going on. For further information about XSS please have a look at OWASP XSS Attack Description.

Let us have a look at our sample app. The vulnerable JSP (xss_html.jsp) contains the following code fragment:


<% 
final String amount = request.getParameter("amount"); 
Enumeration pNames = request.getParameterNames(); 
while (pNames.hasMoreElements()){     
   final String pName = pNames.nextElement();     
   final String pVal = request.getParameter(pName);     
%>
    <input type="hidden" name="<%=pName%>" />" value="" />;</pre>
<table>
<tbody>
<tr>
<td>Credit card</td>
<td><input type="text" maxlength="16" name="cc" size="16" value="" /></td>
</tr>
<tr>
<td>Exp Date (mm/yy)</td>
<td><input type="text" maxlength="2" name="expMonth" size="2" value="" />
/<input type="text" maxlength="2" name="expYear" size="2" value="" /></td>
</tr>
<tr>
<td>CVV2</td>
<td><input type="text" maxlength="2" name="expMonth" size="2" value="" />
/<input type="text" maxlength="2" name="expYear" size="2" value="" /></td>
</tr>
<tr>
<td colspan="2"><input id="button1" type="submit" name="button1" value="Pay" /></td>
</tr>
</tbody>
</table>

The form receives amount to charge as an HTTP Request parameter, collects credit card data form user and them charges her (of course, such last step is not included, you can try with any non-real credit card data). Users could be redirected here from any ecommerce site using a URL such as http://……/XSS_Vulnerable/xss_html.jsp?amount=12.25 (again, nobody would choose such a path for a payment gateway pretending to be a trusted one). But let us see what happens if an attacker tricks someone into loading a malicious URL as the one included in index.jsp instead when the user press Pay button (Firefox 24.0 for Linux):

firefox_vulnerable

Just an alert, but injected Javascript code could have created an image or some other kind of link, so the attacker would have been able to collect the data by just looking at Apache access logs. Indeed, some browsers are able to identify such threats and will not execute injected scripts, as shown (Chromium 28.0 for Linux).

chrome_refuses_exec_script

XSS Detection

 

Fortunately, there are a lot of tools that perform XSS threats scanning so, for the most common issues, there is no need to look at every line of code in every web page when trying to locate such vulnerabilities. One of those tools is OWASP Zed Attack Proxy Object (ZAP). Although it would not be fair to say it is just a XSS scanner, as it provides many many more interesting features.

ZAP can be used as a proxy (indeed, it is based on older Paros Proxy) being able to scan all pages accesed during the session. However, we are just going to introduce the URL( http://……/XSS_Vulnerable/xss_html.jsp?amount=12.25) and press the Attack button (we are using ZAP 2.2.2). To avoid several warnings and making scanning faster we disabled all scan types except for XSS.

ZAP_XSS_vulnerable

Starting from provided URL ( http://……/XSS_Vulnerable/xss_html.jsp?amount=12.25) ZAP has made several checks adding javascript code to be injected.

The tab at the bottom shows one successful attempt of a XSS attack. ZAP has replaced the numeric value of amount parameter by and URL encoded javascript code (as seen in URL field) which is just “><script>alert(1);</script> in plain text (as seen in Evidence field).

Moreover, in the tab above, where HTTP response is shown, the result of the XSS attack is clearly shown: the injected code in amount parameter first closes the double quotes (“) around value for amount field and closes HTML input (>). Afterwards, it adds the script alert(1); (<script>alert(1);</script>). The resulting HTML code to be executed by web browser contains:

<input type="hidden" name="amount" value=""><script>alert(1);</script>" />

XSS Fix

 
Although there is not a single fix for all XSS attacks, all of them are based on input validation, where “input” could be any from HTTP Request parameters, HTTP Headers values or even names… all depends on what the code uses as input.

In our sample app, a HTTP Request parameter is being used to write HTML code.

OWASP provides OWASP Enterprise Security API (ESAPI) in several languages, including, of course Java. ESAPI includes much more functionality related to security, from XSS and CSRF to crypto.

To fix our XSS vulnerability, we are just using a ESAPI encoder (ESAPI 2.1.0). The fix is based on writing the received amount parameter HTML encoded instead of as just received. This way, the user web browser will not execute the javascript code, as it will be seen as the value of the amount parameter.

The fix requires just HTML encoding the amount parameter (see xss_html_esapi.jsp) as follows:

<form method="POST" name="sendForm" id="sendForm" onsubmit="return sendPaymentRequest()">

<%
final String amount = request.getParameter("amount");
Enumeration<String> pNames = request.getParameterNames();
while (pNames.hasMoreElements()){
    final String pName = pNames.nextElement();
    final String pVal = request.getParameter(pName);

    final org.owasp.esapi.Encoder esapiEnc = DefaultEncoder.getInstance();
    final String encPVal = esapiEnc.encodeForHTML(pVal);

    %>
    <input type="hidden" name="<%=pName%>" value="<%=encPVal%>" />
    <%
}
%>

<table>

Running ZAP against fixed JSP (xss_html_esapi.jsp) does not report XSS Vulnerabilities.

Advertisements

Programación orientada a aspectos con Guice

Me gusta la programación orientada a aspectos por la cantidad de ifs que te quita. Ademas son ifs que tienes que ir repitiendo a lo largo del código.
Se me ha ocurrido hacer un pequeño ejemplo con JUnit. Vamos a hacer las tipicas comprobaciones de:

Las vamos a hacer de las dos formas, con aspectos y sin aspectos, demostrando que el resultado es el mismo, pero con aspectos queda mas limpio.

Antes de empezar con el Test, hay que configurar Shiro. Explicandolo por encima, tenemos un fichero que se llama shiro.ini, donde he definido los usuarios con su rol.

[users]
root = secret, admin
guest = guest, guest

[roles]
admin = *

También hay que configurar el modulo de Shiro para Guice, donde se le dice que se configure en base al contenido de shiro.ini:

package com.tododev.shiro;

import org.apache.shiro.config.Ini;
import org.apache.shiro.guice.ShiroModule;
import org.apache.shiro.realm.text.IniRealm;

import com.google.inject.Provides;

public class MyShiroModule extends ShiroModule{

	protected void configureShiro() {
        try {
            bindRealm().toConstructor(IniRealm.class.getConstructor(Ini.class));
        } catch (NoSuchMethodException e) {
            addError(e);
        }
    }

    @Provides
    Ini loadShiroIni() {
        return Ini.fromResourcePath("classpath:shiro.ini");
    }

}

Ahora vamos ya con el test. Este test contiene 4 metodos de test, que comprueban lo siguiente sobre el método de la interfaz Interface.doSomething:

  • Si no estas autenticado lanza una UnauthenticatedException.
  • Si no tienes el rol de “admin” lanza una UnauthorizedException.
  • Si le pasas el parámetro a nulo lanza una ConstraintViolationException.
  • Si estas autenticado, con el rol de “admin” y no pasas el parámetro a nulo, no lanza excepciones.

La gracia es que el test lo vamos a hacer con dos implementaciones diferentes de Interface.class:

  • Aop.class
  • NoAop.class

Es decir, se van a hacer 2 x 4 = 8 tests. El resultado tiene que se el mismo.


package com.tododev.shiro;

import static org.junit.Assert.fail;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.constraints.NotNull;

import org.apache.bval.guice.Validate;
import org.apache.bval.guice.ValidationModule;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.guice.aop.ShiroAopModule;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

/**
 * Vamos a hacer que el test se ejecute con la clase Parameterized. Esta clase
 * nos ejecutara 2 veces todos los tests, insertando en el constructor los
 * valores del metodo data().
 * 
 * @author jorge
 * 
 */
@RunWith(Parameterized.class)
public class GuiceAopTest {

	private final static String ADMIN_ROLE = "admin";
	private final UsernamePasswordToken ADMIN = new UsernamePasswordToken(
			"root", "secret");
	private final UsernamePasswordToken GUEST = new UsernamePasswordToken(
			"guest", "guest");
	private final Injector injector;

	/**
	 * Cuando se crea la instancia de GuiceAopTest.class, se le inserta uno de
	 * los valores del metodo data().
	 * 
	 * @param injector
	 */
	public GuiceAopTest(Injector injector) {
		this.injector = injector;
	}

	/**
	 * Devuelve una coleccion que contiene un inyector con AOP y otro que no. La
	 * idea es demostrar que los tests pasan satisfactoriamente de las dos
	 * formas posibles.
	 * 
	 * @return
	 */
	@Parameters
	public static Collection<Object[]> data() {
		Injector withAop = Guice.createInjector(new ShiroAopModule(),
				new MyShiroModule(), new ValidationModule(),
				new AbstractModule() {
					@Override
					protected void configure() {
						bind(Interface.class).to(Aop.class);
					}
				});
		Injector noAop = Guice.createInjector(new MyShiroModule(),
				new AbstractModule() {
					@Override
					protected void configure() {
						bind(Interface.class).to(NoAop.class);
					}
				});

		Object[][] data = { { noAop }, { withAop } };
		return Arrays.asList(data);
	}

	public static interface Interface {
		void doSomething(String value);
	}

	/**
	 * Implementacion preparada para tener aspectos.
	 * 
	 * @author jorge
	 * 
	 */
	public static class Aop implements Interface {

		@Validate
		@RequiresRoles(ADMIN_ROLE)
		@Override
		public void doSomething(@NotNull String value) {
			System.out.println(getClass() + ": " + value);
		}

	}

	/**
	 * Hace lo mismo que hace Aop, pero esta no usara aspectos.
	 * 
	 * @author jorge
	 * 
	 */
	public static class NoAop implements Interface {

		@Override
		public void doSomething(String value) {
			if (value != null) {
				Subject currentUser = SecurityUtils.getSubject();
				if (currentUser.isAuthenticated()) {
					if (currentUser.isPermitted(ADMIN_ROLE)) {
						System.out.println(getClass() + ": " + value);
					} else {
						throw new UnauthorizedException();
					}
				} else {
					throw new UnauthenticatedException();
				}
			} else {
				throw new ConstraintViolationException(
						new HashSet<ConstraintViolation<?>>());
			}
		}

	}

	/**
	 * Preparamos Shiro para ser usado.
	 */
	@Before
	public void before() {
		SecurityManager securityManager = injector
				.getInstance(SecurityManager.class);
		SecurityUtils.setSecurityManager(securityManager);
	}

	/**
	 * Deslogueamos al usuario si estuviera logueado.
	 */
	@After
	public void after() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.logout();
	}

	/**
	 * Vefirica que si no estas logueado salta una UnauthenticatedException
	 */
	@Test(expected = UnauthenticatedException.class)
	public void unauthenticated() {
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething("any value");
		fail("Unreachable code");
	}

	/**
	 * Verifica que si no tienes los permisos adecuados salta una
	 * UnauthorizedException
	 */
	@Test(expected = UnauthorizedException.class)
	public void unauthorized() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.login(GUEST);
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething("any value");
		fail("Unreachable code");
	}

	/**
	 * Verifica que si llamas al metodo con el String nulo, salta una
	 * ConstraintViolationException
	 */
	@Test(expected = ConstraintViolationException.class)
	public void constraintViolation() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.login(ADMIN);
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething(null);
		fail("Unreachable code");
	}

	/**
	 * Verifica el caso de que todo es correcto y no hay excepciones
	 */
	@Test
	public void allowed() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.login(ADMIN);
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething("any value");
	}

}


El resultado de los test es este:


Los 8 tests han pasado bien.

Este es el resultado que se muestra en la consola:

class com.tododev.shiro.GuiceAopTest$NoAop: any value
class com.tododev.shiro.GuiceAopTest$Aop$$EnhancerByGuice$$4ee6b586: any value

Os podeis fijar que la clase Aop “ha sido mejorada” por Guice, añadiendole los aspectos de Shiro y BVal. Lo curioso es que en las clases compiladas vemos lo siguiente:


La clase que aparece señalada debería llamarse GuiceAopTest$Aop$$EnhancerByGuice$$4ee6b586, pero se llama GuiceAopTest$Aop. Esto es porque Guice no hace los aspectos en tiempo de compilación, sino en ejecución. Esto tiene algunas limitaciones, pero en muchos casos resulta ser suficiente.

En conclusión:

Se puede comprobar lo limpio que queda el código usando aspectos. Así es mas fácil ver qué es lo que hace el método doSomething(). Además, esas anotaciones las podemos aprovechar y llevar a otros métodos que requieran esas comprobaciones.

Vulnerabilidad en Mysql permite evitar el mecanismo de autenticación

Una vulnerabilidad en ciertas versiones de MySQL permite evitar el mecanismo de autenticación y obtener acceso a la máquina. La vulnerabilidad es extremadamente sencilla de explotar por fuerza bruta, conociendo un usuario de la base de datos. Como root suele ser un usuario habitual, normalmente se conseguirá acceso como root, en caso de resultar vulnerable !!

Podéis encontrar detalles sobre la vulnerabilidad en Security vulnerability in MySQL/MariaDB sql/password.c Aquí podréis obtener las combinaciones MySQL y  memcmp() vulnerables.

Y un exploit, como ya hemos dicho, extremadamente sencillo, en SecManiac

Oferta de cursos online: Cryptography y Software Testing entre otros

Coursera y Udacity anuncian nuevos cursos y reposiciones para las próximas fechas. De todos ellos, destacaría Cryptography (Coursera) y CS258 Software Testing: How To Make Software Fail (Udacity). Aunque para el subtítulo me ha ido bastante bien hasta ahora sin hacer ningún curso  😉 Otro que podría ser interesante es CS 211 Logic & Discrete Maths: Foundations of Computing (Udacity).

No conozco el curso de Criptografía de Coursera, pero el de Udacity, aún siendo la primera edición y, por tanto, susceptible de mejoras, realmente vale la pena. Han hecho un gran esfuerzo para plantear problemas prácticos y pequeños retos (challenges) que hacen el curso muy ameno y, sobre todo, orientado a la práctica. Por poner algún ejemplo, realizar una prueba de concepto de la vulnerabilidad BEAST de SSL/TLS o explotar vulnerabilidades en algoritmos de hashing para conseguir colisiones. Recomendado 100 %.

A continuación, el listado de los cursos que comienzan en breve:

  • Intro to Physics
  • Intro to Statistics
  • Logic & Discrete Maths: Foundations of Computing
  • Software Testing: How To Make Software Fail
  • Algorithms: Crunching Social Networks
  • Cryprography

La oferta completa la tenéis en Udacity y Coursera.

Bueno, os dejo que estoy liado con el challenge del examen final !!

Comprobar la seguridad de infraestructuras basadas en SSL

A partir de un estudio sobre la seguridad de las implementaciones SSL en webs populares realizado recientemente por Trustworthy Internet han aparecido diversas noticias con titulares como Los sitios web con seguridad HTTPS son inseguros o incluso La mayoría de webs HTTPS, 90%, son vulnerables.

Que levante la mano el que conozca un sitio 100% seguro. No ?

Dentro de SSL existen diversos algoritmos, algunos de ellos se consideran actualmente como NO seguros. Por lo tanto, incluso los servicios ofrecidos sobre SSL pueden ser potencialmente inseguros.

Otro aspecto fundamental de la seguridad basada en SSL son los certificados. Los certificados instalados en los servidores deben estar firmados por CA (Autoridades de Certificación) de confianza ampliamente conocidas. Como usuarios, nuestros navegadores incluyen una serie de certificados que permiten comprobar que el certificado SSL del sitio al que estamos accediendo ha sido emitido por una de estas CA de confianza. Aunque sobre este tema recomiendo echar un vistazo a SSLCop de Security By Default.

Una forma sencilla de comprobar la seguridad de un sitio SSL es usar uno de los escáneres online mas conocidos, como el de Qualys. Si preferís que todo quede en casa, hay diversas soluciones muy sencillas de utilizar, por ejemplo, SSLScan.

Usando SSL Scan

 

Una vez compilado el código fuente descargado, para lo que se necesitará tener instalado libssl y libssl-dev  no queda mas que ejecutarlo. La opción –no-failed hace que se informe únicamente de los algoritmos soportados.

yo@localhost:~/sslscan-1.8.2$ ./sslscan --no-failed ssl.midominio.com

Testing SSL server ssl.midominio.com on port 443
Supported Server Cipher(s):
 Accepted SSLv3 128 bits DHE-RSA-AES128-SHA 
 Accepted SSLv3 128 bits AES128-SHA 
 Accepted SSLv3 168 bits EDH-RSA-DES-CBC3-SHA
 Accepted SSLv3 56 bits EDH-RSA-DES-CBC-SHA
 Accepted SSLv3 40 bits EXP-EDH-RSA-DES-CBC-SHA
 Accepted SSLv3 168 bits DES-CBC3-SHA
 Accepted SSLv3 56 bits DES-CBC-SHA
 Accepted SSLv3 40 bits EXP-DES-CBC-SHA
 Accepted SSLv3 128 bits RC4-SHA
 Accepted SSLv3 128 bits RC4-MD5
 Accepted SSLv3 40 bits EXP-RC4-MD5
 Accepted TLSv1 128 bits DHE-RSA-AES128-SHA
 Accepted TLSv1 128 bits AES128-SHA
 Accepted TLSv1 168 bits EDH-RSA-DES-CBC3-SHA
 Accepted TLSv1 56 bits EDH-RSA-DES-CBC-SHA
 Accepted TLSv1 40 bits EXP-EDH-RSA-DES-CBC-SHA
 Accepted TLSv1 168 bits DES-CBC3-SHA
 Accepted TLSv1 56 bits DES-CBC-SHA
 Accepted TLSv1 40 bits EXP-DES-CBC-SHA
 Accepted TLSv1 128 bits RC4-SHA
 Accepted TLSv1 128 bits RC4-MD5
 Accepted TLSv1 40 bits EXP-RC4-MD5
Prefered Server Cipher(s):
 SSLv3 128 bits DHE-RSA-AES128-SHA
 TLSv1 128 bits DHE-RSA-AES128-SHA

En este punto ya se puede verificar si la lista de algoritmos soportados son realmente los que deseamos. En cualquier caso, deberían desecharse todos los algoritmos SSL que no sean de, al menos, la versión 3. Los TLS tienen fama de ser mas seguros, pero el soporte no está tan extendido entre los clientes.

En el caso de que necesitaseis modificar los algoritmos de cifrado, podéis encontrar un artículo interesante sobre cómo configurar Tomcat para no permitir cifrados débiles. De nuevo, en Security By Default.

A continuación se mostrará información sobre el certificado SSL instalado.

SSL Certificate:
 Version: 2
 Serial Number: -4294967295
 Signature Algorithm: sha1WithRSAEncryption
 Issuer: /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)09/CN=VeriSign Class 3 Secure Server CA - G2
 Not valid before: Jun 14 00:00:00 2010 GMT
 Not valid after: Jun 14 23:59:59 2012 GMT
 Subject: /C=ES/ST=COMUNIDAD DE MADRID/L=Madrid/O=MIDOMINIO/OU=Financial Dept./OU=Terms of use at www.verisign.es/rpa (c)05/OU=Authenticated by VeriSign/OU=Member, VeriSign Trust Network/CN=ssl.midominio.com
 Public Key Algorithm: rsaEncryption
 RSA Public Key: (2048 bit)
 Modulus (2048 bit):
 00:9f:3c:52:2f:38:45:9c:bb:8f:a2:8f:36:be:02:
 4f:b5:69:ec:72:1d:42:fa:d0:6d:4d:ac:c7:e7:8a:
 1f:25:f8:e8:3a:28:7b:04:57:3c:00:f7:91:5b:d3:
 69:5a:a0:85:42:a6:04:68:e3:9e:0b:9d:fd:45:a1:
 cc:1b:eb:47:c6:6e:64:d6:8d:bf:ab:d4:e5:25:01:
 a2:4a:e8:fc:f2:59:72:8a:89:da:6c:2c:a6:33:6e:
 0f:fa:23:47:7c:a1:38:e2:5d:b7:54:42:ed:15:58:
 9c:f4:3c:ad:6b:1d:f1:d7:d6:1e:37:d0:83:7c:74:
 ae:85:63:a5:3f:77:63:c7:01:21:0d:82:eb:5c:36:
 c9:fd:cd:4a:35:44:9c:c1:c1:71:f1:a3:18:a5:18:
 0f:e8:66:78:cb:74:33:47:c8:35:45:53:82:41:ac:
 78:ec:8a:d1:ef:11:0a:e4:2f:c4:68:02:90:54:df:
 ad:16:61:4b:8d:e0:4f:ca:1d:16:00:43:37:c8:4d:
 a2:f0:a1:1f:77:ba:62:4d:21:ce:c3:25:48:5b:8c:
 f8:e2:15:c9:d1:65:9b:3a:11:27:17:1d:15:a4:4b:
 72:a6:fa:43:f4:c7:a3:50:57:7e:0c:41:9b:41:3f:
 b4:f5:ab:a3:b7:21:8d:05:32:46:f0:23:df:f8:39:
 f6:43
 Exponent: 65537 (0x10001)
 X509v3 Extensions:
 X509v3 Basic Constraints: 
 CA:FALSE
 X509v3 Key Usage: 
 Digital Signature, Key Encipherment
 X509v3 CRL Distribution Points: 
 URI:http://SVRSecure-G2-crl.verisign.com/SVRSecureG2.crl
X509v3 Certificate Policies: 
 Policy: 2.16.840.1.113733.1.7.23.3
 CPS: https://www.verisign.es/rpa
X509v3 Extended Key Usage: 
 TLS Web Server Authentication, TLS Web Client Authentication
 X509v3 Authority Key Identifier: 
 keyid:A5:EF:0B:11:CE:C0:41:03:A3:4A:65:90:48:B2:1C:E0:57:2D:7D:47
Authority Information Access: 
 OCSP - URI:http://ocsp.verisign.com
 CA Issuers - URI:http://SVRSecure-G2-aia.verisign.com/SVRSecureG2.cer
Categories: Seguridad Tags: , , , , ,

Conexiones seguras SSL y certificado cliente

04/25/2012 Leave a comment

Conexiones seguras SSL con certificado cliente

Primero vamos a ver los pasos necesarios para crear, firmar y configurar un certificado de servidor que se utilizará para cifrar la conexión. Y luego vamos a crear y configurar otro certificado que se utilizará en la parte cliente como certificado cliente para identificar el cliente que hace la petición.

En esta guía vamos a utilizar un Tomcat 7.0.23, java 7u3 y Apache Httpclient 4.1.1.

Antes de empezar vamos a matizar algunos conceptos que vamos a utilizar. Para realizar las conexiones SSL y autenticarnos con los certificados de cliente utilizaremos diferentes entradas del keystore. Cada una de estas entradas es un par clave privada clave pública, donde la clave pública se encuentra envuelta en un certificado X.509 v3 que por defecto está autofirmado (esto luego lo cambiaremos), esta cadena de certificados es guardada como un único elemento. El exportar una entrada lo que estamos exportando es el certificado que envuelve la clave pública. En la importación de certificados hay dos casos que se explican más abajo.

Nota: En algunos códigos de ejemplo se han tenido que meter saltos de línea para que el texto entrase. Es posible que estos saltos de línea tengan que ser eliminados para el correcto funcionamiento de los comandos.

Conexión SSL

Generar las claves, certificado SSL

Primero tenemos que crearnos un par de claves en nuestro keystore que se utilizarán como certificado SSL en el servidor

keytool -genkeypair -keystore server.keystore -alias certSSL -dname CN=localhost
  • -keystore: Es la ruta al archivo keystore que contiene la nueva entrada que se va a generar. Este será el archivo que leerá nuestro servidor para cifrar la conexión.
  • -alias: Es el alias con el que vamos a identificar a está entrada dentro del keystore.
  • -dname: Es el nombre del host del servidor que va a cifrar la conexión en formato CN=host_name. Cuando el cliente haga una petición a nuestro servidor resolverá en su /etc/hosts el host o ip que pongamos en la URL y utilizará ese nombre para validar el certificado del servidor. Si no coincide, no aceptará la petición.

Primero nos pedirá una contraseña para el keystore si el keystore no existe y luego nos pedirá confirmarla. Luego nos pedirá una contraseña para la clave que va a generar y que la volvamos a escribir, si no ponemos ninguna utilizará la del keystore.

El certificado que hemos generado arriba está autofirmado. Eso quiere decir que no ha sido firmada por otra clave (privada). Es muy útil que tengamos nuestro certificado firmado por otro que funcione como autoridad certificadora (CA), de esta forma si el cliente tiene esa CA en su baúl (truststore), todo certificado firmado por esa CA será de confianza y no tendremos que ir añadiendolos independientemente. Luego hablaremos de cómo añadir CA al baúl cliente.

Firmar certificado

Para firmar un certificado primero tenemos que hacer una petición de firmado de certificado (CSR) del certificado que queremos firmar.

keytool -certreq -keystore server.keystore -alias certSSL -file certSSL.csr
  • -keystore: Es la ruta al archivo keystore que contiene el certificado que queremos firmar.
  • -alias: Alias que identifica la entrada que contiene el certificado que vamos a firmar.
  • -file: Ruta al archivo que va a guardar la petición de firmado.

En este proceso te pedirá la contraseña del keystore y si la contraseña de la clave es distinta también te la pedirá. Ahora tenemos dos opciones le pedimos a una autoridad certificadora externa que nos firme nuestro CSR (VeriSign, Thawte,…) o lo firmamos nosotros con una clave privada nuestra.

Firma CA externa

Si elegimos a la autoridad externa, tendremos que enviarle el CSR a parte de otros datos que nos pida. La CA nos devolverá un nuevo certificado, que es el nuestro firmado por ella.

Firma con nuestro CA

Si no tenemos una clave privada para firmar, tendremos que generarla. Se hace igual que la que hemos generado antes

keytool -genkeypair -keystore ca.keystore -alias myca -dname CN=CA
  • -keystore: Es la ruta al archivo keystore que contiene la clave que se va a generar. Es recomendable que sea distinto al que hemos usado para SSL. Luego exportaremos el este certificado y se lo añadiremos al keystore de SSL con un alias nuevo (por la forma de añadirlo se guardará como una entrada de certificado de confianza).
  • -alias: Es el alias con el que vamos a identificar a está entrada dentro del keystore.
  • -dname: Puede ser lo que quieras.

Este comando te pedirá la contraseña del keystore y luego la contraseña de la clave que acaba de generar. Ahora firmamos el CSR con nuestra clave privada bajo el alias myca.

keytool -gencert -keystore ca.keystore -alias myca -ext san=dns:ca1
 -infile certSSL.csr -outfile signedCert.cert
  • -keystore: Es la ruta al archivo keystore que contiene la clave que antes hemos generado para firmar la CSR.
  • -alias: Alias de la clave que vamos a usar para firmar la CSR.
  • -ext: Extensiones X.509 que será embebidas en el certificado que vamos a firmar (fuera del ámbito del documento).
  • -infile: Ruta al archivo que tiene la solicitud de firma CSR.
  • -outfile: Ruta al archivo en el que se guardará el certificado (certSSL) firmado por nuestro CA.

En el proceso te pedirá la contraseña del keystore y si es distinta la contraseña de la clave myca.

Añadir certificado firmado

Da lo mismo el método de firmado elegido, al final tenemos un nuevo archivo (el que nos devuelva la CA externa o el que hemos generado nosotros signedCert.cert) que contiene la cadena de cerificados. Ahora hay que añadir ese certificado a nuestro keystore.

keytool -importcert -keystore server.keystore -file signedCert.cert -alias certSSL
  • -keystore: Es la ruta al archivo keystore donde queremos guardar la nueva clave firmada.
  • -file: Ruta al archivo que tiene la clave firmada.
  • -alias: Alias de la clave sin firmar que va a ser sobrescrito con la nueva clave firmada.

En el proceso te pedirá la contraseña del keystore y, si es distinta, la contraseña de la entrada con alias certSSL.

Si faltase algún certificado de los que componen la cadena de certificados que estamos añadiendo daría un error y no lo añadiría. Habría que añadir los certificados que faltasen (ver Añadir certificado CA).

Configurar servidor

Ya tenemos nuestro keystore con la cadena de certificados y clave privada que vamos a utilizar para la conexión SSL. Ahora hay que configurar el servidor para que utilice ese certificado y clave privada. En el server.xml definimos un nuevo conector de la siguiente manera

<Connector protocol="org.apache.coyote.http11.Http11Protocol" port="18081" 
minSpareThreads="5" enableLookups="true" disableUploadTimeout="true" 
acceptCount="100" maxThreads="200" scheme="https" secure="true" 
SSLEnabled="true" keystoreFile="server.keystore" keystorePass="cambiala" 
keyAlias="certSSL" keyPass="cambialaTambien" sslProtocol="TLS" />
  • port: Puerto donde va a escuchar nuestras peticiones.
  • keystoreFile: Es la ruta al archivo keystore donde tenemos guardada la cadena de certificados y clave privada que vamos a usar para la conexión SSL.
  • keystorePass: Contraseña del keystore.
  • keyAlias: El alias de la entrada del keystore que vamos a utilizar para cifrar la conexión.
  • keyPass: Contraseña de la entrada identificada con el alias especificado en keyAlias.

Configurar cliente

Si hemos firmado el certificado que el servidor utiliza para la conexión SSL con una CA externa, entonces no tenemos que hacer nada especial ya que el certificado de esa CA externa seguramente ya esté en baúl de confianza. Pero si hemos utilizado nuestro propio certificado, tenemos que añadir ese certificado al baúl de confianza que va a utilizar nuestro cliente. Hay distintas formas de hacer esto, voy a explicar la más localizada, sólo afectará a nuestro cliente. Primero exportamos el nuestro certificado de CA como se ha hecho arriba, pero en vez de importarlo en el keystore del sevidor lo añadimos al truststore del cliente

keytool -importcert -keystore client.truststore -file ca.cer -alias certSSL
  • -keystore: Ruta al archivo keystore donde vamos a guardar certificado que firma la clave utilizada para la conexión SSL.
  • -file: Ruta del archivo donde se esta el certificado.
  • -alias: Alias con el que se va a guardar la entrada del certificado de CA.

Ahora tenemos que decir a nuestro código que utilice este keystore como truststore. Primero obtenemos el truststore

FileInputStream instream = new FileInputStream(new File(TRUSTSTORE_PATH)
KeyStore truststore  = KeyStore.getInstance(KeyStore.getDefaultType());
truststore.load(instream, TRUST_STORE_PASS.toCharArray());

Luego creamos una factoría de sockets que utilice nuestro truststore y un esquema (scheme) para https que utilice esta factoría de sockets. Y registramos este esquema en el gestor de conexiones (connectionManager) de nuestro cliente.

SSLSocketFactory socketFactory = new SSLSocketFactory(truststore);
Scheme sch = new Scheme("https", 18081, socketFactory);
client.getConnectionManager().getSchemeRegistry().register(sch);

El código resultante sería algo así:

public class Client {

	private final static String URL = "https://localhost:18081/appSSL";
	private final static String TRUESTORE_PATH = "client.truststore";
	private static final String KEY_STORE_PASS = "cambialaTrust";

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		DefaultHttpClient client = new DefaultHttpClient();

		try (FileInputStream instream = new FileInputStream(new File(
                            TRUESTORE_PATH));){
			KeyStore truststore  = KeyStore.getInstance(
                            KeyStore.getDefaultType());

		        truststore.load(instream, KEY_STORE_PASS.toCharArray());

	        	SSLSocketFactory socketFactory = 
                            new SSLSocketFactory(truststore);
	        	Scheme sch = new Scheme("https", 18081, socketFactory);
		        client.getConnectionManager().getSchemeRegistry()
                            .register(sch);

			HttpGet httpget = new HttpGet(URL);
			HttpResponse response;

			response = client.execute(httpget);
			HttpEntity entity = response.getEntity();
			if (entity != null) {
			    BufferedReader reader = new BufferedReader(
                                new InputStreamReader(entity.getContent()));

			    String text;
			    while ((text = reader.readLine()) != null)
			    	System.out.println(text);
			}
		} catch (IOException | CertificateException | KeyStoreException 
                             | NoSuchAlgorithmException | KeyManagementException 
                             | UnrecoverableKeyException e) {
			e.printStackTrace();
		}

	}

}

Certificado cliente

Una de las formas que tenemos de identificar al cliente que está haciendo la petición es mediante un certificado de cliente. El cliente utiliza el par certificado-clave privada para identificarse contra el servidor.

Generar par certificado-clave privada

La entrada se genera igual que para el caso de la conexión SSL, pero en este caso el valor que pongamos en el parámetro -dname va a ser lo que luego nos identificará en el servidor.

keytool -genkeypair -keystore client.keystore -alias clientCert -dname CN=Nikola
  • -keystore: Es la ruta al archivo keystore que va contener el par certificado-clave que se va a generar.
  • -alias: Es el alias con el que vamos a identificar a está entrada dentro del keystore.
  • -dname: Es el nombre que luego nos identificará en el servidor.

Primero nos pedirá una contraseña para el keystore si el keystore no existe y luego nos pedirá confirmarla. Luego nos pedirá una contraseña para la entrada que va a generar y que la volvamos a escribir, si no ponemos ninguna utilizará la del keystore.

Configuración servidor

Esto se puede hacer de dos formas según quieras que sea el servidor o la aplicación quien autentique al cliente.

Autenticación por aplicación

En el server.xml tenemos que modificar nuestro conector SSL añadiéndole los siguientes atributos:

  • truststoreFile=“client.keystore”
  • truststorePass=“cambiala”
  • clientAuth=“false”

quedaría así:

<Connector protocol="org.apache.coyote.http11.Http11Protocol" port="18081" 
minSpareThreads="5" enableLookups="true" disableUploadTimeout="true" 
acceptCount="100" maxThreads="200" scheme="https" secure="true" 
SSLEnabled="true" keystoreFile="/var/lib/tomcat7/conf/pruebando" 
keystorePass="asdasd" keyAlias="ssl" keyPass="asdasd" 
truststoreFile="client.keystore" truststorePass="cambiala" clientAuth="false" 
sslProtocol="TLS" />
  • truststoreFile: Ruta al archivo keystore con los certificados de los clientes que vamos a autenticar.
  • truststorePass: Contraseña del keystore.

En el web.xml de la aplicación tendríamos que añadir las restricciones de seguridad a los recursos de la aplicación e indicar el tipo de autenticación que queremos realizar. Podría ser algo así:

<login-config>
    <auth-method>CLIENT-CERT</auth-method>
    <realm-name>Mi realm</realm-name>
</login-config>

<security-role>
    <description>Cliente con certificado</description>
    <role-name>pruebaClientCert</role-name>
</security-role>

<security-constraint>
    <display-name>Toda la aplicacion</display-name>
    <web-resource-collection>
        <web-resource-name>Todo</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>pruebaClientCert</role-name>
    </auth-constraint>
</security-constraint>

Estás líneas en el web.xml indican que el método de autenticación va a ser por certificado de cliente (<auth-method>CLIENT-CERT</auth-method>) que el rol necesario para poder acceder a cualquier recurso de la aplicación es “pruebaClientCert”.

Y luego en donde estén definidos los usuarios las contraseñas y los roles tenemos que meter la siguiente entrada:

  • Nombre de usuario: Es el nombre que definimos el crear la clave del cliente en el parámetro -dname. (En nuestro ejemplo “CN=Nikola”).
  • Contraseña: null
  • Roles: Los roles a los que van a pertenecer a este cliente. (En nuestro caso, como mínimo, pruebaClientCert).

Para el caso del realm UserDatabaseRealm habría que añadir algo así:

<role rolename="pruebaClientCert"/>
...
<user username="CN=Nikola" password="null" roles="pruebaClientCert" />

Autenticación por servidor

Configuración cliente

Vamos a utilizar la entrada que hemos generado antes para identificar al cliente que hace la petición, para esto vamos a tener que decir a nuestro código que utilice este keystore. Primero obtenemos el keystore

FileInputStream instreamKey = new FileInputStream(new File(KEYSTORE_PATH)
KeyStore keystore  = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(instreamKey, KEYSTORE_PASS.toCharArray());

Luego modificamos la factoría de sockets que hemos creado antes para que utilice nuestro keystore.

SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore, KEYSTORE_PASS, 
trustStore);

El código resultante sería algo así:

public class Atacando {

	private final static String URL = "https://localhost:18081/appSSL";
	private final static String TRUSTSTORE_PATH = "client.truststore";
	private static final String TRUSTSTORE_PASS = "cambialaTrust";

	private final static String KEYSTORE_PATH = "client.keystore";
	private static String KEYSTORE_PASS = "cambiala";

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		DefaultHttpClient client = new DefaultHttpClient();

		try (FileInputStream instream = new FileInputStream(new File(
                              TRUSTSTORE_PATH));
			   FileInputStream instreamKey = 
                              new FileInputStream(new File(KEYSTORE_PATH));){
			KeyStore trustStore  = KeyStore.getInstance(
                              KeyStore.getDefaultType());
			KeyStore keyStore  = KeyStore.getInstance(
                              KeyStore.getDefaultType());

	                trustStore.load(instream, TRUSTSTORE_PASS.toCharArray());
	                keyStore.load(instreamKey, KEY_STORE_PASS.toCharArray());

	                SSLSocketFactory socketFactory = new SSLSocketFactory(
                               keyStore, KEYSTORE_PASS, trustStore);
         	        Scheme sch = new Scheme("https", 18081, socketFactory);
        	        client.getConnectionManager().getSchemeRegistry()
                               .register(sch);

			HttpGet httpget = new HttpGet(URL);
			HttpResponse response;

			response = client.execute(httpget);
			HttpEntity entity = response.getEntity();
			if (entity != null) {
			    BufferedReader reader = new BufferedReader(
                                 new InputStreamReader(entity.getContent()));

			    String text;
			    while ((text = reader.readLine()) != null)
			    	System.out.println(text);
			}
		} catch (IOException | CertificateException | KeyStoreException 
                          | NoSuchAlgorithmException | KeyManagementException 
                          | UnrecoverableKeyException e) {
			e.printStackTrace();
		}
	}
}

La (inexistente) seguridad en los pagos NFC

No veo muy claro los beneficios de la tarjeta contactless puesto que, al final, tienes que llevar igualmente la tarjeta encima. Ni siquiera la de los pagos NFC con el móvil, puesto que necesitas tener una tarjeta de crédito igualmente (y la tarjeta ocupa menos que el smartphone en el bolsillo).

Para colmo, en el siguiente informe se muestra la nula seguridad de ese tipo de tarjetas, que no proporciona ningún tipo de autenticación ni cifrado, permitiendo el robo de ciertos datos de la tarjeta (PAN, fecha de caducidad) que son suficientes para realizar pagos en muchos comercios online. Ni que decir tiene que no cumpliría la normativa PCI DSS (Payment Card Industry Data Security Standard, significa Estándar de Seguridad de Datos para la Industria de Tarjeta de Pago).

Podéis leer el artículo completo en http://code.google.com/p/readnfccc/downloads/detail?name=hes2012-bt-contactless-payments-insecurity.pdf&can=2&q=

Categories: Seguridad, Tecnologías Tags: , , ,
%d bloggers like this: