Búsqueda de Ciberamenazas

PHP corrige un fallo de seguridad en el código de validación de entradas

Si estás usando PHP en tu red, comprueba que estás usando la última versión, actualmente la 8.1.3. Publicada el 17/2/2022, esta versión corrige varios errores de gestión de la memoria, incluido CVE-2021-21708, que es una vulnerabilidad use-after-free en una función llamada php_filter_float().

(Las versiones 8.0 y 7.4 aún son compatibles y también son vulnerables; si no estás utilizando la versión más reciente de PHP (8.1), necesitas 8.0.16 y 7.4.28 respectivamente).

Un exploit de prueba de concepto basado en el uso de PHP para consultar una base de datos muestra que el error se puede usar para bloquear el proceso de PHP, por lo que ya se sabe que es posible un ataque de denegación de servicio (DoS).

Por supuesto, como a Mozilla le gusta señalar de manera rutinaria e inquebrantable en sus actualizaciones periódicas, cuando se corrigen errores que muestran evidencia de corrupción de la memoria, se debe “asumir que con suficiente esfuerzo algunos podrían haber sido explotados para ejecutar código arbitrario”.

La ejecución remota de código (RCE), en la que los datos enviados desde el exterior no solo bloquean un programa en tu ordenador, sino que también pueden obtener el control del mismo, generalmente conduce a la intrusión en la red, la exfiltración de datos, la implantación de malware o a un desagradable cóctel de todos ellos.

Código de validación inválido

Irónicamente, las funciones de filtro de PHP están diseñadas para validar los datos entrantes, por ejemplo, para asegurarse de que si esperas que alguien te envíe un número entero (por ejemplo: 5, 7, 11), no te han enviado una cadena de texto que no se puede convertir de forma fiable en un número entero, como 3,14159 o 3/16 de pulgada.

El error CVE-2021-21708 es parte del código que verifica los números de punto flotante, un término utilizado para lo que probablemente llamas “números reales” o “decimales”.

Los decimales suelen tener un punto (o una coma, según el país) que separa la parte entera de la parte fraccionaria, como en 2,5 para representar dos y cinco décimos o dos y medio.

Las funciones de filtro numérico de PHP permiten verificar no solo que el número entrante sea válido, sino también que esté dentro de un rango específico, como asegurarse de que no sea mayor a 2,71828 o que esté entre -1 y 1.

Si el número entrante ya es un número de punto flotante (un decimal), entonces se utiliza el código que se muestra a continuación, donde el antiguo código PHP (8.1.2) está a la izquierda y el nuevo código (8.1.3) está en la derecha. (No hay ningún error aquí, por lo que las dos versiones son idénticas).

No te preocupes si no sabes C; lo importante a tener en cuenta es que la verificación de errores se realiza primero, seguida de una línea que libera la memoria que PHP usa para almacenar el número, seguida inmediatamente de una línea que reasigna la memoria para que la use PHP.

En caso de que te lo estés preguntando, el curioso nombre zval_ptr_dtor() es una abreviatura de destructor de puntero de memoria interna de PHP:

Izquierda. PHP 8.1.2. Derecha. PHP 8.1.3. Ambos lados siguen el enfoque de “Mira, entra en la carretera, cruza”.

Si el número es un número entero, sin parte decimal, se usa un código ligeramente diferente.

A continuación, como puedes ver, la secuencia de “hacer el chequeo y salir si falla; pero si está bien, desasignar y reasignar almacenamiento para el número” se mezcló en la versión anterior.

La secuencia fue “desasignar la memoria utilizada por este valor de PHP, luego hacer la verificación y salir si falla, dejando atrás un objeto de PHP que se refiere a la memoria que pronto se asignará a otra cosa y por lo tanto causará un conflicto use-after-free; pero si la comprobación está bien entonces reasignar nuevo almacenamiento para el número”.

Eso es un poco como entrar primero en la carretera y solo luego comprobar si es seguro y completar el cruce.

El código actualizado en la versión 8.1.3 ha restaurado el código a una secuencia más segura, aunque sería aún más seguro si hubiera una sola función llamada, por ejemplo, dtor_and_alloc_in_one_go(), para que los futuros programadores no pudieran volver a insertar accidentalmente el código entre la llamada al destructor y la llamada al asignador.

El nuevo código es más como comprobar primero si la carretera es segura, luego entrar en ella y caminar directamente hacia el otro lado.

La vista “diff” (jerga para la diferencia de código) creada por Visual Studio Code muestra claramente cómo la línea marcada en rojo en la versión 8.1.2 se movió hacia abajo al punto verde en la versión 8.1.3:

Izquierda: PHP 8.1.2. ‘Entra en la carretera, mira, cruza’. Derecha: PHP 8.1.3. ‘Mira, entra en el camino, cruza’.

¿Qué hacer?

  • Si eres usuario de PHP, actualiza a 8.1.3. Si aún no has cambiado a la versión 8.1 de PHP, todavía se admiten otras dos versiones anteriores: la 8.0 necesita actualizarse a 8.0.16 y la 7.4 necesita actualizarse a 7.4.28. Si estás utilizando una distribución de Linux que administra PHP por ti, verifica tu distribución para obtener más detalles.
  • Si eres programador, recuerda que el código escrito en C requiere que tengas cuidado con la memoria todo el tiempo y en todas partes, para que errores bien escondidos como este no pasen desapercibidos.
  • Si eres un programador, intente escribir tu código para reducir la cantidad de formas en que los codificadores que vienen detrás de ti pueden introducir errores.