Mejorar la seguridad en el envío de datos con Phonegap y jQuery

por

phonegapA la hora de realizar una APP con Phonegap es habitual que necesitemos realizar peticiones a una base de datos externa para obtener datos de usuario, noticias, articulos, etc. Esto puede llegar a ser un problema en cuanto a seguridad se refiere.

Los servicios que creamos para responder a las peticiones son servicios públicos que cualquiera podría atacar y utilizar sin control a menos que nosotros introduzcamos una serie de medidas.

Una cosa que debéis tener muy en cuenta es que cualquier usuario un poco "avispado" puede tener routeado su móvil y podría alterar el código fuente de nuestra aplicación, por lo tanto, debemos tener en cuenta que cualquier código que incluyamos en el paquete .apk de nuestra APP debemos considerarlo público, y por tanto, alterable.

Vamos a poner un ejemplo muy sencillo para que nos entendamos: supongamos que tenemos una aplicación en la que nos logueamos con nuestro usuario y nuestro password y, tras confirmarse el correcto logueo, nos desplazamos a una página desde la que podemos modificar nuestros datos de cuenta. Para hacer esto en una aplicación web normal bastaría con pasarle el id del usuario guardado en sesión pero en una APP no es suficiente ya que esas variables de sesión pueden ser adulteradas o seteadas directamente para que tengan ciertos valores concretos, lo que le daría total control a un usuario de todos los demás. Por lo tanto no nos vale con loguearnos y guardar en sesión el id de usuario solamente.

Vamos a dividir los servicios entre servicios públicos y privados ya que los niveles de seguridad serán muy diferentes. Antes de nada debemos mencionar que escribiremos nuestros servicios en PHP y que los datos sensibles se envían ya encriptados con md5(otra opción sería usar sha1 que es más seguro auqnue para nuestro ejemplo no importa).

Seguridad en servicios públicos

Como ya supondréis, la seguridad en un servicio público es menor que en un privado por definición. La única resticción que podemos añadir a la hora de realizar una petición a un servicio público es la de enviarle una "public key" generada por nosotros en la APP y comprobar que coincide con la definida en nuestro servicio.

Un ejemplo que puede ilustrar esto sería:

Login.html

		
	
	
	

	

Login

Funciones.js


	var public_key="123456789";
	
	function login(user, pass){
	
		$.getJSON('http://www.midominio.com/app/services/login.php?callback=?',{user:user, pass:pass, key:public_key},function(data){
			
			if(data.error > 0){
			
				switch (data.error){
				
					case '1':
						alert('Debes rellenar todos los campos');
						break;
					
					case '2': 
						alert('Usuario o password incorrecto');
						break;
					default: 
						alert('Error desconocido');
						break;
				
				}
			}
			
			else{
			
				/** GUARDAMOS DATOS EN localStorage **/		
				window.localStorage.setItem("id_user", data.id_user);
				window.localStorage.setItem("username", data.username);
				window.localStorage.setItem("private_key", data.private_key);	
			}
			
			
		});
	}
	

Login.php

   $id_user, "username" => $username, "private_key" => $private_key ,"error" => "0");
				$json = json_encode($data);				
				
			}
			else{

				$data = array("error" => "2");
				$json = json_encode($data);	
			}
		}			
		else{

				$data = array("error" => "1");
				$json = json_encode($data);	
		}
		
		echo $_GET['callback'] . '(' . $json . ')';		

?>

Esta medida evitará que aplicaciones no autorizadas ataquen directamente a nuestro servicio.

Sin embargo, esta es una medida que puede superarse si te routean la aplicación. En este caso, la única medida que nos quedaría sería la de denunciarlo a la Android por copia ilegal.

Seguridad en servicios privados

Este es el punto más importante que debemos tener en cuenta ya que si tuviesemos una brecha de seguridad en esta parte estaríamos exponiendo los datos privados de los usuarios y permitiríamos que otros usuarios pudiesen alterarlos.

En el caso de las medidas para los públicos, si fuesen superadas, el único inconveniente que tendríamos sería el de que otra aplicación estaría usando nuestro login, por ejemplo, pero nunca afectaría a nuestra base de datos.

¿Cómo podemo evitar que un usuario suplante a otro?

