Mes de la seguridad PHP: Inicialización de Variables
| en General | Publicado el 17-05-2010
2
Contribuyendo con la celebración del “MPOS” (Month of PHP Security) les traigo a ustedes este pequeño artículo de Jakub Vrana llevado al español de mi mano y con algunos ajustes personales.
Inicialización de variables PHP
Consideremos el siguiente código:
<?php
if (authUser($_POST["login"], $_POST["password"])) {
$auth = true;
}
if ($auth) {
echo "Secret\n";
}
?>
Puedes detectar fácilmente la vulnerabilidad en el mismo. La variable $auth no se inicializó para todos los casos por lo que puede ser engañada desde el exterior. PHP define una forma de manejar las variables sin inicializar (a diferencia del lenguaje C, por ejemplo), todos ellos tienen el valor null. El problema es que la variable se puede inicializar de otras fuentes:
- Código Anterior.
- Archivo incluido (include)
- Remotamente si register_globals está habilitado.
- Usando el extract de una variable (no fiable).
Defensa:
Defenderse de este tipo de vulnerabilidad es muy simple – Siempre inicializa las variables:
<?php
$auth = false;
if (authUser($_POST["login"], $_POST["password"])) {
$auth = true;
}
?>
Ahora $auth contiene algo (o nada) entonces siempre resolverá a FALSE. Es una buena idea inicializar las variables incondicionalmente. El siguiente código debería funcionar aunque no es tan robusto:
<?php
if (authUser($_POST["login"], $_POST["password"])) {
$auth = true;
} else {
$auth = false;
}
?>
Ahora consideremos que a alguien le gusta revisar las direcciones IP y lo hace de este modo:
<?php
if ($_SERVER["REMOTE_ADDR"] == "127.0.0.1") {
if (authUser($_POST["login"], $_POST["password"])) {
$auth = true;
}
} else {
$auth = false;
}
?>
La variable $auth puede ser engañada de nuevo !
Nota: El atacante debe conocer la variable a engañar, bien puede adivinarla, obtenerla de algún mensaje de error, usando fuerza bruta o en la mayoría de los casos, se ha hecho con el código fuente (aplicaciones open source o siendo un ex-empleado de una empresa).
Deshabilitando register_globals
Es importante mencionar que deshabilitar register_globals no es en definitiva una defensa contra este tipo de ataques. Esto sólo nos acerca a evitar el más común de los tipos de ataque. Por supuesto es una buena idea deshabilitarlo pero las buenas aplicaciones no dependen de esta directiva y funcionan indistintamente de ellas, así no es necesario emular de este modo:
<?php
// NUNCA USES ESTE TIPO DE CÓDIGO
if (ini_get("register_globals")) {
foreach ($_REQUEST as $key => $val) {
unset($$key);
}
}
?>
Reconociendo variables no inicializadas
PHP posee un mecanismo para vigilar variables no inicializadas, este es el manejador de errores E_NOTICE quien nos muestra las variables no inicializadas, aunque esto conlleva algunos problemas:
- No advierte acerca de un array sin inicializar.
- Advierte al acceder a un índice no-existente en un array debidamente inicializado.
- Se publica en tiempo de ejecución.
E_NOTICE No advierte acerca de un array sin inicializar.
Este primer problema es el más relevante, considere el siguiente código:
<?php
$config["password"] = "pwd";
if (isset($_POST["password"]) && $_POST["password"] == $config["password"]) {
echo "Secret information.\n";
}
?>
Esto no notará si un atacante falsifica la variable $config. Si el pasa una cadena entones el código es interpretado como esto:
<?php
$config[0] = "p";
// $config es una cadena de modo que [] es usada para acceder a los bytes de la cadena
// "password" es convertido al número 0 porque la posición de la cadena son siempre enteros
// sólo un byte puede ser escrito a [0] de modo que "pwd" es interpretado como "p"
if (isset($_POST["password"]) && $_POST["password"] == $config[0]) {
echo "Secret information.\n";
}
?>
Ahora bien, esto es suficiente para adivinar el primer carácter de la contraseña y enviarla junto con el falso $config
E_NOTICE advierte al acceder a un índice no-existente en un array debidamente inicializado.
Este segundo problema no está relacionado con la seguridad pero si con desarrollar código “bien hecho”. El siguiente código informaría una notificación aún si funciona bien y no puede ser engañado:
<input name="search" value="<?php echo htmlspecialchars((string) $_GET["search"]); ?>" />
Con el E_NOTICE habilitado, nosotros debemos re-escribir el código aunque un poco más largo con la misma funcionalidad:
<input name="search" value="<?php
if (isset($_GET["search"])) {
echo htmlspecialchars((string) $_GET["search"]);
}
?>" />
Otro ejemplo de esta restricción – El siguiente código es perfectamente válido y hace lo que esperamos que haga (contar miembros de un grupo):
<?php
$groups = array();
foreach (getData() as $data) {
$groups[$data["id_group"]]++;
}
?>
Con el E_NOTICE habilitado, debemos re-escribirlo de este modo:
<?php
$groups = array();
foreach (getData() as $data) {
if (!isset($groups[$data["id_group"]])) {
$groups[$data["id_group"]] = 0;
} else {
$groups[$data["id_group"]]++;
}
}
?>
Yo no diría que este es el código más claro y menos propenso a errores (ya hay un error incluido).
E_NOTICE se publica en tiempo de ejecución.
Este tercer problema está relacionado con la seguridad. Si usamos variables no inicializadas que no monitorizamos durante el desarrollo, entonces el atacante puede usarle. Es bueno informar sobre variables no inicializadas en el log de errores pero si esto puede ser usado por el atacante, entonces será demasiado tarde. Es posible hacer notificaciones fatales con set_error_handler pero no vale la pena para la mayoría de las aplicaciones.
Mejor monitoreo de variables no inicializadas
No recomendaría deshabilitar las notificaciones. Sin embargo, esto no soluciona todos los problemas y requiere de re-escribir un código más profundo como han notado. Afortunadamente, hay una forma mejor de rastrear las variables no inicializadas que resuelve los tres problemas de las notificaciones. Su nombre es php-initialized. Es una herramienta para analizar el código fuente en búsqueda de variables no inicializadas. Esto no ejecuta el código y tiene algunas limitaciones pero se puede utilizar para comprobar el código de forma rutinaria, por ejemplo, después o antes de realizar una refactorización.
La principal limitación es que sólo el bloque actual es considerado dentro del ámbito de variables. Así, el siguiente código reclamaría a $auth como variable no inicializada:
<?php
if (authUser($_POST["login"], $_POST["login"])) {
$auth = true;
} else {
$auth = false;
}
if ($auth) {
echo "Secret\n";
}
// Imprime: Uninitialized variable $auth on line 7
?>
No use $_REQUEST
Engañar variables no sólo involucra a las variables globales. La variable $_REQUEST puede ser llenada desde cualquier fuente externa.
Recordemos que $_REQUEST obtiene sus valores de los métodos: POST, GET y COOKIE … en su lugar deberíamos ser más específicos respecto al origen de la información usando las super-globales: $_POST, $_GET y $_COOKIE en cada caso.
En resumen
Las buenas aplicaciones PHP deberían siempre inicializar sus variables antes de usarlas. Es una maravillosa idea establecer el register_globals en OFF Aunque una buena aplicación debe funcionar indistintamente de dicha directiva. PHP nos ofrece un manejador de errores E_NOTICE que puede monitorizar las variables que no están inicializadas pero esto no debe ser 100% confiable. La herramienta php-initialized resolverá estás deficiencias.

