Ese extraño código PHP en WordPress

Publicado en Blog · 17 junio 2019.
Tips programación WordPress

Nota: para seguir este post se presupone que dispones de unos conocimientos mínimos de programación PHP.

Si sabes de PHP, y has desarrollado alguna vez algo para WordPress, seguramente habrás visto el siguiente código( o similar ) en la parte superior de casi todos los archivos de este CMS:

Quizás hasta hayas leído que debes ponerlo siempre en los archivos de código para WordPress que desarrolles sin que nadie te explicara muy bien el porqué.

Lo primero que debes de saber es que no existe sólo para WordPress. También existe algo similar para el CMS Joomla, lo que cambia es que en vez de ABSPATH, se usa JEXEC. Este CMS partió de otro llamado Mambo, que usaba también el código pero con _VALID_MOS como constante. Si seguimos retrocediendo en el tiempo encontramos al primer CMS en qué se empezó a usar este código: PHP-Nuke( al que algunos consideran el primer CMS PHP ).

El flujo de ejecución de PHP-Nuke( y el de la mayoría de CMS/Framework hoy en día ), consistía en la carga secuencial de varios archivos que en conjunto daban la respuesta a la acción del usuario/visitante en la web. Es decir, imagínate en una web de aquella época, bajo el dominio example.net y con un PHP-Nuke. Cada vez que se cargaba la home, lo que se hacía era cargar una sucesión de archivos. Por ejemplo( no real, hipotético ): index.php => load_modules.php => modules.php En esta sucesión, primero se cargaba index.php. Index.php cargaba load_modules.php y este último cargaba modules.php.

