Cómo es el archivo php.ini

En el archivo de configuración php.ini se encuentran las opciones que necesita PHP para funcionar (puede ver en el glosario XHTML+CSS cómo instalar PHP 5.2.13 en Windows). Se trata de un archivo de texto donde aparecen expresiones como engine = On que es la primera configuración en ese archivo. Esto es para activar los scripts PHP bajo el servidor Apache (puede ver en el glosario XHTML+CSS cómo instalar Apache 2.2.15 en Windows). A una cierta altura del archivo encontramos literalmente:

; Enable the PHP scripting language engine under Apache.
; http://php.net/engine
engine = On

Los comentarios son las líneas que empiezan por un punto y coma. El php.ini está bien comentado, lo cual se agradece. Lo expuesto en estos temas son copias literales de ese php.ini que se encuentran en el servidor Apache que tengo instalado como local, pues es necesario para efectuar el desarrollo de los scripts. En todos los casos se tratan de asignaciones como la vista engine = On, donde la parte izquierda es una opción de configuración y la derecha es un valor que se le asigna.

En este capítulo se exponen las configuraciones que están relacionadas con lo visto en los capítulos anteriores. Se trata de algunas opciones que afectan a la seguridad y que conviene conocer.

Qué es magic-quotes

En el manual de PHP tenemos la referencia de magic-quotes. Se trata de escapar con una barra invertida (\) todas las comillas dobles ("), simples ('), barras invertidas (\) y caracteres nulos (NULL) que se reciben con GET, POST, COOKIE o ENV, es decir todo lo que puede venir desde el usuario. Hace lo mismo que la función addslashes().

En primer lugar conviene decir que se declara obsoleta desde la versión 5.3.0 estando previsto que desaparezca en la versión 6. En la versión 5.2.13 que estoy usando en mi servidor local viene desactivada en el php.ini:

; Magic quotes are a preprocessing feature of PHP where PHP will attempt to
; escape any character sequences in GET, POST, COOKIE and ENV data which might
; otherwise corrupt data being placed in resources such as databases before
; making that data available to you. Because of character encoding issues and
; non-standard SQL implementations across many databases, it's not currently
; possible for this feature to be 100% accurate. PHP's default behavior is to
; enable the feature. We strongly recommend you use the escaping mechanisms
; designed specifically for the database your using instead of relying on this
; feature. Also note, this feature has been deprecated as of PHP 5.3.0 and is
; scheduled for removal in PHP 6.
; Default Value: On
; Development Value: Off
; Production Value: Off
; http://php.net/magic-quotes-gpc
magic_quotes_gpc = Off

Con get_magic_quotes_gpc() podemos saber si está activada esta opción de configuración en el servidor, dando 0 para la desactivada y 1 para la activada.

El problema de la inseguridad se explica en el manual PHP sobre la seguridad, destácandose que nace con la idea de prevenir la inyección de caracteres no deseados cuando se hacían consultas SQL a una base de datos. Sin embargo no siempre es necesario escapar las comillas que, por ejemplo, provienen de un formulario y no van a una base de datos. Confiar toda la seguridad del sitio en magic_quotes es un error, pues lo indicado es partir del principio de desconfiar de cada valor recibido. Así simplemente podemos usar addslashes() con cada valor que entra en el sistema si es que esperamos que pueda causar daños. Incluso mejor usar htmlspecialchars() con ENT_QUOTES como vimos en un tema anterior. Y si los valores van a una base de datos, usar las funciones específicas de la misma para evitarlo, pues todas las bases de datos no tienen el mismo comportamiento en cuanto a cuestiones de seguridad.

Deshacer magic-quotes activado con stripslashes()

Si magic_quotes_gpc no se puede desactivar en tiempo de ejecución, una de las cosas que podemos hacer es quitar las barras invertidas si es que no deseamos almacenarlas. Esto podemos hacerlo con stripslashes(). Si envíamos la cadena <"&'> en un campo de formulario a un script PHP que le aplica htmlspecialchars() con ENT_ QUOTES, si el servidor tiene magic_quotes_gpc activado aparacerán las comillas dobles y simples escapadas. Visualmente veremos <\"&\'>, aunque el código real de estos caracteres será &lt;\&quot;&amp;\&#39;&gt;. Si está desactivado se verá igual pero sin las barras invertidas. Usando stripslashes() si magic_quotes_gpc estuviera activado, evitaremos que se agreguen esas barras invertidas.

Aunque el servidor tenga magic_quotes_gpc activado no podemos confiar totalmente en ese uso. Puede ser conveniente detectar al inicio de cada script este punto, para luego en su caso eliminar las barras de escape si es necesario:

//Vemos si tiene magic_quotes activado el servidor
$magicq = (get_magic_quotes_gpc() == 1);

//Iniciamos las variables que contendrán los valores
//enviados por el usuario
$un_campo = "";

//Iniciamos recogiendo los GET enviados por el usuario
foreach($_GET as $campo=>$valor){
    switch ($campo) {
        case "un-campo":
            //La función htmlspecialchars() evita que
            //el usuario envíe caracteres no deseados
            $un_campo = htmlspecialchars($valor, ENT_QUOTES);
            //Si el servidor tiene magic_quotes_gpc activado, quitamos
            //las barras invertidas
            if ($magicq) $un_campo = stripslashes($un_campo);               
            break;
    }
}

Se observa que si magic_quotes_gpc está activado, usamos stripslashes() para eliminar las barras invertidas que escapan las comillas.

Qué es register-globals

Como fuente inicial de consulta en cuestiones relacionadas con register-globals señalo los siguientes enlaces de la documentación oficial PHP:

Cómo parece estar tan relacionada con la seguridad, búsquemos esa opción de configuración register_globals en el php.ini de mi servidor local:

; Whether or not to register the EGPCS variables as global variables.  You may
; want to turn this off if you don't want to clutter your scripts' global scope
; with user data.
; You should do your best to write your scripts so that they do not require
; register_globals to be on;  Using form variables as globals can easily lead
; to possible security problems, if the code is not very well thought of.
; http://php.net/register-globals
register_globals = Off

Vemos que esta desactivada (Off) en mi servidor local. De hecho ha estado así desde que instale el PHP, de lo que se deduce que con la versión 5.2.13 ya viene desactivada por defecto. Según se desprende de la documentación y debido a problemas de seguridad, a partir de la versión 4.2.0 ya venía desactivada. E incluso a partir de la versión 5.3.0 ya se declara obsoleta. Una traducción personal del comentario previo podría ser:

Opción para configurar las variables EGPCS (ENV, GET, POST, COOKIE, SERVER) como variables globales. Se debería ponerlas a off si no queremos que el alcance de nuestro script se vea afectado con datos de usuario. Lo mejor sería escribir el código sin necesitar register_globals activado (on); Usar variables de formulario como globales puede conducir fácilmente a posibles problemas de seguridad cuando el código no está preparado para ello.

Supongamos que register_globals está activada. Si tuviésemos un script PHP que recibiera una contraseña de acceso para autorizar un usuario, algo como este script.php. El código es el siguiente, (obviando algo de HTML para no alargarnos, pues en el propio ejemplo se pone el código completo):

<?php
/* Ejemplo para ver el funcionamiento de register_globals
 * Andrés de la Paz © 2010
 * http://www.wextensible.com
 */
//Vemos si tiene magic_quotes activado el servidor
$magicq = (get_magic_quotes_gpc() == 1);
//Iniciamos las variables que contendrán los valores enviados por el usuario
$un_usuario = "";
$una_contrasenya = "";
//Iniciamos recogiendo los POST enviados por el usuario
foreach($_POST as $campo=>$valor){
    switch ($campo) {
        case "usuario":
            $un_usuario = htmlspecialchars($valor, ENT_QUOTES);
            if ($magicq) $un_usuario = stripslashes($un_usuario);           
            break;
        case "contrasenya":
            $una_contrasenya = htmlspecialchars($valor, ENT_QUOTES);
            if ($magicq) $una_contrasenya = stripslashes($una_contrasenya);           
            break;          
    }
}
//Para probarlo en su localhost, debe comentar esta línea para no inicializar
//la variable y así probar el ejemplo con register_globals = Off
//$autenticado = false;
//Esto autenticará el usuario
if (($un_usuario != "")&&($una_contrasenya != "")
    &&($un_usuario == "user") && ($una_contrasenya == "1")){
    $autenticado = true;
}
//Ahora componemos la página de salida
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
<head>
    <title>Respuesta</title>
    ...
</head>
<body>
    ...
    <?php if (!$autenticado) { ?>
        <h3>Formulario para la autenticación</h3>    
        <form action="<?php echo $_SERVER["PHP_SELF"]; ?>"  method="post">
            Nombre usuario: <input type="text" name="usuario" value="" /><br />
            Contraseña: <input type="text" name="contrasenya" value="" /><br />
            <input type="submit" value="enviar" />
        </form>   
    <?php } else { ?>
        <h3>Usuario autenticado</h3>
        <p>Ha entrado con su nombre de usuario <?php echo $un_usuario; ?> y 
        contraseña <?php echo $una_contrasenya; ?>.</p>
    <?php } ?>
</body>
</html>

Ejecutándolo desde este sitio la línea de código resaltada en amarillo estará sin comentar. Este es el punto más importante, pues estamos declarando explícitamente todas las variables que usamos en el script. Por lo tanto con el ejemplo en línea no podrá ver los efectos. Para ejecutar este ejemplo en nuestro servidor local tendrá que aparecer esa línea comentada, luego vamos al php.ini y ponemos la configuración register_globals = On. Después reiniciamos el servidor y ejecutamos este ejemplo, saliendo algo como esta captura de pantalla:

register globals

Vemos que register_globals está activado y no existe la variable $autenticado pues no se ha inicializado previamente. En el localhost aparecerán los mensajes de error (Notice:) pues nos interesa que salgan al estar en proceso de diseño, mensajes que conviene que no se muestren cuando el script esté ya en el sitio definitivo. Estos mensajes también se configuran en php.ini. En este caso está advirtiendo que no se ha definido la variable $autenticado, pero el script sigue volcando el resto de elementos de la página. Ahora podemos pasar un parámetro por URL con el mismo nombre que la variable:

register globals

El script toma ese párametro y como register_globals está activado lo asigna a una variable con ese nombre: $autenticado. Como no hemos inicializado esa variable, ella toma el valor que le viene por URL. Por lo tanto es necesario que register_globals esté desactivado, pues en ese caso esto no sucederá. Aún así se recomienda inicializar siempre las variables pues así reescribiremos cualquier valor que haya entrado previamente de alguna forma, aparte de ser una práctica altamente recomendada en programación en cualquier caso.