Skip to main content

5 formas de prevenir la inyección de código PHP

Escrito por
Headshot of DeveloperSteve Coochin

DeveloperSteve Coochin

social php security

29 de junio de 2021

0 minutos de lectura

Después de mi publicación anterior sobre cómo detectar vulnerabilidades de seguridad en PHP Composer, pensé que esta publicación podría ser útil para crear aplicaciones más seguras que prevengan la inyección de código PHP. Como desarrolladores, creamos aplicaciones para facilitarles la vida a los usuarios finales. Ya sea una aplicación de entretenimiento, laboral o de redes sociales, el objetivo final es proteger a las personas para quienes desarrollamos, incorporando la seguridad en el código. Adoptar prácticas de programación segura no solo ayuda a proteger a los usuarios finales, sino que también ahorra tiempo y costos de desarrollo al evitar tener que rehacer el trabajo.

¿Qué es la inyección de código?

La inyección de código es un ataque que introduce una carga de código malicioso a través de un vector de ataque vulnerable. Su objetivo es comprometer la integridad de la aplicación a la que se dirige. El atacante puede enviar código PHP ejecutable o JavaScript, que se ejecuta en el entorno de ejecución de la aplicación o en el navegador del usuario final. Es importante tener en cuenta que PHP no ofrece un mecanismo de escape específico para JavaScript, lo que también puede causar problemas. La vulnerabilidad se procesa en el entorno de ejecución de PHP y se ejecuta en el servidor o en el navegador de un usuario desprevenido.

Cómo prevenir la inyección de código en PHP

La seguridad del desarrollo comienza desde el código, con la adopción de una convención de programación segura. Presta atención a las entradas que se reciben y a cómo se procesan. Sanitiza todos los datos entrantes y ten en cuenta cómo se almacenan en tu aplicación.

Como regla fundamental, nunca se debe permitir la ejecución dinámica de código en una aplicación. Por ejemplo, evita usar funciones básicas de PHP como shell_exec() y exec() siempre que sea posible, ya que se ejecutan a nivel del sistema operativo.

Se recomienda ampliamente usar siempre una herramienta de análisis de seguridad de código abierto como Snyk Open Source, que analizará tu administrador de paquetes para identificar posibles vulnerabilidades en el uso de bibliotecas y dependencias. También he estado consultando a diario la Snyk Vulnerability Database de Composer para conocer las técnicas que se están usando. Las páginas de informes detallados te permiten rastrear las vulnerabilidades hasta los repositorios, a nivel de código, y explorar cómo se identificaron.

5 formas de prevenir la inyección de código al desarrollar aplicaciones PHP

  1. Evita usar exec(), shell_exec(), system() o passthru()

  2. Evita usar strip_tags() para sanitizar

  3. Serialización de código

  4. Usa un linter de seguridad para PHP

  5. Usa una herramienta SAST para identificar problemas de inyección de código

1. Evita usar exec(), shell_exec(), system() o passthru()

Como dice el dicho: «Aquí hay dragones». Como regla general, evita siempre que sea posible usar cualquier función que pueda llamar directamente al entorno operativo desde PHP. Desde la perspectiva de los vectores de ataque, esto abre muchas oportunidades para introducir todo tipo de problemas directamente en tu stack.

Tradicionalmente, se usaban funciones como exec(), shell_exec(), system(), y passthru() para comprimir o descomprimir archivos, crear tareas cron e incluso recorrer archivos y carpetas del sistema operativo. Todas se ejecutan sin ninguna sanitización de código integrada; ahí empiezan los problemas, sobre todo cuando se usan con entradas directas del usuario que no se han validado ni sanitizado.

PHP ofrece algunos operadores funcionales con escape de entradas integrado, en particular escapeshellcmd() y escapeshellarg(), que aplican cierto nivel de escape y sanitización a las entradas como parte de la llamada a la función. Hay formas más seguras de lograr la misma funcionalidad.

Desde PHP 7.4, el archivado se puede gestionar con la clase ZipArchive, activada con la opción --with-zip al compilar PHP. Sin embargo, hay que tener especial cuidado, ya que esto todavía podría permitir un ataque de recorrido de directorios.