La solución es bastante simple. A la hora de realizar el login, desde nuestro servicios generamos una private key a partir de la public key, la fecha actual y una cadena aleatoria definida por nosotros. Esta private key es guardada en base de datos y a su vez devuelta a través del servicio y guardada en sesión. Es imposible que nadie pueda generar una private key sin tener el usuario y el password de un usuario y aún así, esa private key cambia cada vez que un usuario se vuelve a loguear.

Podéis ver el código que la genera en el ejemplo expuesto en el punto anterior, login.php

Esa private key será la base para el acceso de un usuario a sus opciones privadas. Cada private key es única y nadie sabe su contenido excepto el propio usuario que la tiene en sesión por lo que ningún usuario que no sepa el password de otro puede averiguarla.

A la hora de realizar una petición de una página privada de usuario debemos comprobar en el servidor que la private key que enviamos coincide con la almacenada en la base de datos del usuario.

Pongamos un ejemplo: supongamos que estamos logueados y queremos hacer una oferta por un producto que vende otra persona(estilo eBay).

sendOffer.php

  
	$id_user = $_GET['id_user'];
	$id_comprador = $_GET['id_comprador'];
	$id_item = $_GET['id_item'];
	$private_key = $_GET['key'];
	$offer = $_GET['offer'];
	$fecha=date("Y-m-d H:i:s");
	
	$dbhost='midominio.mihosting.com';
	$dbusername='miname';
	$dbuserpass='mipass';
	$dbname='midatabase';


	// Conectar a la base de datos
	mysql_connect($dbhost, $dbusername,$dbuserpass) or die(mysql_error());
	mysql_select_db($dbname) or die(mysql_error());

	/** COMPROBAMOS QUE LA KEY ENVIADA COINCIDA CON LA ALMACENADA EN BD **/
	
	
	if(checkPrivateKey($id_comprador,$private_key)){
		
		$query = mysql_query("INSERT INTO ofertas (id_item,id_usuario,id_comprador,oferta,fecha) VALUES('$id_item','$id_user','$id_comprador','$offer','$fecha')")or die(mysql_error());
		$data = array("error" => "0");
		$json = json_encode($data);		
	}
	
	else{
	
		$data = array("error" => "1");
		$json = json_encode($data);

	}
		
	echo $_GET['callback'] . '(' . $json . ')';	




	function checkPrivateKey($id_user, $key){
	
		$query = mysql_query("SELECT private_key FROM usuarios WHERE id='$id_user'" ) or die(mysql_error());
		$data = mysql_fetch_array($query);
		
		if($data['private_key'] == $key && $key!='' && $key!= null){
			return true;
		}
		else{
			return false;
		}
		
	}	


El ejemplo es muy sencillo. Recibimos los datos necesarios: id del usuario que vende, id del usuario que compra, la private key del usuario que quiere comprar y el id del item a comprar. Llamamos a la función checkPrivateKey() que devolverá true o false en función de si la private key que enviamos coincide con la almacenada en la base de datos del usuario comprador.

En caso de que no coincidan se devuelve un error, en caso contrario se sigue el procedimiento habitual de acceso y modificación de la base de datos.

Daros cuenta de que si no implementásemos este nivel de seguridad cualquier persona podría enviar ofertas en nombre de cualquier usuario.

COMENTARIOS

07-10-2014 23:01:28
Hola de nuevo Sergio! Gracias por responder, pero desafortunadamente no he llegado a esa parte porque probando desde un navegador en el PC y desde el navegador de Eclipse funciona!, pero después de darle buil al proyecto y emularlo en Android, no hace nada cuando llega al llamado AJAX, sólo llega hasta unos alert que tengo antes del AJAX, pero no hace más, tampoco me muestra error alguno o bueno, no se como verlos... es mucha molestía que te envíe el www que estoy trabajando para ver si puedes colaborarme en que estoy fallando (si puedes dejame un correo para contactarte y enviartelo)??? Gracias de antemano
02-10-2014 21:04:50
Hola @Pedro, para recuperarlo deberías usar la función getItem en vez de set item. Por ejemplo: window.localStorage.setItem("id_user") Un saludo!
01-10-2014 21:56:21
Hola, muchas gracias por el pos, me ha ayudado mucho, solo me queda una duda, tu colocas el código por medio del cual guardamos en LocalStorage la clave y el id_user, pero no el como recuperarlos de allí después de guardados... De antemano gracias !!!

DEJA TU COMENTARIO