Seguridad a la hora de crear entradas en WordPress

INDICE
0.-Resumen
1.-Introducción
2.-Buscando un algoritmo
3.-Implementando nuestro sistema en WordPress
3.1.-Añadiendo posts a nuestra base de datos
3.1.1.-Analizando la tabla de wp_posts
3.1.2.-Nuestra consulta
3.1.3.-La tabla wp_postmeta
3.2.-Implementando la seguridad en nuestra aplicación
3.3.-Conclusión y funcionamiento final
4.-Códigos finales completos

0.-Resumen

En este post intentaré explicar un método casero para poder escribir en nuestro respectivo blog de WordPress desde cualquier lugar sin tener que preocuparnos en que nos estén analizando el tráfico. Ideal por si estamos en una red que no es la nuestra, como en casa de algún conocido o en un hotel.

1.-Introducción

Una de las responsabilidades que tenemos/tienen los bloggers es de mantener actualizado nuestro blog que tanto queremos, incluso si nos encontramos en otros lugares. Sin embargo, no todos disponemos de 3G y algunos tenemos que ir mendigando WiFi por los hoteles, o en casa de un pariente. Yo, con lo paranoico que soy que incluso uso la versión encriptada de Google, siempre trato de intentar comprometer lo menos posible mis datos.

Si no tenemos puesto SSL en nuestro blog de WordPress, y nos logueamos en una red que puedan tener monitorizada, fácilmente nos sacan nuestro user y pass, por lo que tendremos que buscar una alternativa para loguearnos que sea más segura que nuestra propia cuenta.

2.-Buscando un algoritmo

Una idea que se me ha ocurrido para esto, es identificarnos con un hash que varíe, de forma que si es obtenido por algún tercero, no le sirva para nada. Lo más fácil de recordar que varíe, es el tiempo, así que podriamos usar un algoritmo basado en:

  • Caracteres alfanuméricos.
  • Caracteres especiales.
  • Caracteres que varien automáticamente (en función del tiempo).
  • Caracteres que varien de forma controlada.
  • Mayúsculas y minúsculas.

Un ejemplo podria ser en base al siguiente:
///XXlipmanYY///
En donde XX es el dia del mes, y YY la hora. He elegido este par de números debido a que varian lo suficientemente rápido como para que no se reutilicen, y lo suficientemente lento como para controlarlos. Además de esto, usaremos un truco para añadir un caracter aleatorio fácil de recordar. Dependiendo del dia de la semana (lunes, martes…) teclearemos una tecla debajo de los números, dependiendo del dia que sea. Por ejemplo, cuando sea lunes, pulsamos la Q, cuando sea martes la W… (notese que seria en mayúsculas).

Ejemplo completo:
Lunes 20 a las 8AM: ///20Qlipman8///
Martes 21 a las 15PM: ///21lWipman15///
Viernes 24 a las 2AM: ///24lipmTan2///

Notese que las letras mayúsculas “aleatorias” las vamos alternando en las posiciones entre la palabra “lipman”. Para que quede bien, necesitamos palabras de 6 caracteres.
De aquí también podemos descatar lo imprescindible que es que una contraseña no empiece por números o por letras cercanas a los extremos del alfabeto, para que sea todavia más improbable que un brute force nos descubra la contraseña.

3.-Implementando nuestro sistema en WordPress

Ya tenemos el diseño (respecto a la seguridad) de nuestra aplicación, por lo que queda la parte más entretenida: realizar una aplicación para comunicarnos con WordPress implementando nuestro sistema. Voy a poner solo lo que es esencial: comunicación entre nuestra aplicación y la base de datos para añadir entradas. Por nuestra cuenta podriamos desarrollar más cosas, como una mejor interfaz gráfica, pero esto lo dejaré a parte ya que seguramente dejaré esto como un plugin (ya hablaré más adelante) y haré mejoras en todo.

3.1.-Añadiendo posts a nuestra base de datos

Para nuestra suerte, la base de datos de WordPress (y en general la de cualquier CMS) suele ser sencillita, así que no deberiamos de tener problemas para manejarla.
Lo primero de todo, aprovecharemos el fichero de wp-config.php para conectar a nuestra base de datos. Este fichero es el primero que se genera cuando creamos nuestro sitio en WordPress, y contiene los datos de nuestra base de datos: nombre, usuario, contraseña… Así que le haremos un include.

3.1.1.-Analizando la tabla de wp_posts

Lo interesante de este tipo de situaciones, es analizarlas y aprovecharlas para intentar entender el funcionamiento interior de WordPress, por ello, no me limitaré a hacer, copiar y pegar código simplemente, sino que se intentará explicará todo.

A continuación, vamos a escribir los campos que tendremos que tocar (osease, todos menos los que suelen ir a NULL), explicandolos un poco, sobre todo, los que sean menos obvios.

Nombre Comentario
ID Id (PK) del post
post_author ID del autor
post_date Fecha de escritura
post_date_gmt Fecha de escritura (GMT)
post_content Contenido del post
post_title Titulo
post_status Estado: publicado, borrador..
comment_status Permitir comentarios
ping_status Permitir pingback
post_name Nombre parseado (*)
post_modified Fecha de modificación
post_modified_gmt Fecha de modificación (GMT)
post_parent A quien pertenece (*)
guid URL parseada (*)
menu_order Orden: 0
post_type Indica si es un post
comment_count Contador de comentarios

-Nombre parseado: Se obtiene en función del titulo. Si tenemos el siguiente titulo: “Hola Mundo”, obtendremos: “hola-mundo”. Osease, lo que aparece (si lo tenemos configurado así) de enlace.

-A quien pertenece: Esto solo hace referencia a los borradores y demás. Con respecto a los posts publicados, ni caso.

-URL parseada: Esta seria la URL del post en el caso de tenerlo configurado de manera que se viera la ID en la URL. Un ejemplo, en el caso de delanover, si escribiesemos un post con ID 1234, este campo seria: http://delanover.com/?p=1234

3.1.2.-Nuestra consulta

De momento, llevamos lo siguiente:

//Incluimos los datos de configuración de nuestra base de datos
include('wp-config.php');

mysql_connect(DB_HOST, DB_USER, DB_PASSWORD) or die("Error al conectar");

//Creamos la consulta
$consulta_principal = "INSERT INTO `wp_posts`(`ID`, `post_author`, `post_date`, `post_date_gmt`,
	`post_content`, `post_title`, `post_status`, `comment_status`, `ping_status`,
	`post_name`,`post_modified`, `post_modified_gmt`,
	`post_parent`, `guid`, `menu_order`, `post_type`, `comment_count`) VALUES
	('$id', 1,$fecha,$fecha_gmt,'$contenido','$titulo','publish','open','open',
	'$titulo_parseado',$fecha,$fecha_gmt,0,'$id_parseada',0,'post',0)";
		
$resultado_principal = mysql_query($consulta_principal);

Como podemos observar en nuestra consulta, hay variables y valores constantes. Me limitaré a contar un poco las variables al igual que la forma de obtenerlas, ya que los valores constantes son un tanto redundantes.

-ID: Es la ID que tendrá nuestro post. Esta ID se consigue obteniendo el valor máximo de todas las IDs que ya hay, incrementando su valor en uno, de la siguiente manera:

SELECT MAX(ID) from `wp_posts`

-Fecha: Es la fecha de creación de nuestra entrada. Tiene un formato específico y estricto que deberemos seguir para que nos funcione correctamente. El formato es el siguiente: AAAA-MM-DD HH:MM:SS
Obtendremos este formato de la siguiente manera:

$fecha = "DATE '" . date("Y-n-j H:i:s",time()) . "'";

Nota: notese que hay que concatenar “DATE ‘” a nuestra fecha, para que a la hora de introducirlo a la BD lo reconozca como tal.

-FechaGMT: Lo mismo que el anterior, incrementando +2 horas. Todo esto de las fechas, depende totalmente de donde nos encontramos.

$fecha_gmt = "DATE '" . date("Y-n-j ",time()) . (date("H",time())+2) . date(":i:s",time()) . "'";