No siempre se empezaba la cadena por “index.php”. Pues una persona podía saltarse fácilmente parte de este flujo llamando directamente a uno de los otros archivos php( http://example.net/load_modules.php o http://example.net/modules.php ) de la cadena y, la mar de las veces( como veremos más abajo ), esto era peligroso.

¿ Cómo se le dio solución ?. Gracias a códigos, que se ponían al principio de cada archivo, similar a este:

Básicamente lo que hacía este código en la cabecera de un archivo llamado “modules.php” es comprobar si el primer archivo en la cadena de ejecución es en el que tenía el código( “modules.php”). Si era así, se cortaba la ejecución con el siguiente mensaje: "You can't access this file directly…". Si no era ese valor, es que estábamos en el flujo principal normal y se seguía adelante.

Pero este código tenía algunas pegas: El código era diferente dependiendo el archivo dónde se ponía y, además, bajo ciertas circunstancias, PHP no le da un valor a $HTTP_SERVER_VARS['PHP_SELF']. ¿ Entonces ?, ¿ Qué hicieron ?. Cambiaron todos esos códigos basados en $HTTP_SERVER_VARS['PHP_SELF'], por este otro:

En este código, que ya por entonces era habitual encontrar cosas similares en la comunidad PHP, se comprobaba la existencia de una constante. Esta constante se creaba, y daba valor, en el primer archivo del flujo(“index.php” o “home.php” o similar) por lo que si no existía en algún otro de los archivos de la secuencia, es porque alguien se saltó el index.php y lanzó otro directamente.

Peligros de que alguien lance un archivo PHP directamente.

Seguro que llegado a este punto estarás pensado que romper la cadena de ejecución debe de ser lo más grave del mundo. Sin embargo, la verdad es que normalmente no es nada peligroso.

Como nos cuenta la wiki de la constante JEXEC de Joomla. El peligro puede ser que de un error PHP y la ruta a nuestros archivos queden expuestas. Cosa que si se tiene capada la salida de errores en servidor no debería de importarnos y, aunque no tuviéramos capados los errores, tampoco debería de ser un problema en sí mismo. Lo único es que estamos dando algunas pistas a un atacante, pero poco más.

Hay un error que no nos cuenta la wiki, y es que alguien podría lanzar archivos que contuvieran trozos de HTML( de vistas ), pudiéndose revelar parte de estos archivos. En la mayoría de los casos, esto tampoco debería de preocuparnos.

También puede pasar que un desarrollador bien por despiste, bien por falta de experiencia, meta código peligroso y sin dependencia externa. Es muy pero que muy raro, pero puede suceder. Es raro porque si se especificó que ese archivo fuera parte de una cadena es porque tiene dependencia externas. Es decir, usa clases o funciones o variables de otros archivos que son necesarias para su ejecución. Con lo que si lo lanzas directamente por url, ese scripts va a ocasionar errores( el primer caso que vimos ) al no encontrar sus dependencias y no seguirá su ejecución.

Entonces, ¿ por qué tanto añadir el código de marras si luego no hay apenas por lo que preocuparse ?. Pues, por lo siguiente que cuenta en la wiki de Joomla: “También evita la inyección accidental de variables a través de un ataque a register globals impidiendo que el archivo PHP suponga que esta dentro de la aplicación cuando realmente no lo es.”

Register globals

Desde el nacimiento de PHP, todas las variables a través de urls( GET ) o a traves de formularios( POST ) automáticamente se convertían en globales. Es decir, que si yo llamaba al archivo: download.php?filepath=/etc/passwd. En el archivo download.php( y en los dependientes de este ) se podía hacer un echo $filepath; y daba como resultado /etc/passwd.

Entonces, y siguiendo con el ejemplo, desde el mismo archivo download.php, no había forma de saber el origen de la variable $filepath. Es decir, no se podía determinar si la variable estaba disponible bien porque un archivo antes en el flujo la creó, o bien porque alguien la falseo por url( o por POST ). Eso creaba tremendo agujeros de seguridad. Vamos a verlo:

Suponte que este código fuera el contenido del archivo downlad.php.

Seguramente el desarrollador pensó que sería buena idea implementar un front controller. Esto es, hacer que todas las peticiones web fueran a través de un único archivo de entrada( index.php o home.php u cualquier otro ). Este archivo de entrada se encargaría de inicializar la session, cargar variables comunes, … y, finalmente, derivar la petición a un archivo( en nuestro caso download.php ) que hiciera el trabajo especifico( descargar un archivo ).

Sin embargo, bastaría con hacer una llamada mediante download.php?filepath=/etc/passwd,para saltarse la secuencia de ejecución planeada por el desarrollador. Ya que al lanzar esta url, PHP automáticamente crearía, como hemos visto, al inicio del script una variable global $filepath con valor /etc/passwd. Como resultado, el atacante se descargaría el archivo /etc/passwd del sistema. Casi ná.

Esto es la punta del iceberg, porque se podía hacer tremendas barbaridades con muy poco. Por ejemplo, en un simple código como el siguiente, que el programador hubiera dejado como script a medias:

un atacante podía ejecutar cualquier tipo de código, usando un ataque de tipo Remote File Inclusion. Esto es, se crea un archivo MiClase.class.php con el código que le diera la gana y, luego, llamando al script pasándole su dominio codigo_parecia_inutil.php?classes_path=https://trasweb.net, el ataque estaba hecho.

Otro ejemplo, en un archivo llamado borra_archivo.inc.php con el siguiente código:

se le podía llamar a ese archivo directamente haciendo algo como:borra_archivo.inc.php?filename=/etc/hosts para borrar el archivo /etc/hosts del sistema( sino tuviera permisos, podría borrar otros que sí ).

En un CMS como WordPress, en el que encima se usa variables globales internamente, esto era devastador. Sin embargo, usado la técnica de la constante, estos y otros scripts PHP estaban a salvo. Veamoslo con el último ejemplo:

Si alguien intentara hacer un borra_archivo.inc.php?filename=/etc/hosts, ahora la constante le bloquea el paso.

Tiene que usarse una constante, porque si se usara una variable, como es lógico, estaríamos en las mismas( un atacante la podría inyectar ).

Llegado a este punto, seguramente te esté preguntando que por qué PHP mantiene esta funcionalidad si es tan peligrosa. Además, si conoces otros lenguajes de scripting( JSP, Ruby, ... ), verás que no tienen nada igual( de hecho, por eso mismo tampoco usan la técnica de la constante ). Bueno, la verdad es que la gente de PHP viendo los problemas que había, decidieron añadir una directiva php.ini llamada “register_globals”( activa por defecto ) para poder desactivar este comportamiento.

Viendo que los problemas persistían, decidieron que no estuviera activa por defecto. Aún así los alojamientos la seguían activando( pues les preocupaba que los proyectos de sus clientes no funcionaran. Esto es así¡ porque mucho del código de aquella época no usaba las aconsejadas variables HTTP_*_VARS para acceder a las variables GET/POST/... que existían, sino que usaban las variables como hemos visto ).

Por tanto, viendo que la cosa no cambiaba, tomaron la decisión que les quedaba: eliminar esta funcionalidad en PHP 5.4 para evitar todos estos problemas. Así que hoy en día, códigos como los que hemos visto( y sin usar constante ), que suponían un peligro, ya no suponen nada( bueno, un advertencia/notice visible e inofensiva en el peor de los casos pero poco más ).

Uso hoy en día.

Hoy en día la técnica de la constante es muy usada. Lo triste, y el motivo que originó este post, es que parece que pocos saben el motivo real de porque la usan.

Parece, como otras buenas prácticas de antaño( como copiar los parámetros en una función a otras variables para evitar los peligros de las referencias en la llamada, los guiones bajo en variables privadas para distinguirlas, … ), hay quien lo sigue haciendo porque en su día alguien le contó que era buena práctica. Eso, a pesar que en la mayoría de los casos, hoy día no tiene sentido mantenerla.

No tiene mucho sentido mantener esta buena práctica de antaño porque:

  • Ya casi nadie usa PHP 5.4 por lo que ya no existe la funcionalidad de registrar variables GET/POST como variables globales de PHP. Como hemos visto, sin register globals, la ejecución de scripts aislados pasan a ser inofensivos.
  • Incluso con un PHP inferior a 5.4, el código que hoy en día se desarrolla está mejor diseñado y se suele meter en clases / funciones, con lo que es difícil intervenir en los códigos de desarrollo mediante variables externas( incluso para WordPress que trabaja con variables globales ).
  • Relativo al anterior: Se crean front-controllers con un mejor diseño. Es decir, ahora el inicio de la cadena también se encarga de la ejecución del código contenido en las clases/funciones. Si no se lanza el flujo desde el principio, no debería de ejecutarse otros códigos( aunque trates de cargar los archivos directamente ).
  • Porque hoy en día se suele hacer autocarga de las clases, con lo que, a no ser que seas un desarrollador novato, no hay includes/requires que puedan ser peligrosos( que hagan independiente el código de un archivo, o que puedan ocasionar ataques Remote File Inclusion y Local File Inclusion ).
  • Porque algunos CMS/Frameworks empezaron a separar código público( recursos ), de código privado( de programación ). Esto es muy bueno y no es que se hiciera por register globals y ahora no haga falta, no. Uno de los motivos por lo que lo hicieron es porque si PHP dejara de funcionar en el servidor, el código de los archivos PHP( usen la técnica de la constante o no ) podría ser expuesto.
  • Porque se hace un uso intensivo de url amigables. Esta técnica configura el servidor para forzar siempre un único punto de entrada de programación por más que se intente cargar archivos aislados.
  • Porque ahora los CMS/Frameworks, por defecto, capan la salida de errores. Así no habrá pista alguna a los atacantes, por si eso pudiera servir a sus fines( por ejemplo, de ayuda para otro tipo de ataques ).

OJO, que no tenga sentido por regla general no quiera decir que no lo vayas a usar nunca de ahora en adelante. Ser un desarrollador profesional implica analizar cada caso y determinar, si bajo el contexto especifico en el que está, es necesario o no. Eso deberías de hacerlo siempre, incluso para lo que te han dicho que son buenas/malas prácticas.

Si tienes dudas, aquí está mi consejo:

  • Ponlo SIEMPRE si piensas que tu código pueda ser usando con una versión de PHP inferior a 5.4.
  • No lo pongas si tu archivo sólo tiene la definición de una clase.
  • No lo pongas si tu archivo sólo tiene código de funciones.
  • No lo pongas si tu archivo sólo tiene html/css. A no ser que el html exponga algún tipo de información valiosa.
  • No lo pongas si es un archivo con sólo constantes.
  • Para todo lo demás, si tienes dudas, ponlo. No debería de ser dañino, pero todo código es un mundo( sobretodo si estás empezando ). Cuando tengas experiencia, la cosa cambiará.

Sigue aprendiendo...( o si aún no te convence lo que te digo )

¡ Compártelo !
Este sitio utiliza cookies propias y de terceros para mejorar tu experiencia con el sitio web. Al continuar con la navegación consideramos que acepta su uso.