Aplicaciones seguras con PHP
Por mucho tiempo uno de los fuertes de PHP fue que los valores enviados desde un formulario eran automáticamente convertidas en variables globales para ti. Los creadores de PHP una manera alternativa de accesar los datos enviados. En la versión 4.2 cambiaron la manera antigua en que las cosas funcionaban. Cómo explicaré en este artículo, estos cambios se hicieron en el nombre de la seguridad. Juntos exploraremos las nuevas características de PHP para manejar datos enviados desde un formulario y cómo se pueden usar para escribir scripts más seguros.
¿Ahora qué?
Considere este script que da acceso a una página web si es que se ingresa el nombre de usuario y contraseña correcta:
if ( $username == 'admin' && $password == 'secret' ){
$authorized = true;
}
if ( $authorized ){ ?>
<!-- Contenido HTML Super-Secreto va aquí -->
<?php } else { ?>
<!-- A los usuarios no autorizados se les mostrará este formulario -->
<p >Please enter your username and password:</p >
<form action="<?php echo $PHP_SELF; ?>" method="post">
<p>Username: <input type="text" name="username" /><br>
Password: <input type="password" name="password" /><br>
<input type="submit" /></p >
</form>
<?php } ?>
Ahora, el problema con este script es que se puede obtener acceso fácil con tener que escribir el nombre de usuario y contraseña correcta, es más, sin escribir nada se puede accesar. Simplemente escribe en tu barra de navegador la dirección con ?authorized=1 al final. PHP crea una variable para cada valor enviado (puede que sea desde un formulario o desde la URL o una cookie) esto setea $authorized como 1, es decir como verdadero, lo cual te permite el acceso instantáneo.
¿Cómo es que PHP 4.2 cambia las cosas?
Una instalación fresca de PHP tiene a la opción register_globals como Off por defecto. Así los valores EGPCS ( Environment, Get, Post, Cookies, Server ) no son creadas como variables. Sí, esta opción puede cambiarse a On manualmente, pero el equipo de PHP preferiría que no lo hagas. Para cumplir sus deseos necesitas una manera alternativa de recibir estos valores.
$_ENV -- Contiene las variables del sistema.
$_GET -- Contiene las variables en el "query string" incluyendo los formularios GET.
$_POST -- Contiene las variables enviadas des de un formulario POST.
$_COOKIE -- Contiene todas las variables de las Cookies.
$_SERVER -- Contiene todas las variables de servidor, tales como HTTP_USER_AGENT.
$_REQUEST -- Contiene todo dentro de $_GET, $_POST y $_COOKIE.
$_SESSION -- Contiene todas las variables de sesiones registradas.
Ahora reescribamos el script mal hecho que teníamos anteriormente para que funciones con PHP 4.2 o superior (con register_globals Off)
$username = $_REQUEST['username'];
$password = $_REQUEST['password'];
$PHP_SELF = $_SERVER['PHP_SELF'];
//Chekea el usuario y contraseña
if ( $username == 'admin' && $password == 'secret' ){
$authorized = true;
}
if ( $authorized ){ ?>
<!-- Contenido HTML Super-Secreto va aquí -->
<?php } else { ?>
<!-- A los usuarios no autorizados se les mostrará este formulario -->
<p >Por favor, igrese su usuario y contraseña:</p >
<form action="<?php echo $PHP_SELF; ?>" method="post">
Usuario: <input type="text" name="username" /><br>
Contraseña: <input type="password" name="password" /><br>
<input type="submit" />
</form>
<?php } ?>
Como verán, lo único que necesité hacer fue agregar estas tres líneas:
$password = $_REQUEST['password'];
$PHP_SELF = $_SERVER['PHP_SELF'];
Cómo esperamos que el usuario ingrese sus datos lo que hacemos es obtener todos estos mediante $_REQUEST.
Ahora usando esto el usuario puede accesar de distintas maneras, sea mediante la URL, una cookie o un forumario. Si deseas limitar la forma en que el usuario debe ingresar usa $_POST en lugar de $_REQUEST:
$password = $_POST['password'];
$PHP_SELF = $_SERVER['PHP_SELF'];
También incluimos dentro de las variables de servidor a la muy usada variable $PHP_SELF porque como register_globals está desabilitado entonces la variable no se crear como una variable global. Cómo en nuestro script esta variable se usa una sola vez tal vez quisieras ponerla directamente en tu formulario:
De esta manera damos acceso a estas variables, pero en sí el script no ha cambiado en casi nada. Con register_globals Off simplemente obliga al desarrollador que tenga en cuenta la data que viene de fuentes no confiables.
¿Pero esto no significa escribir más?
Sí, en este simple script que tenemos más atrás se necesita escribir más código, pero míralo de un lado positivo, tienes más seguridad y a la vez tu código luce más elegante.
Una característica especial de estos arrays es que no como otras variables de PHP, estas son totalmente globales. ¿Cómo ayuda esto? extenderemos nuestro ejemplo un poco más.
Para dar acceso a multiples páginas moveremos nuestro código a un archivo llamado protectme.php:
/* proteccion.php */
function authorize_user( $authuser, $authpass ){
$username = $_POST['username'];
$password = $_POST['password'];
// chequea usuario y password
if ( $username != $authuser || $password != $authpass ){
?>
<!-- A los usuarios no autorizados se les mostrará este formulario -->
<p >Por favor, igrese su usuario y contraseña:</p >
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
Usuario: <input type="text" name="username" /><br>
Contraseña: <input type="password" name="password" /><br>
<input type="submit" /><
</form>
<?php
exit();
}
}
?>
Ahora nuestra página protegida se verá algo como:
require('proteccion.php');
authorize_user('admin','secret');
?>
<!-- Contenido HTML Super-Secreto va aquí -->
¿Bonito y simple, cierto? ahora dime, que le falta ala función authorize_user()? Lo que falta es la declaración de $_POST dentro de la función. Con PHP 4.0 con register_globals habilitado era necesario agregar una línea de código para accesar las variables $username y $password dentro de la función:
En otros lenguajes de programación con sintáxis similar a la de PHP es necesario declarar las variables dentro de una función para poder accesarlas con el comando global demostrado más arriba.
Con register_globals desabilitado en PHP 4.0 para incrementar la seguridad usarías $HTTP_POST_VARS para obtener los valores que enviaste mediante un formulario, pero aun así es necesario hacer lo siguiente:
global $HTTP_POST_VARS;
$username = $HTTP_POST_VARS['username'];
$password = $HTTP_POST_VARS['password'];
}
Note que he agregado global $HTTP_POST_VARS;
Pero en PHP 4.1 o superior las variables como $_POST (y todas las demás anteriormente mencionadas) siempre están accesibles desde cualquier alcance. Por esto es que no se necesita declarar "global" al comienzo de la función:
$username = $_POST['username'];
$password = $_POST['password'];
}
¿Cómo afecta esto a las sesiones?
La introducción de $_SESSION ayuda a simplificar el código de sesión. En lugar de variables globales como variables de sesión y que después se te hace todo un mundo, simplemente refierete a tus variables de sesión de la siguiente manera: $_SESSION['variable'].
Consideremos otro ejemplo de autorización en PHP. Esta vez usará sesiones para dar acceso al usuario a tu sitio web. El primer ejemplo con PHP 4.0 (y register_globals habilitado):
session_start();
if ( $username == 'admin' && $password == 'secret' ){
$authorized = true;
session_register('authorized');
}
if ( !$authorized ){ ?>
<!-- Mostrar formulario HTML para autorización -->
<?php } else { ?>
<!-- Contenido HTML Super-Secreto va aquí -->
<?php } ?>
Ahora, localize el hoyo de seguridad. Como antes, con sólo agregar un ?authorized=1 al final de la URL se consigue el acceso inmediatamente. El desarrolador probablemente pensó que $authorized era una variable de sesión y se olvidó que podría ser utilizada por el usuario para obtener acceso sin tener que escribir un usuario ni contraseña.
Aquí les mostramos la manera en que se ve el mismo script con register_globals desabilitado y usando un array especial:
session_start();
if ( $_POST['username'] == 'admin' && $_POST['password'] == 'secret' ){
$_SESSION['authorized'] = true;
}
if ( !$_SESSION['authorized'] ){ ?>
<!-- Mostrar formulario HTML para autorización -->
<?php } else { ?>
<!-- Contenido HTML Super-Secreto va aquí -->
<?php } ?>
En vez de registrar una variable normal como una de sesión, seteamos directamente la variable de sesión mediante $_SESSION y despues usándola de la misma manera. Ya no hay más confusiones sobre que variable es una de sesión y además habrás notado que hay menos código.
Conclusión
Aquí hemos demostrado lo importante de cómo se debe tratar una variable y cómo hacer para datos de fuentes externas más convenientemente. En la versión de PHP 4.2, register_globals fue desabilitado por defecto para reducir la tendencia desarroladores inexpertos de escribir scripts inseguros.