-Contenido y Titulo: Este par de campos, sencillamente son lo que escribimos de titulo y contenido, por lo que no hay que tratar demasiado.

-ID parseada: Como comenté anteriormente, no es muy dificil de conseguir, simplemente seria concatenando una constante a la ID:

$id_parseada = "http://delanover.com/test_wordpss/?p=" . $id;

-Titulo parseado: Esta la he dejado para el final ya que es la más largilla de hacer, para la cual he creado una función específica. En principio, al ser el enlace, lo unico que puede contener son letras (quitaré las tildes), numeros, guiones bajos y guiones. Por lo que haré lo siguiente:

$titulo_parseado = parsear_titulo($titulo);

function parsear_titulo($titulo){
  //pasamos a minusculas y cambiamos caracteres
  $titulo = strtolower($titulo);
  $titulo = str_replace(" ", "-", $titulo);
  $titulo = str_replace("á", "a", $titulo);
  $titulo = str_replace("é", "e", $titulo);
  $titulo = str_replace("í", "i", $titulo);
  $titulo = str_replace("ó", "o", $titulo);
  $titulo = str_replace("ú", "u", $titulo);
  
  $caracteres_permitidos = array("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "ñ", "o", "p",
  			"q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "1", "2", "3", "4", "5", "6", "7", "8", "9",
  			"0", "_", "-");
  			
  //Si nos encontramos con un caracter no permitido (que no salga en el array anterior), lo eliminamos
  for($a=0;$a

3.1.3.-La tabla wp_postmeta

Afortunadamente para nosotros, el tratamiento de esta tabla será todavia más sencillo, y limitaré mi explicación por no tener demasiado que ver con el tema principal.

Explicaré brevemente los detalles dentro del código fuente:


//Obtenemos el ID maximo
$consulta_metapost = "SELECT MAX(meta_id) from `wp_postmeta`";
$respuesta_metapost= mysql_query($consulta_metapost);
if($resultado=mysql_fetch_array($respuesta_metapost))
	$_id = $resultado[0]+1;

//Introducimos el ID recogido, el ID anterior que teniamos del post, y dos valores constantes
$consulta2 = "INSERT INTO `wp_postmeta`(`meta_id`, `post_id`, `meta_key`,
`meta_value`) VALUES ($_id,$id,'_edit_last',1)";
$resultado_consulta2 = mysql_query($consulta2);
	
$_id++; //Aumentamos el ID ya que vamos a insertar otra fila
//Obtenemos un valor que meteremos, compuesto por el tiempo y el ID del autor
$meta_valor = time() . ":1";
$consulta3 = "INSERT INTO `wp_postmeta`(`meta_id`, `post_id`, `meta_key`,
`meta_value`) VALUES ($_id,$id,'_edit_lock','$meta_valor')";
$resultado_consulta3 = mysql_query($consulta3);

3.2.-Implementando la seguridad en nuestra aplicación

Muy bien, con todo esto ya deberia de funcionarnos el sistema, pero claro, falta lo más importante, implementar la seguridad con ese hash dinámico que generamos.

Lo más sencillo es crear una función que nos devuelva un valor booleano, en función de si el hash introducido es verdadero (entonces se ejecuta) o falso (devolviendo un error).

function comprobar_hash($hash){
$hora = date("H", time())+6; //Obtenemos la hora (la adecuamos en función de la hora del servidor a la hora de nuestro pais)
$dia = date("j", time()); //Obtenemos el dia

//Nuestro salt
$cadena = "lipman";

//En función del dia, generamos un resultado u otro
switch(date("N", time())){
  case 1:
  $nueva_cadena = "Q" . $cadena[0] . $cadena[1] . $cadena[2] . $cadena[3] . $cadena[4] . $cadena[5];
  break;
  case 2: $nueva_cadena = $cadena[0] . "W" . $cadena[1] . $cadena[2] . $cadena[3] . $cadena[4] . $cadena[5]; break;
  case 3: $nueva_cadena = $cadena[0] . $cadena[1] . "E" . $cadena[2] . $cadena[3] . $cadena[4] . $cadena[5]; break;
  case 4: $nueva_cadena = $cadena[0] . $cadena[1] . $cadena[2] . "R" . $cadena[3] . $cadena[4] . $cadena[5]; break;
  case 5: $nueva_cadena = $cadena[0] . $cadena[1] . $cadena[2] . $cadena[3] . "T" . $cadena[4] . $cadena[5]; break;
  case 6: $nueva_cadena = $cadena[0] . $cadena[1] . $cadena[2] . $cadena[3] . $cadena[4] . "Y" . $cadena[5]; break;
  case 7: $nueva_cadena = $cadena[0] . $cadena[1] . $cadena[2] . $cadena[3] . $cadena[4] . $cadena[5] . "U"; break;
  default:
  	$nueva_cadena = "error";
  	
}

//Obtenemos la cadena formada y la pasamos a MD5
$resultado = "///" . $dia . $nueva_cadena . $hora . "///";
$resultado = md5($resultado);

//Finalmente comprobamos si coincide con el hash pasado por parámetro
if($resultado==$hash)
	$final = "Verdadero";
else
	$final = "Falso";

return $final;
}

3.3.-Conclusión y funcionamiento final

Nos falta diseñar la página de "login" en la que tendriamos que recoger el titulo de la entrada, el contenido, y el hash en MD5. Este hash en MD5 recordemos que es nuestra contraseña aleatoria pasada a MD5. Un apunte respecto a esto: imaginemos que nos están esnifando la red y tal. Vale, usamos este sistema y todos felices, pero si a la hora de crear nuestro hash MD5 (para que posteriormente lo valide) usamos una herramienta externa en una página web, nos esnifan esto, y no tiene mucho sentido.

Para ello, recomiendo que tengais un programa o algo, ya que desde la consola de Windows, a diferencia de la Linux, no se puede obtener el MD5 de una cadena. Si no, si teneis un servidor web instalado, lo haceis desde ahí usando la función de PHP "md5".

4.-Códigos finales completos

crear.html




Metemos el hash

Titulo

Contenido

create.php

error
"; function parsear_titulo($titulo){ //Pasamos a minusculas y quitamos las tildes $titulo = strtolower($titulo); $titulo = str_replace(" ", "-", $titulo); $titulo = str_replace("á", "a", $titulo); $titulo = str_replace("é", "e", $titulo); $titulo = str_replace("í", "i", $titulo); $titulo = str_replace("ó", "o", $titulo); $titulo = str_replace("ú", "u", $titulo); $caracteres_permitidos = array("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "ñ", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "_", "-"); //Si encontramos un caracter que no está en el array de permitidos, lo eliminamos for($a=0;$a

Usando la API de Virustotal

Índice:
1.-Analizando un archivo
2.-Realizando comentarios
3.-Analizando una URL

Introducción

Quien no conoce Virustotal, el servicio online en el que podemos subir un archivo y nos lo escanea con hasta 42 antivirus para tener varios resultados.

Hace ya tiempo, los que se dedicaban al desarrollo de Malware usaban el KIMS (que recuerdos me trae), que era un software que se descargaba varios antivirus (creo que hasta 20) y los analizaba localmente en tu propio ordenador. Ahora con el avance de los tiempos, tenemos servicios online como este del que voy a hablar hoy, o de NoVirusThanks, el cual tuvo su auge en cuanto los desarrolladores supieron que la plataforma Virustotal distribuia lo que subian (o eso se dijo), por lo que si subian virus que estaban desarrollando, no les traia cuenta que estos ficheros fueran dados a las marcas de Antivirus para que los analizasen y detectasen posteriormente.

Una de las razones que me ha llevado a interesarme por esta API, es su increible simpleza y flexibilidad, como podremos ver a continuación en un script. Antes de nada, decir que este script, y todo lo que pondré a continuación está sacado de aquí. La razón por la cual quiera hacer un post de esto, es para dar a conocer más esta API y que todos vean lo simple que es.