Gestiona mediante programación las interacciones con el sistema de archivos y reduce las formas en que se puede interactuar directamente con él mediante entradas. PHP cuenta con funciones para archivos que puedes usar sin llamar directamente al sistema operativo. Por ejemplo, puedes obtener una lista de archivos en un directorio específico con la función scandir(), que devuelve un arreglo de archivos disponibles en un directorio para luego abrirlos o hacer referencia a ellos en el propio código. Recuerda tener cuidado de no acceder a archivos creados a partir de entradas expuestas y de usar con precaución algunas funciones relacionadas con los permisos de archivos, como chmod() y chown(). También es importante no confiar en el encabezado de contenido (que se puede falsificar) y validar el tipo de archivo.

Crea tareas cron dinámicamente con una biblioteca de Composer como cron. En realidad, esto se ejecuta mediante una tarea cron, que a su vez ejecuta sus propias tareas cron durante su ejecución. Aun así, debes tener cuidado con lo que se ejecuta, pero este enfoque permite crear procesos de forma más programática sin exponer el acceso al sistema operativo.

2. Evita usar strip_tags() para sanitizar

Siempre debes tener especial cuidado al sanitizar y procesar las entradas de los usuarios. El objetivo es aceptar entradas válidas y, luego, almacenarlas y procesarlas correctamente para que tu aplicación no quede expuesta a vulnerabilidades. Recuerda que las entradas son un vector de ataque abierto que permite a actores maliciosos interactuar con tu aplicación. Por eso, tómate el tiempo necesario para procesar siempre los datos de entrada de forma adecuada.

La función strip_tags() está diseñada principalmente para eliminar HTML y PHP de las entradas proporcionadas. Esto significa que la función considerará válidos elementos como JavaScript, SQL y otras entradas potencialmente maliciosas. htmlentities() es otra opción que se usa para sanitizar entradas y que también permite definir juegos de caracteres UTF. Ten en cuenta que tampoco sanitiza las entradas por completo.

También puedes reducir los datos con una función de expresiones regulares como preg_replace('/[^a-zA-Z0-9]+/', '', $text). Para juegos de caracteres UTF-8, esta función solo devuelve texto y números de la entrada. También debes tener cuidado con funciones como mb_strtolower() al limpiar entradas de usuario. En el pasado, hemos visto vulnerabilidades en algunas funciones de cadenas multibyte (mb_), como CVE-2020-7065, una vulnerabilidad de escritura fuera de límites causada por la codificación UTF-32LE. Esto puede provocar un desbordamiento de búfer en la pila y permitir la ejecución de código.

En su lugar, opta por usar algo como filter_var(), que valida y sanitiza según las opciones de filtro definidas. Por ejemplo, si pasas FILTER_SANITIZE_STRING y FILTER_FLAG_STRIP_HIGH a la función filter_var() junto con una entrada del usuario, se eliminarán todas las etiquetas HTML y los caracteres con un valor ASCII > 127.

También hay varias bibliotecas de Composer de uso común para sanitizar entradas. Una biblioteca como HTML purifier ofrece una sanitización compatible con HTML y una lista de elementos permitidos bastante completa, que puedes personalizar según el tipo de datos que quieras aceptar en tu aplicación.

3. Evita unserialize() en PHP

Esta sección podría ser fácilmente una publicación de blog completa por sí sola. Ha sido un tema muy debatido durante años, incluso entre los desarrolladores que trabajan en PHP. El manual de PHP destaca los peligros de usar la función unserialize(). En particular, indica que «no debe recibir entradas de usuario no confiables», ya que esto puede hacer que «se cargue y ejecute código».

Unserialize() está diseñada para convertir una clase en una cadena que se pueda almacenar y pasar a otras funciones, o guardar en caché para usarla más adelante. Por sí sola, parece relativamente inofensiva, hasta que empiezas a entender cómo funciona el código C subyacente al almacenar datos de unserialize() en la memoria, que es donde pueden surgir la mayoría de los problemas.

Se recomienda ampliamente usar un formato de datos estándar, como JSON, mediante json_decode() y json_encode(). Esto permite contar con un método seguro y sanitizado para enviar y recibir datos serializados del usuario.

4. Usa un linter de seguridad para PHP

Contar con las herramientas adecuadas en tu flujo de trabajo de desarrollo ayuda a crear código seguro y más funcional desde el principio. Los linters son una excelente forma de reducir errores y posibles problemas durante el desarrollo de aplicaciones PHP, así como de reducir las vulnerabilidades en el código fuente.

También se recomienda encarecidamente desactivar de forma predeterminada la visualización de errores en las configuraciones de PHP.ini. Desactivar error_reporting = E_ALL, ~E_NOTICE y ~E_WARNING elimina los mensajes de error que podrían usarse para identificar información sobre el entorno y la configuración de tu aplicación.