Antes de dar paso al uso de la API, quisiera agradecer a Julio Canto y Emiliano Martínez (del staff de VirusTotal) por su total ayuda (incluso en un domingo =P) a través de twitter y por correo electrónico. Sin ellos no podría haber terminado este post, ya que tuve dudas importantes con el uso de la API analizando webs.

A continuación, vamos a hacer lo más simple, y es analizar un fichero y obtener el reporte de los antivirus. Para ello lo primero que tenemos que hacer, es registrarnos en Virustotal.com, y obtener la API Key, que se halla en My Account -> Inbox -> Public API.

1.-Analizando un archivo

Ahora os dejo con el código, comentado y explicado, de cómo analizar un fichero virus.exe y obtener el resultado:

0: fecha (-2 horas española)
--->1: Array
------>Nombre del antivirus: Resultado
------| Si no detecta nada, el resultado está vacio
------| Hay 42 antivirus
*/

echo "
"; if($retrieve){ foreach($retrieve['report'][1] as $clave => $valor) echo $clave . ": " . $valor . "
"; //Esto nos devuelve todos los antivirus, seguido del resultado que nos dan echo "
"; } function virustotal_scanfile($filepath, $key){ //Editada y comentada por lipman // Author: Peter Bailey // Website: Bailey-Projects.com $post = array('key' => $key, 'file' => '@'.$filepath); //preparamos lo que vamos a pasar por POST $ch = curl_init(); //Iniciamos CURL curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); curl_setopt($ch, CURLOPT_URL, 'http://www.virustotal.com/api/scan_file.json'); curl_setopt($ch, CURLOPT_POSTFIELDS, $post); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($ch); curl_close($ch); //Cerramos CURL } function virustotal_getreport($resource, $key){ $url = 'https://www.virustotal.com/api/get_file_report.json'; $fields = array('resource'=>$resource, 'key'=>$key); foreach($fields as $key=>$value) $fields_string .= $key.'='.$value.'&'; $fields_string = rtrim($fields_string,'&'); //Preparamos la variable que enviaremos por el método POST $ch = curl_init(); //Iniciamos CURL curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($ch,CURLOPT_POST,count($fields)); curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($ch); curl_close($ch); //Cerramos CURL $result = json_decode($result, true); //Decodeamos usando la función de json el resultado if(isset($result['report'][0])) return $result; //Si todo sale bien, lo devolvemos a la función else return false; } ?>

Resultado (seguro que podeis adiviniar que virus testeé):

2.-Realizando comentarios

En los reportes que se generan online tras el analizado de un fichero, se pueden realizar comentarios, incluso puntuar si estos son buenos o malos. Para realizar comentarios también disponemos de una función que he explicado en los comentarios del script:

$key,'comment'=>$comment);
	 if(preg_match('/^https?:\/\/.*/',$fileorurl)){
	    $fields['url']=$fileorurl;
	 } else {
	    $fields['file']=$fileorurl;
	 }
	 //Esto detecta si lo que pasamos es la URL o el hash del archivo, pero como
	 //dije anteriormente, a mi pasando la URL no me funciona, asi que podriamos
	 //deshacer este if-else
	
    $fields_string='';
	foreach($fields as $key=>$value)
	   $fields_string .= $key.'='.$value.'&';
	
	$fields_string=rtrim($fields_string,'&');
	//Creamos lo que vamos a pasar por POST-JSON
	
        $ch = curl_init();
	//Iniciamos CURL
	curl_setopt($ch,CURLOPT_URL,$url);
	curl_setopt($ch,CURLOPT_POST,count($fields));
	curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

	$result = curl_exec($ch);

	curl_close($ch);
	//Cerramos CURL
	$result = json_decode($result, true);
	//Decodeamos el resultado para saber si ha sido exitoso o no el proceso

	 if($result['result']=="1"){
	 	return true;
	 } else {
	 	return false;
	 }
}

Analizando una URL

Virustotal también nos permite analizar una URL. A priori, al analizar una URL, los motores de análisis tales como Google Safebrowsing y el de Firefox, nos indican la maliciosidad de la web (devuelto en un array, al igual que los resultados de cuando analizábamos un archivo). Pero además de esto, si la URL que indicábamos corresponde a un archivo, también nos devuelve un identificador que posteriormente podemos usar para ver si ese archivo es detectado o no.

Aquí el código, explicado:

$key = 'API_KEY';
//Api que se obtiene en el perfil
$file = "http://i476.photobucket.com/albums/rr125/lipmandj/asd.jpg";
//URL a analizar

$identificador = virustotal_scanurl($file,$key);

$retrieve = virustotal_geturlreport($key,$identificador);


if($retrieve){
	print_r($retrieve); // Array que contiene el reporte
}

function virustotal_scanurl($url, $key){

	// Esta función envia la URL

	
    // Author: Kenny Lyons aka ih8censorship
	// Website: http://pasture.sourceforge.net
    // Editado por lipman
	
    //URL a la que enviaremos los datos
    $url = 'http://www.virustotal.com/api/scan_url.json';
    //Preparamos los campos de lo que vamos a enviar
	$fields = array('url'=>$url, 'key'=>$key);
    $fields_string='';
	foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; }
	$fields_string=rtrim($fields_string,'&');

    //Procedemos a enviarlo mediante cURL
    $ch = curl_init();

	curl_setopt($ch,CURLOPT_URL,$url);
	curl_setopt($process, CURLOPT_HTTPHEADER, array("Content-Type: text/xml","SOAPAction: \"/soap/action/query\"", "Content-length: ".strlen($fields_string))); 
	curl_setopt($ch,CURLOPT_POST,count($fields));
	curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	
	$result = curl_exec($ch);

	curl_close($ch);
	//Tras enviarlo, cerramos la conexión y decodeamos el resultado devuelto
	$result = json_decode($result, true);
	
	
	if($result['result']=="1"){ //Si todo va bien, devolvemos la ID del scan
		return $result['scan_id'];
	} else {
		return false;
	}
	
}

function virustotal_geturlreport($key,$resource,$scan=false){

	// Nos devuelve el reporte
	
    // Author: Kenny Lyons aka ih8censorship
	// Website: http://pasture.sourceforge.net
    //Editado por lipman	

    //URL a donde enviaremos los datos
    $url = 'http://www.virustotal.com/api/get_url_report.json';
    //Preparamos los campos
	$fields = array('resource'=>$resource, 'key'=>$key,'scan'=>$scan);
    $fields_string='';
	foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; }
	$fields_string=rtrim($fields_string,'&');
    //Finalmente, usamos cURL para enviarlo y recoger el resultado
    $ch = curl_init();

	curl_setopt($ch,CURLOPT_URL,$url);
	curl_setopt($ch,CURLOPT_POST,count($fields));
	curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	
	$result = curl_exec($ch);

	curl_close($ch);
	
	$result = json_decode($result, true);
	
	 if($scan && $result['result']=="0"){
	 	return $result['scan_id'];
	 } else {
	 	return $result;
	 }
	
}

Saludos, lipman

[Script] Enviar e-mails con archivos adjuntos en PHP

Hace mucho tiempo me volví loco para encontrar un script, función o cualquier tipo de ayuda para enviar un email con archivos adjuntos en PHP, ya que todo lo que encontraba no me funcionaba. Supongo que al igual que a mi, hay más gente en las mismas, por lo que me dispongo a copiar una función que encontré aquí. Está hecha de forma totalmente sencilla, en la que para ejecutarla, simplemente tengamos que escribir:

//Ejemplo de usar la función para enviar un fichero llamado fichero.exe que se encuentra en la misma ruta que el script
mail_attachment("fichero.exe","","destinatario@hotmail.com","remitente@delanover.com","Administrador","no-reply@delanover.com","Asunto","Mensaje de prueba");

En caso de que se ejecute correctamente veremos el mensaje de mail send … OK. En caso contrario, veremos el otro mensaje: mail send … ERROR!.

Recordemos que para usar este script, nuestro servidor web ha de ser capaz de enviar correos electrónicos. De no ser así, obviamente no funcionará, pero para los que esteis seguros que funciona, este script debería de funcionar.

Aquí la función:

function mail_attachment($filename, $path, $mailto, $from_mail, $from_name, $replyto, $subject, $message) {
    $file = $path.$filename;
    $file_size = filesize($file);
    $handle = fopen($file, "r");
    $content = fread($handle, $file_size);
    fclose($handle);
    $content = chunk_split(base64_encode($content));
    $uid = md5(uniqid(time()));
    $name = basename($file);
    $header = "From: ".$from_name." <".$from_mail.">\r\n";
    $header .= "Reply-To: ".$replyto."\r\n";
    $header .= "MIME-Version: 1.0\r\n";
    $header .= "Content-Type: multipart/mixed; boundary=\"".$uid."\"\r\n\r\n";
    $header .= "This is a multi-part message in MIME format.\r\n";
    $header .= "--".$uid."\r\n";
    $header .= "Content-type:text/plain; charset=iso-8859-1\r\n";
    $header .= "Content-Transfer-Encoding: 7bit\r\n\r\n";
    $header .= $message."\r\n\r\n";
    $header .= "--".$uid."\r\n";
    $header .= "Content-Type: application/octet-stream; name=\"".$filename."\"\r\n"; // use different content types here
    $header .= "Content-Transfer-Encoding: base64\r\n";
    $header .= "Content-Disposition: attachment; filename=\"".$filename."\"\r\n\r\n";
    $header .= $content."\r\n\r\n";
    $header .= "--".$uid."--";
    if (mail($mailto, $subject, "", $header)) {
        echo "mail send ... OK"; // or use booleans here
    } else {
        echo "mail send ... ERROR!";
    }
}

Un saludo, lipman

[Script] Tratar cookies esnifadas con Wireshark en PHP

Un sitio web puede tener más de una cookie en el ordenador del usuario que navega, pero las cookies críticas son las que nos permiten la identificación del usuario en la página web. De esta manera, mediante técnicas como los conocidos XSS o los MITM seriamos capaces de obtener cookies críticas que nos permitan falsificar y hacer creer al sistema que nosotros somos el usuario objetivo.

El problema viene cuando el objetivo de un esnifado de red es únicamente saber las cookies críticas del que navega. Con todos los paquetes de datos que recibimos, incluso filtrándolos por HTTP en Wireshark, tendriamos mucho trabajo por delante.

De este problema me surgió la necesidad de crear un script en PHP, el cual quiero compartir en el blog, que nos permite, no solo ordenar y obtener las cookies de manera agradable para la vista, sino que, incluyo una función en la que a partir de un array cuyo índice sea el domino y el valor sea el nombre de la cookie crítica de ese sitio, nos lo colorea de rojo, permitiéndonos así localizarlas más fácilmente. De la misma manera, nos quita las cookies de los hosts repetidos.

El código está completamente comentado y dividio en dos funciones que nos hacen el entendimiento más fácil. El archivo que tenemos que pasarle al script, ha de ser el fichero pcap guardado del wireshark una vez esté filtrado usando el siguiente filtro:
http contains "Cookie: "
Esto nos permite obtener los paquetes que contenga “Cookie:” que son los que nos interesan.

Este es un ejemplo del resultado. Sabemos que la cookie crítica de twitter es auth_token:

Script:





Documento sin título





0)
    $final[] = $valor;
}

//La matriz "final" contiene por cada valor, un paquete con host y cookie
//"resultado" es una matriz vacia en la que se almacenará el resultado devuelto
parsear_matriz($final,$resultado);


//Printeamos el resultado
foreach($resultado as $clave => $valor){

  echo $clave;
  unset($final);
		$nuevo = explode(";",$valor);
		foreach($nuevo as $valor2)
			$final .= "" . $valor2 . "
"; echo $final . "
"; } //FUNCIONES function parsear_matriz($matriz,&$resultado) { foreach($matriz as $valor) { $host = explode("Host: ",$valor); $host = explode("\r\nUser",$host[1]); //Host[0] tiene cada host $cookie = explode("Cookie: ",$valor); //Cookie[1] tiene el valor de la cookie en cada host detectar_cookies_criticas($host[0],$cookie[1]); $host[0] = "" . $host[0] . ":
"; $resultado[$host[0]] = $cookie[1]; //Devolvemos el resultado } } function detectar_cookies_criticas($host,&$cookie){ $buscar = array("twitter.com" => "auth_token"); //Esta matriz contiene el host y la cookie crítica que destacará en el caso de que la encuentre if(array_key_exists($host,$buscar)){ $nuevo = explode($buscar[$host],$cookie); $cookie = $nuevo[0] . "" . $buscar[$host]; $nuevo = explode(";",$nuevo[1]); $cookie .= $nuevo[0] . ";" . $nuevo[1]; } } ?>

Usando la Geolocalización MAC en nuestro favor. Parte II

Anteriores entradas relacionadas:
[Análisis] Prey, software antirrobo de Código Abierto
Usando la Geolocalización MAC en nuestro favor. Parte I

Actualmente no funciona, debido a que al parecer, Google tiene deshabilitada la respuesta JSON de cuando enviamos una petición para geolocalizar

Índice:
1.-Geolocalización con un script en PHP mediante file_get_contents
2.-Geolocalización con un script en PHP mediante cURL
3.-Geolocalización a través de la shell (también usando cURL)
4.-Geolocation API Specification (W3C)

Siguiendo el hilo argumentativo del análisis que hice a Prey, hice una primera parte que quedó un poco escueta, sobre la que hablé de Geolocalización MAC. Pudimos usar un software para tratar de geolocalizar MACs, e incluso desde la página del MapXSS de Samy. Pero nos quedó pendiente una cosa: geolocalizar MACs por nuestros medios.

En esta entrada voy a tratar 4 formas distintas de geolocalizar una MAC, tres de ellas (las primeras) usando la API de Google. Dos de estas formas serán a través de scripts en PHP: uno de ellos usando cURL, y otro mediante file_get_contains. La otra forma, un tanto curiosa, será a través de la shell (en la que también necesitaremos tener cURL instalado). Con respecto a la forma que no usa la API de Google, usaremos la especificación de la W3C para geolocalizar, que está disponible en algunos navegadores.

1.-Geolocalización con un script en PHP mediante file_get_contents

Los dos primeros métodos funcionan internamente igual: hacemos una enviamos una petición JSON mediante POST, esperando recibir una respuesta que deberiamos de recibir, y posteriormente cuando la tengamos, la decodificamos y obtenemos un array con todos los valores y datos que deseamos, nuestra preciada dirección.

Antes de poner el código, una pequeña aclaración: si probamos este código en un servidor web remoto de algún hosting, es muy probable (casi 100%) de que no funcione. Esto tendriamos que probarlo en nuestro propio servidor PHP. Esto es debido a que los hostings deshabilitan (o deberian) la función file_get_contents por una vulnerabilidad que se encontró en la misma.

Aquí dejo el código explicado:

array(
    'method' => "POST",
    'header'  => 'Content-type: application/x-www-form-urlencoded',
    'content' => $datos
  )
);

//Utilizamos file_get_contents para hacer la petición a la API de Google
$respuesta = file_get_contents(
    'http://www.google.com/loc/json',
    false,
    stream_context_create($datos_completos)
);

//Decodificamos lo que nos devuelve
$respuesta = json_decode($respuesta, true);

//Obtenemos los resultados
echo "Direccion Geografica aproximada:
"; foreach($respuesta['location']['address'] as $clave => $valor) echo $clave . ": " . $valor . "
"; echo "Coordenadas aproximadas:
"; echo "Latitud: " . $respuesta['location']['latitude'] . "
Longitud: " . $respuesta['location']['longitude']; ?>

A continuación escribiré una estructura de tipo árbol de los datos que nos devuelve Google tras este envio, ya que nos devuelve una matriz multidimensional, y viene bien saber el contenido de esta:


$respuesta (Array)

>access_token
>location: Array
|-->latitude
|-->longitude
|-->accuracy
|-->address: Array
|---|-->country
|---|-->country_code
|---|-->region
|---|-->country
|---|-->city
|---|-->street
|---|-->postal_code

2.-Geolocalización con un script en PHP mediante cURL

Aquí si ponemos el mismo código que enviamos en el script anterior, no nos funciona, así que tendremos que modificar la petición como veremos a continuación de una manera todavía más sencilla. Además de eso, tendriamos que cambiar la parte de file_get_contents por el cURL. Este código si que deberia de funcionarnos en un servidor remoto, y digo deberia porque yo no lo he conseguido, solo he conseguido que me aparezca el robot de Google tan mono advirtiéndome de un error “temporal” que no tengo ni idea de porqué puede ser… Pero vamos, que en nuestro servidor casero si que funciona.

Aquí el código:


//Datos que enviaremos
$datos_completos = '{version:"1.1.0",request_address:true,wifi_towers:[{mac_address:"xx-xx-xx-xx-xx-xx"}]}';

//Iniciamos cURL
$ch = curl_init();
//Configuramos cURL
curl_setopt($ch, CURLOPT_POST, 1); 
curl_setopt($ch, CURLOPT_POSTFIELDS, $datos_completos);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); 
curl_setopt($ch, CURLOPT_URL,'www.google.com/loc/json'); 

$respuesta = curl_exec($ch);
curl_close($ch);

//Obtenemos la respuesta y posteriormente la decodificamos
$respuesta = json_decode($respuesta, true);

//Para terminar, printeamos los resultados
echo "Direccion Geografica aproximada:
"; foreach($respuesta['location']['address'] as $clave => $valor) echo $clave . ": " . $valor . "
"; echo "Coordenadas aproximadas:
"; echo "Latitud: " . $respuesta['location']['latitude'] . "
Longitud: " . $respuesta['location']['longitude'];

Esto nos devolverá exáctamente el mismo resultado que el anterior script.

3.-Geolocalización a través de la shell (también usando cURL)

Ya por último, acabaremos esta entrada geolocalizando de la forma más sencilla, a través de la shell, en la que solo tenemos que escribirlo y lo recibimos inmediatamente, como veremos en la siguiente imagen.

Usaremos el comando de cURL, por lo que propiamente es hacer lo mismo que en el segundo apartado, pero con la shell:


curl -d '{version:1.1.0,request_address:true,wifi_towers:[{mac_address:xx-xx-xx-xx-xx-xx}]}' www.google.com/loc/json

4.-Geolocation API Specification (W3C)

Para los que usamos twitter, esta especificación no nos deberia de parecer nueva, ya que desde hace tiempo tenemos la opción de añadir desde donde twitteamos usando este método.

A continuación, el código que nos permite esto:











Tras ejecutarlo en Firefox por ejemplo, nos saldrá un aviso de si queremos compartir nuestra localización, que igual a alguno le suena haber visto anteriormente:

Y nos saldrá esto:

A veces no me funciona, igual es que tiene algún límite de peticiones por tiempo (como algunas APIs). Lo curioso (y bueno) del asunto, es que si analizamos con Wireshark los datos, podemos ver que pasan por SSL.

Para terminar con este apartado, decir que en el siguiente enlace disponemos más detalladamente datos relativos al uso de esta Geolocation API.

Saludos, lipman

Usando Google Maps API versión 3

Índice:
1.-Breve Introducción
2.-Inicializando un mapa
3.-Estableciendo puntos
4.-Eventos
5.-Eventos (pasando valores)
6.-Polígonos
7.-Líneas

1.-Breve Introducción

Mucha de la documentación que se encuentra en Internet con respecto a la API de Google Maps es sobre las versiones viejas, por lo que he decidido intentar hacer un conjunto de posts exclusivamente para la última versión.

Para empezar, oficialmente las versiones 1 y 2 se consideran obsoletas, por lo que viene bien aprender un poco acerca de la última versión. Por otro lado, una de las implementaciones nuevas de esta versión, es la adición de Google Street View.

Con las versiones viejas, primero de todo habia que obtener la clave API dependiendo del sitio web en donde quisieramos usar el código. Pero con esta nueva versión no es necesario, es todo mucho más sencillo.

2.-Inicializando un mapa

Lo primero de todo vamos a tratar de entender cómo funciona el “hola mundo de la api de GMaps”:





(1): Como podemos ver, la URL que cargamos es la siguiente: http://maps.google.com/maps/api/js?sensor=false. Aquí simplemente quería explicar qué significa el parámetro sensor, que puede tomar valores de true y false. Este sensor se pone en true cuando el dispositivo sobre el cual se espera que se cargue esta página dispone de un localizador (como un GPS) encargado de determinar la localización del usuario. Como estamos haciendo pruebas sobre un ordenador (que no dispone de GPS) lo tengo puesto en false.

(2): Aquí simplemente decir que creamos una variable llamada coordenadas en la que establecemos el punto céntrico en donde queremos que se centre nuestro mapa. Para obtener las coordenadas, simplemente nos movemos con el Google Maps a un punto, y le damos al botón de enlazar para obtener las coordenadas en los parámetros que se pasan en la URL, como en la siguiente imagen.

(3): Este valor es la cantidad de zoom que queremos aplicar a nuestro mapa. Normalmente va desde el valor 0 al 19. Este valor depende de la zona y del tipo de mapa empleado.

(4): Establecemos el centro, que es la variable coordenadas anteriormente definida.

(5): Aquí elegimos el tipo de mapa que queremos usar:

  • ROADMAP: mapa callejero con el nombre de las calles y demás.
  • SATELLITE: mapa satélite con ningún nombre puesto.
  • HYBRID: mapa híbrido, satélite con los nombres
  • TERRAIN: mapa físico con algunos nombres

(6): Creamos el mapa. Nos tenemos que dar cuenta de que usamos el identificador mapa para luego posteriormente usarlo.

Ahora nos vamos con la parte HTML del asunto:


  

Como podemos ver, creamos un div con el identificador anteriormente usado, y establecemos mediante CSS que ocupe toda la pantalla del navegador.

Ahora, juntamos todo el código fuente, y nos deberia quedar algo así:









  

3.-Estableciendo puntos

En este apartado aprenderemos como establecer puntos dentro de lo que es el mapa. Para este ejemplo, seguiremos usando el código anterior, por lo que, el siguiente código irá a continuación del código Javascript anterior (dentro de la función de iniciar)

  var coordenadas = new google.maps.LatLng(37.680000,-1.669407);
  //Establecemos las coordenadas del punto
  var marker = new google.maps.Marker({ //opciones
      position: coordenadas,
      //Decimos que la posición es la de la variable 'coordenadas'
      map: map,
      //Nombre del mapa
      title:"Hola Mundo!"
      //Titulo (visible cuando colocamos el ratón sobre el punto)
  });

¿Sencillo verdad? Y el resultado es el siguiente:

4.-Eventos

Los eventos son acciones que se toman tras la realización de otra. Con el siguiente ejemplo lo veremos muy claro. Trataremos dos eventos: el cambio de zoom, y el “click” en una marca.

var map;
function iniciar() {
  var coordenadas= new google.maps.LatLng(-25.363882,131.044922);
  var myOptions = {
    zoom: 4,
    center: coordenadas,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  }
  map = new google.maps.Map(document.getElementById("mapa"), myOptions);
  //Hasta aquí es igual que antes

  google.maps.event.addListener(map, 'zoom_changed', function() {
    setTimeout(moveToDarwin, 3000);
  });
  //(1) Primer evento
  
  var marker = new google.maps.Marker({
      position: myLatlng, 
      map: map,
      title:"Hello World!"
  }); //Esto ya lo vimos antes, creamos una marca o punto
  
  google.maps.event.addListener(marker, 'click', function() {
    map.setZoom(8);
  });
  //(2) Segundo evento
  
}
  
function moveToDarwin() {
  var darwin = new google.maps.LatLng(-12.461334, 130.841904);
  map.setCenter(darwin);
}
//Función que se encarga de centrar el mapa en esas coordenadas

Antes de entrar en cada evento particular, podemos destacar las características comunes. Un evento tiene 3 parámetros: el primero hace referencia al objeto que estamos tratando (un mapa o una marca en estos ejemplos). El segundo hace referencia al nombre interno del evento que tiene que ocurrir (que cambie el zoom o que se clickee). El último parámetro simplemente es el accionamiento de una función.