El propio lenguaje PHP incluye un linter que muestra mensajes de error muy detallados durante la validación. Puedes ejecutarlo desde la CLI (o como parte de los frameworks de pruebas) con el comando PHP -l, seguido del archivo que quieras revisar. La desventaja de usarlo como linter es que solo puede revisar un archivo a la vez, aunque puedes incluirlo en un bucle para iterar por varios archivos en una sola ejecución.

PHPlint es una opción sólida y más conocida para revisar varios archivos rápidamente. Puedes usar PHPLint desde la CLI o como una biblioteca instalada mediante Composer. También puedes ejecutarlo fácilmente en una imagen de Docker. PHPLint permite revisar código PHP 7 y PHP 8. Aunque su salida es bastante detallada, puede identificar problemas mediante varios procesos de linting a la vez.

Otro linter popular es PHP-Parrallel-Lint, que admite PHP 5.3 a PHP 8.0 y puede ejecutar varios procesos de linting más rápido de lo que tardas en escribir echo "hello world". PHP-Parrallel-Lint ofrece resultados más detallados que las opciones mencionadas antes, aunque por el momento todavía no admite una opción de CLI ni una solución de Docker lista para usar.

5. Usa una herramienta de pruebas de seguridad de aplicaciones estáticas para identificar problemas de inyección de código

PHPStorm es uno de los IDE más usados para el desarrollo en PHP e incluye funciones muy útiles de análisis estático de código. Además de los linters que vimos antes, PHPStorm ofrece integración con Xdebug, ideal para aplicar linting, crear perfiles y, como su nombre lo indica, depurar código para reducir errores. Para los flujos de trabajo de programación segura, PHPStorm permite aplicar reglas de codificación y estándares PSR específicos. Esto resulta especialmente útil al trabajar en aplicaciones que cumplen con PCI.

Para mejorar la seguridad del código, Snyk tiene un complemento para PHPStorm que te ayuda a detectar problemas de calidad y vulnerabilidades en el código y las bibliotecas que usas. Es fácil de instalar y configurar, y está disponible en la sección de complementos de la aplicación.

Marketplace de plugins de PhpStorm con resultados de búsqueda de Snyk Vulnerability Scanner y su descripción de vulnerabilidad de seguridad

Otra herramienta de desarrollo que cada vez más desarrolladores usan, en particular quienes trabajan con PHP, es VSCode. Al igual que PHPStorm, también incluye un linter y permite aplicar los estándares PSR.

Analizar vulnerabilidades en VSCode con Snyk es tan sencillo como abrir una terminal nueva de VSCode y usar Snyk CLI para ejecutar snyk test o usar snyk monitor y recibir alertas continuas sobre tu base de código. Para empezar a usar Snyk CLI con PHP, consulta mi publicación anterior sobre cómo detectar vulnerabilidades de seguridad en PHP Composer con Snyk.

Actualización: También lanzamos recientemente la compatibilidad de Snyk Code con PHP. Para obtener más información y probarla, consulta la publicación del blog La compatibilidad de Snyk Code con el análisis de vulnerabilidades en PHP entra en fase beta.

Resumen

Como desarrolladores, creamos aplicaciones para ayudar a los usuarios finales en su vida cotidiana. Mantenerlas seguras no solo garantiza que sus datos estén protegidos, sino que también les permite confiar en ti.

Es fácil identificar vulnerabilidades de inyección de código mientras desarrollas aplicaciones, siempre que cuentes desde el principio con las herramientas y el flujo de trabajo adecuados.

Comienza con Capture the Flag

Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.

Leer más

blog feature snyk container custom base recommendations
Blog

Presentamos Evo Agentic AppSec: seguridad al ritmo del desarrollo con IA

El desarrollo impulsado por IA supera el ritmo de los flujos de trabajo de seguridad tradicionales. Agentic AppSec de Snyk usa agentes de seguridad para encontrar, corregir y verificar vulnerabilidades de forma continua, mientras las personas definen el estándar.

blog feature ai violet
Blog

VulnBump: ¿Pueden los agentes de IA corregir dependencias vulnerables sin romper la compilación?

VulnBump prueba agentes de IA en 307 actualizaciones reales de dependencias. Una corrección cuenta solo si elimina la vulnerabilidad y mantiene en verde la compilación y las pruebas.

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.