(1): El primer evento se acciona cuando cambiamos el zoom (ya sea para acercar o disminuir), y cuando esto ocurre, al cabo de 3000 ms se acciona la función moveToDarwin que establece el centro en otro sitio.

(2): Este evento se acciona cuando se clickea en la marca, y una vez esto, se establece que el zoom deba de ser 8.

5.-Eventos (pasando valores)

A veces, a la hora de esperar a que se realice un evento, este evento realizado puede transmitir información que nos sea útil. En los ejemplos anteriores no nos valia de nada transmitir información. En uno de los eventos anteriores, simplemente al hacer click en un sitio, estableciamos de zoom 8, y en el otro, simplemente nos moviamos a unas coordenadas predeterminadas.

En el siguiente ejemplo vamos a hacer que cada vez que pinchemos en el mapa, establezcamos una marca. Para esto, a la hora de pinchar, tenemos que transmitir la información de cuando pinchamos (las coordenadas) a la función que se encargará de crear esa marca.


(1): Como podemos ver aquí, transmitimos las coordenadas. event.latLng hace referencia a las coordenadas que queremos transmitir y que posteriormente usaremos para crear la marca.

6.-Polígonos

Simplemente pondré un script de cómo se hacen polígonos. Es realmente fácil y muy intuitivo. El siguiente polígono da como resultado la traza del triángulo de las bermudas:


 
 

7.-Líneas

Funcionan casi igual que los polígonos.. solo que tienen algunas opciones distintas y el nombre de la función (como es obvio..) también cambia. Código de ejemplo:

function initialize() {
  var myLatLng = new google.maps.LatLng(0, -180);
  var myOptions = {
    zoom: 3,
    center: myLatLng,
    mapTypeId: google.maps.MapTypeId.TERRAIN
  };

  var map = new google.maps.Map(document.getElementById("map_canvas"),
      myOptions);
  var flightPlanCoordinates = [
    new google.maps.LatLng(37.772323, -122.214897),
    new google.maps.LatLng(21.291982, -157.821856),
    new google.maps.LatLng(-18.142599, 178.431),
    new google.maps.LatLng(-27.46758, 153.027892)
  ];
  var flightPath = new google.maps.Polyline({
    path: flightPlanCoordinates,
    strokeColor: "#FF0000",
    strokeOpacity: 1.0,
    strokeWeight: 2
  });

  flightPath.setMap(map);
}

Para la gente que se quiera adentrar y profundizar en este mundillo, teneis una referencia justo aquí de los métodos, valores que devuelven, tipos y propiedades, publicado por Google. Viene una cantidad ingente de información de la cual es imposible resumir, pero al venir en completo castellano, será fácil encontrar cosas que queramos adaptar a nuestro uso por parte de la API de Google Maps.

Saludos, lipman

Cómo generar código CSS online

CSS3 es una tecnología que permite a los diseñadores de sitios web modificar rápidamente varios aspectos del diseño de un sitio, pues se mantienen intactos los archivos fuente HTML y solo se modifica el estilo en un archivo aparte, algo verdaderamente versátil. Al principio había que editar estos archivos manualmente, y como la sintaxis varía con respecto al HTML estándar esta tarea podía llevar mucho tiempo. Los niños de hoy en día lo tienen más fácil porque ahora cuentan con los generadores de CSS.

Hay una gran variedad de herramientas destinadas para este fin: CSS3, Please!CSS 3.0 MakerGrad ColorProCSSorCSS3Menu son algunos ejemplos. CSS3.me es uno de los más completos y sencillos de usar.

Podemos seleccionar efectos, bordes, colores y tamaños para los elementos que queremos mostrar, y ver como se modifica el resultado a medida que los vamos cambiando uno a uno. Una vez que lleguemos al resultado deseado, podrás generar un archivo CSS listo para copiar y usar en nuestro sitio haciendo clic en el gran botón azul que dice «GET THE CODE».

Enlace | CSS3.me
Vía| Visual Beta

Instalar la libreria FPDF y algunos comandos básicos

FPDF (Free PDF)

Esta libreria nos permite crear de una manera muy cómoda y sencilla un documento PDF bastante personalizado: cabeceras, pies de página, colores, imágenes (jpeg, png, gif), saltos de línea, saltos de página y diferentes fonts y codificaciones entre otras cosas.
Podemos realizar incluso referencias al propio documento y poderosas tablas, las cuales pueden venir muy bien para resultados de consultas en bases de datos.

  • Instalación

Descargamos la versión más reciente desde su página oficial, en la cual nos encontraremos un archivo comprimido zip con tutoriales y explicaciones de cada función en particular (también disponibles en la página oficial). El archivo más importante del zip es el llamado fpdf.php aunque también necesitaremos tener en nuestro directorio local, el archivo fpdf.css y la carpeta de font.

  • Errores comunes

Antes de empezar a jugar con esta libreria, comentaré un error que puede aparecernos, para de esta manera, poder evitarlo. Puede que nos saltase un error que nos dijera “Cannot modify header information – headers already sent by …”. Esto es solucionable si en el script en donde vamos a realizar el pdf no introducimos nada antes ni después de los tags de inicio y fin de php, es decir, nada antes ni después de “, , , o cualquier otro). Esto puede ser debido a que esta libreria toca la cabecera de nuestro documento, por lo que no debe de haber nada antes de tal.
Otro error común, sobre todo para los castellano parlantes, puede ser el uso de terminadas letras, como la ñ o las vocales acentuadas. Para ello, suponiendo que nuestro texto se encuentre en una variable, la parsearemos de la siguiente manera:

$str = utf8_decode($str);

Por otra parte, puede que sea un caracter “extraño” como el símbolo del euro. Por ello, para estos casos, los definiremos de la siguiente forma:

define('EURO', chr(128));

$str = "Ejemplo de € y " . EURO;
// En nuestro PDF nos muestra lo siguiente: "Ejemplo de ? Y €"

  • Introducción

Para indicar que estamos usando esta libreria, tenemos que hacer referencia al archivo fpdf.php de la siguiente forma:

require("fpdf.php");

Por otra parte, para iniciar la creación del documento PDF, tenemos que indicarlo del siguiente modo:

$pdf=new FPDF();

El funcionamiento a grosso modo es muy simple. Vamos añadiendo páginas y en cada una añadimos lo que queramos. Cuando queramos editar propiedades del tipo colores o fuentes de texto, tenemos que declararlas justo antes de usarlas. Es decir, si queremos hacer dos líneas de texto con fuentes distintas, primero tenemos que declarar las propiedades de la primera fuente, luego escribir la primera línea, declarar las propiedades de la segunda y escribirla.
De igual modo, si alguna vez escribimos algo que ocupe más de una página, esta se añade automáticamente, aunque disponemos de una función (Addpage) para añadir páginas, la cual nos permite acabar de escribir en una página o poner páginas en blanco.
Hay algunas propiedades también, como las cabeceras o los piés de página, que no basta con declarar lo que queremos, sino que hay que realizar la propia función como veremos más adelante. De la misma forma que anteriormente hemos indicado que se iniciaba la configuración del documento pdf, para exportarlo y que salga como tal, dispondremos de otra función que tendremos que aplicar al final (Output).

  • Funciones básicas

Addpage (añade una página), Cell (crea una celda), setFont (declaramos un estilo de fuente), Ln (salto de línea), Image (añade una imagen), setFillColor (establece un color de relleno), setTextColor (establece un color para el texto), SetX (posicionar en el eje X), SetY (posicionar en el eje Y), GetX (obtener posición en el eje X), GetY (obtener posición en el eje Y). Ejemplo:

AddPage();
//Añadimos una página.

$pdf->Addpage();
//Añadimos otra página. De esta manera, la primera quedará en blanco.

$pdf->SetFont('Arial','B',16);
//Seleccionamos la font de nuestros textos. Serial Arial, Bold, de tamaño 16. Ver fonts permitidas en la línea
//104 del archivo fpdf.php. Los estilos permitidos son B (negrita), U (subrayado), I (cursiva), vacio (normal).

$pdf->Cell(10, 20, "Texto interior", 1);
//Creamos una celda de 10 de ancho, 20 de alto, con ese texto en su interior, y 1 de borde.


$pdf->setFillColor(10, 200, 14);
//Declaramos un color de relleno RGB (verde claro).

$pdf->Ln(30);
//Salto de línea de tamaño 30.

$pdf->Cell(50, 20, "Otro texto", 2, 2, L, true);
//50 de ancho, 20 de alto, con ese texto, 2 de borde, L de alineado (landscape), y true es el relleno definido antes
//El segundo 2, nos permite decidir dónde irá el elemento posterior a este. En caso de estar en 2, irá debajo.
// Si hay un 1, al comienzo de la línea siguiente. Si hay un 0, irá a la derecha.

$variable = $pdf->GetY();
// obtenemos la posición Y.

$pdf->SetX($variable);
//Declaro que quiero que el siguiente elemento tenga de posición X, lo que tenía de posición Y.

$pdf->Cell(5, 5, $variable, 1);
//Creamos esta celda.

$pdf->Image("google.jpg", 80, 0, 70, 40);
//Ponemos una imagen. El segundo y el tercer parámetro son las coordenadas X e Y, los dos siguientes 
//son la anchura y la altura respectivamente.

$pdf->Output();
//Función que nos permite obtener el PDF.
?>

  • Funciones más interesantes

En este apartado trataremos de hacer una cabecera y un pie de página para que salgan en todas las páginas. Para esto, hay que extender la clase FPHP como veremos posteriormente de manera que habrá que crear las funciones Footer y Header. Por otra parte, aprenderemos a referenciar objetos dentro del propio documento y establecer el número de páginas totales y actual.

SetFont('Arial','B',15);
    //Declaramos una fuente
    $this->Cell(30,10,'Titulo',1,0,'C');
    //Titulo
    $this->Ln(20);
	//Salto de línea
}

//Pie de página
function Footer()
{
    
    $this->SetY(-15);
    //Posición: a 1,5 cm del final
    $this->SetFont('Arial','I',8);
    
    $this->Cell(0,10,'Página '.$this->PageNo().'/{nb}',0,0,'C');
	//PageNo nos muestra el número de página actual
	//El "/{nb}" es para que nos muestre el número de páginas máximo
	//de tal forma que se quede del tipo "Página 1/2"
	//Esto es debido a la función posterior "AliasNbPages"
}
}

//Creación del objeto de la clase heredada
$pdf=new PDF();
$pdf->AliasNbPages();
//Es la función que nos declara un Alias por defecto para 
//obtener el número de páginas máximo

$pdf->AddPage();
$link = $pdf->AddLink();
//Establecemos el link

$pdf->Write(5, "Pulsar aquí", $link);
//Al pulsar en ese texto, nos linkeamos gracias al link declarado anteriormente

$pdf->AddPage();
$pdf->AddPage();
$pdf->SetLink($link, 70);
//Establecemos el link a 1,5 centímetros (eje Y)

$pdf->Output();
?>

Para finalizar, decir que hay multitud de funciones para un uso más intensivo y completo de esta libreria que se encuentran en la parte de “Manual” su página web. En estas breves páginas he intentado escribir las más importantes y útiles para un sencillo uso.

Saludos, lipman.

[Parte 2] Agregar Captcha a Prestashop y arreglar la base de datos

Parte 1

Recordemos la situación actual: Tenemos en nuestra base de datos un montón de cuentas introducidas con la intención de llenar nuestra base de datos, así que lo primero será borrarlas, y lo segundo intentar que no lo puedan volver a repetir introduciendo un Captcha para la verificación. También ayudaría banear la IP del atacante, excepto si lo hiciese con una botnet (vaya tipo…).

Borrando las cuentas basura
Para lo siguiente, vamos a tener que analizar la situación. En la situación actual, podemos darnos cuenta de que las cuentas son del tipo delanoverN@a.c, por lo que podemos recorrer la base de datos mirando cada cuenta de correo y borrándola si concuerda con el patrón usado por el atacante. Esto sería muy sencillo, puesto que simplemente, podriamos mirar a partir del arroba, ya que el dominio a.c no existe, así que podríamos recoger la cadena, y comprobar si desde el arroba hasta el final, contiene “a.c”.

Para esto, podemos irnos directamente a la base de datos e introducir la orden para eliminar, o desde un fichero php:

$consulta = "DELETE FROM ps_customer WHERE email REGEXP '@a\.c'";
$resultado = mysql_query($consulta);

Esta forma nos permite usar expresiones regulares, y detectará todas las que contengan @a.c, eliminándolas. En caso de complicarse más… se podría mirar la fecha de registro y eliminar a todos los usuarios registrados siguiendo cierto patrón, aunque esto es peligroso ya que podriamos eliminar a más de un usuario real.

Agregando el sistema de CAPTCHA a Prestashop

Aquí, la parte más interesante para los que quieran usar Prestashop, puesto que ya hemos visto el peligro que corremos si no disponemos del captcha. Vamos a usar reCaptcha, el captcha de Google. El primer paso es registrarnos, tras el cual, nos darán dos claves, una pública y una privada. La primera se encarga de que el sistema pueda saber qué página web le está llamando (en nuestro caso, la nuestra). Y la segunda clave, es la verificación del captcha.

Tras registrarnos, procedemos a descargarnos y subir a nuestro servidor los ficheros necesarios para el funcionamiento de este. Estos se encuentran en el siguiente enlace.

Una vez subidos los archivos, procedemos a la inserción del código (y a una modificación que tendremos que hacer). Para ello vamos a explicar cómo funciona a rasgos generales el código de la página, y que si no tenemos conocimientos de programación web, puede resultar un poco más complicado de entender.

Como podemos ver en el enlace a la hora de registrarnos, el archivo encargado de esto, se llama authentication.php y se encuentra en el directorio raiz de la tienda electrónica. Vamos a ver las últimas líneas de este fichero:

include(dirname(__FILE__).'/header.php');
$smarty->assign('errors', $errors);
Tools::safePostVars();
$smarty->display(_PS_THEME_DIR_.'authentication.tpl');
include(dirname(__FILE__).'/footer.php');

Podemos ver que, incluye la cabecera, luego una plantilla tpl, y finalmente el pie de página o footer. Antes de empezar a toquetear, debemos de tener claro dónde queremos colocar el captcha. Yo he decidido colocarlo justo antes del submit del formulario, es decir, entre el “Tax Identification” y el botón Register. Sin embargo, a priori no puedo meter el código, ya que para esto, tendría que dividir la plantilla en dos partes. Espero que se entienda un poco mejor esta aclaración con la siguiente imagen:

Conclusión: tenemos que dividir la plantilla en dos partes para poder introducir en medio nuestro código para llamarlo, así que vayamos a ello.

División de la plantilla en dos partes
La plantilla se encuentra en /themes/prestashop/authentication.tpl. Para empezar, renombraremos este archivo a authentication1.tpl, y crearemos uno vacio llamado authentication2.tpl. Recordemos que nosotros queremos poner el captcha tras la autenticación del DNI, así que vamos al final del fieldset class=”account_creation dni”, de manera que este archivo tiene que acabar:

	{l s='DNI / NIF / NIE'}

{/if}

Nuestro archivo authentication2.tpl contendrá lo que hemos quitado de ahí, que es lo siguiente:

{if isset($back)}{/if} *{l s='Required field'}

Ya tenemos dividida en dos la plantilla. Ahora tenemos que volver al authentication.php para cambiar lo que dijimos anteriormente de forma que quede de la siguiente manera:

include(dirname(__FILE__).'/header.php');
$smarty->assign('errors', $errors);
Tools::safePostVars();
$smarty->display(_PS_THEME_DIR_.'authentication1.tpl');
//Aquí irá el Captcha
$smarty->display(_PS_THEME_DIR_.'authentication2.tpl');
include(dirname(__FILE__).'/footer.php');

Agregando definitivamente el Captcha
Google también nos deja escrito una poca de ayuda para poder meter el captcha en la siguiente dirección. Seguiré explicando cómo se integra, ya que además, Prestashop autentica los datos y realiza el registro en la propia página donde se recogen, osea, no son dos páginas (una de recogida y una de verificación y creación del usuario) sino que se hace todo en la misma. Por lo que el código de “ambas páginas” lo tendremos que poner en la misma, en la de authentication.php.

Por partes: Google nos dice que en la página en donde tiene que ir el captcha, tenemos que poner el siguiente código:

require_once('recaptchalib.php');
$publickey = "your_public_key"; // you got this from the signup page
echo recaptcha_get_html($publickey);

La línea encargada de mostrar el captcha (la del echo) la colocamos justo entre medio de las dos partes de la plantilla, quedando:

include(dirname(__FILE__).'/header.php');
$smarty->assign('errors', $errors);
Tools::safePostVars();
$smarty->display(_PS_THEME_DIR_.'authentication1.tpl');
echo recaptcha_get_html($publickey);
$smarty->display(_PS_THEME_DIR_.'authentication2.tpl');
include(dirname(__FILE__).'/footer.php');

En la parte superior de la authentication.php pondremos las dos líneas que nos quedan para incluir la libreria:

require_once('recaptchalib.php');
$publickey = "xxxxxxxxxxxxxxx"; // Clave pública

Ya tenemos puesta la primera parte, pasemos a la segunda. Google nos dice que introduzcamos la siguiente:

  require_once('recaptchalib.php');
  $privatekey = "your_private_key";
  $resp = recaptcha_check_answer ($privatekey,
                                $_SERVER["REMOTE_ADDR"],
                                $_POST["recaptcha_challenge_field"],
                                $_POST["recaptcha_response_field"]);

  if (!$resp->is_valid) {
    // What happens when the CAPTCHA was entered incorrectly
    die ("The reCAPTCHA wasn't entered correctly. Go back and try it again." .
         "(reCAPTCHA said: " . $resp->error . ")");
  } else {
    // Your code here to handle a successful verification
  }

La explicación de este código la podriamos dividir en dos: al principio se encuentra la parte encargada de la verificación, y posteriormente (con el if-else) se encuentra la parte condicional de lo que pasa si introducimos o no correctamente el captcha.

Volvemos a la parte superior de authentication.php y agregamos más de esas líneas, quedando junto con lo anteriormente escrito, de la siguiente manera:

require_once('recaptchalib.php');
$publickey = "xxxxxxxxxxxxxxx"; // Clave pública

$privatekey = "xxxxxxxxxxxx"; // Clave privada
$resp = recaptcha_check_answer ($privatekey, $_SERVER["REMOTE_ADDR"], $_POST["recaptcha_challenge_field"], $_POST["recaptcha_response_field"]);

Y finalmente, pasamos a la parte condicional de qué pasará al introducir mal nuestro querido captcha. Nos vamos a la parte de los errores, que tiene que estar sobre la línea 55, que empieza así:

if (Tools::isSubmit('submitAccount'))
{
	$create_account = 1;
	$smarty->assign('email_create', 1);
	$validateDni = Validate::isDni(Tools::getValue('dni'));
	if (!Validate::isEmail($email = Tools::getValue('email')))
		$errors[] = Tools::displayError('e-mail not valid');
	if (!Validate::isPasswd(Tools::getValue('passwd')))
		$errors[] = Tools::displayError('invalid password');
	if (Customer::customerExists($email))
		$errors[] = Tools::displayError('someone has already registered with this e-mail address');

Y justo ahí detrás, introducimos las dos últimas líneas que nos quedan:

	if (!$resp->is_valid)
		$errors[] = Tools::displayError('Error entering the CAPTCHA');

Test final
Al no introducir el captcha o introducirlo incorrectamente, nos saldrá el siguiente mensaje:

Y ahora, vamos a introducir en el navegador la línea que introduciamos en la Parte 1 para que se nos registrase una cuenta automáticamente con iMacros, a ver que pasa:

Con esto, damos por concluido este “tutorial”.

Saludos, lipman.

[Parte 1] Prestashop. El peligro de no usar Captcha

El peligro de no usar CAPTCHA

Buenas! Esta vez voy a tratar de hablar acerca de Prestashop, un sistema de comercio electrónico de código abierto.

Leí en otro blog acerca de un “fallo” (más bien descuido o como lo quieran llamar) de esta plataforma, y es que no usa captcha, pero no solo eso, sino que su sistema de verificación de usuarios a la hora de registrarse deja bastante que desear. Por un lado, se traga correos del tipo a@a.c y por otro, es posible crear cuentas con idénticos nombres, apellidos, direcciones… Lo único que se verifica, es que no haya dos cuentas de correo electrónico exáctamente iguales.

Procedemos a analizar con Tamper Data todos los datos que pasamos a la hora de registrarnos, simplemente rellenando los campos que son obligatorios. Deste Tamper Data, podremos ver algo parecido a esto:

Para encontrar lo que queremos con mayor facilidad, fijarnos en el campo de “Método” y buscar “POST” (método que usa el formulario para enviar los datos). Una vez encontrado, copiamos el contenido de POSTDATA, y vamos a hacer lo interesante: llenar la base de datos de cuentas basura.

Como dije anteriormente… lo único que se comprueba es que no existan dos direcciones de correo iguales, asi que podrían valer:

a1@a.c
a2@a.c
a3@a.c

Como no es plan de molestarnos en hacer un programa para ello, vamos a programar un script en la conocida addon de Firefox, iMacros. iMacros permite ejecutar todo tipo de cosas dentro del navegador, podria dedicarle una entrada entera y bien larga a este complemento tan interesante, que si no conociais, ya iba siendo hora. Usaremos iMacros para repetir el proceso tantísimas veces como queramos, de forma controlada.

Antes de programar el script, vamos a ver la base de datos con usuarios de prueba que he creado, para ver el antes, y el después. Como podemos ver, solo tenemos 6 usuarios en nuestra base de datos.

Procedemos a programar el script de iMacros, el cual explicaré un poco. Lo bueno de este addon, es la facilidad y flexibilidad que permite su código. Como vamos a ver, con 2 líneas habremos conseguido lo que queriamos:

1 URL GOTO=http://delanover.com/shop/authentication.php?POSTDATA=customer_firstname=nombre&customer_lastname=apellidos&email=delanover{{!loop}}%40a.c&passwd=password&days=&months=&years=&company=&firstname=nombre&lastname=apellidos&address1=asddd&address2=&postcode=1234&city=asddd&id_country=6&id_state=&other=&phone=&phone_mobile=&alias=Mi+direcci%C3%B3n&dni=&email_create=1&back=my-account.php&submitAccount=Registrarse
2 URL GOTO=http://delanover.com/shop/index.php?mylogout

La primera línea nos manda a esa dirección, en la cual, como podemos comprobar, están los datos que hemos recogido de Tamper Data anteriormente, precedidos de “http://delanover.com/shop/authentication.php?”. La interrogación es para separar, y el resto, es la dirección en donde nos registramos. Esto nos permite registrarnos con una sola línea.
La segunda línea, nos dirige a un enlace que nos desloguea. Es decir que lo que hacemos es: Registrarnos -> Desloguearnos -> Registrarnos -> Desloguearnos -> .. y así sucesivamente.

Prestamos atención a la variable {{!loop}} que se encuentra en la primera línea, en el correo. Esta variable, se irá incrementando en uno a medida que aumenta el bucle, así obtendremos cuentas de correo del tipo delanover1@a.c, delanover2@a.c, delanover3@a.c, …

Ahora sencillamente, ponemos el navegador, iniciamos el iMacros, seleccionamos el rango del bucle (desde 1, hasta 200 por ejemplo) y a esperar. Lo malo de esto es que no podremos usar el ordenador hasta que acabe (alguna pega tendria que tener) así que lo dejamos un rato corriendo y seguimos. Resultado de 15 minutos:

Continuará…

Parte 2

Saludos, lipman