Esté atento: funciones de PHP y WordPress que pueden hacer que su sitio sea inseguro
Publicado: 2022-03-10La seguridad de un sitio web de WordPress (o cualquier otro) es un problema multifacético. El paso más importante que cualquiera puede tomar para asegurarse de que un sitio sea seguro es tener en cuenta que ningún proceso o método por sí solo es suficiente para garantizar que no suceda nada malo. Pero hay cosas que puede hacer para ayudar. Uno de ellos es estar atento, en el código que escribes y en el código de otros que implementas, para funciones que pueden tener consecuencias negativas. En este artículo, cubriremos exactamente esas: funciones que un desarrollador de WordPress debe pensar claramente antes de usar.
WordPress en sí proporciona una biblioteca considerable de funciones, algunas de las cuales pueden ser peligrosas. Más allá de eso, hay muchas funciones de PHP que un desarrollador de WordPress (PHP) usará con cierta frecuencia que pueden ser peligrosas cuando se usan.
Todas estas funciones tienen usos legítimos y seguros, pero también son funciones que pueden facilitar que se abuse de su código para malos fines. Cubriremos las cosas que probablemente sean la causa de una vulnerabilidad de seguridad y por qué debe estar atento a ellas. Primero analizaremos las funciones de PHP en su código que los malhechores pueden usar para hacer el mal, y luego hablaremos sobre las funciones de PHP específicas de WordPress que también pueden causar dolores de cabeza.
Funciones de PHP a tener en cuenta
Como dijimos, PHP contiene muchas funciones fáciles de usar. Algunas de esas funciones son notorias por la facilidad con la que las personas pueden hacer cosas malas con ellas. Todas estas funciones tienen un uso adecuado, pero tenga cuidado con cómo las usa y cómo lo hace otro código que incorpora a un proyecto.
Vamos a suponer que ya tiene comillas mágicas y registrar globales desactivados si su versión de PHP los admite. Estaban desactivados de forma predeterminada en PHP 5 y posteriores, pero se podían activar para versiones inferiores a 5.4. Pocos hosts lo permitieron, pero no creo que un artículo de seguridad de PHP esté realmente completo sin incluirlos.
O una mención de que PHP 5.6 es la última versión 5.x aún con soporte de seguridad continuo. Deberías estar en eso, al menos. Y debe tener un plan para pasar a PHP 7 antes de que finalice el soporte de 5.6 a fines de 2018.
Después de eso, solo tendrá que lidiar con algunas funciones riesgosas. Empezando con…
extract
es una mala noticia, especialmente en $_POST
o similar
Afortunadamente, el uso de la función de extract
de PHP ha caído en desuso. El núcleo de su uso era que podía ejecutarlo en una matriz de datos, y sus pares clave-valor se convertirían en variables vivas en su código. Entonces
$arr = array( 'red' => 5 ); extract($arr); echo $red; // 5
trabajaría. Esto es genial, pero también es realmente peligroso si está extrayendo $_GET
, $_POST
, etc. En esos casos, básicamente está recreando el problema de register_globals
usted mismo: un atacante externo puede cambiar los valores de sus variables fácilmente agregando una cadena de consulta o campo de formulario. La mejor solución es simple: no use extract
.
Si crea la matriz que extrae usted mismo, no es específicamente un problema de seguridad usar extract
y, en algunos casos, puede ser útil. Pero todos sus usos tienen el problema de confundir a los futuros lectores. Crear una matriz y llamar a extract
es más confuso que simplemente declarar sus variables. Así que te animo a que hagas declaraciones manuales en su lugar, a menos que sea completamente inviable.
Si debe usar la extract
en la entrada del usuario, siempre debe usar el indicador EXTR_SKIP
. Esto todavía tiene el problema de la confusión, pero elimina la posibilidad de que un intruso malicioso pueda cambiar sus valores preestablecidos a través de una cadena de consulta simple o una modificación del formulario web. (Porque "salta" los valores ya establecidos).
“ eval
es malvado” porque el código arbitrario da miedo
eval
en PHP y casi cualquier otro idioma que lo lleve siempre ocupa un lugar destacado en listas como esta. Y por una buena razón. La documentación oficial de PHP en PHP.net lo dice con bastante franqueza:
PRECAUCIÓN : La construcción del lenguaje eval() es muy peligrosa porque permite la ejecución de código PHP arbitrario. Por lo tanto, se desaconseja su uso. Si ha verificado cuidadosamente que no hay otra opción que usar esta construcción, preste especial atención a no pasar ningún dato proporcionado por el usuario sin validarlo correctamente de antemano.
eval
permite que cualquier cadena arbitraria en su programa se ejecute como si fuera un código PHP. Eso significa que es útil para la "metaprogramación" en la que está construyendo un programa que puede construir un programa en sí mismo. También es realmente peligroso porque si alguna vez permite que fuentes arbitrarias (como un cuadro de texto en una página web) se pasen inmediatamente a su evaluador de cadenas de eval
, de repente ha hecho que sea trivialmente fácil para un atacante malintencionado hacer prácticamente cualquier cosa que PHP pueda hacer. hacer en su servidor. Esto incluye, obviamente, conectarse a la base de datos, eliminar archivos y casi cualquier otra cosa que alguien pueda hacer cuando se conecta a una máquina mediante SSH. Esto es malo.
Si debe usar eval
en su programa, debe hacer todo lo posible para asegurarse de no permitir que se le pasen entradas arbitrarias de usuarios. Y si debe permitir que usuarios arbitrarios accedan a las entradas para eval
, limite lo que pueden hacer a través de una lista negra de comandos que no les permite ejecutar, o (mejor, pero mucho más difícil de implementar) una lista blanca que es solo los comandos que usted considerar seguro. Mejor aún, solo permita una pequeña cantidad de cambios de parámetros específicos, como solo números enteros validados.
Pero siempre tenga en cuenta esta línea de Rasmus Lerdorf, el fundador de PHP:
"Si `eval()` es la respuesta, es casi seguro que estás haciendo la pregunta equivocada".
variaciones en eval
Hay, además del conocido eval
, una variedad de otras formas en que PHP históricamente ha admitido cadenas evaluadas como código. Los dos que son más relevantes para los desarrolladores de WordPress son preg_replace
con el modificador /e
y create_function
. Cada uno funciona un poco diferente, y preg_replace
después de PHP 5.5.0 no funciona en absoluto. (PHP 5.5 y anteriores ya no reciben actualizaciones de seguridad oficiales y, por lo tanto, es mejor no usarlos).
Los modificadores /e
en expresiones regulares también son "malvados"
Si está ejecutando PHP 5.4.x o inferior, querrá estar atento a las llamadas PHP preg_replace
que terminan con una e. Eso puede verse como:
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
PHP ofrece varias formas de "cercar" su expresión regular, pero lo principal que debe buscar es "e" como el último carácter del primer argumento de preg_replace
. Si está allí, en realidad está pasando todo el segmento encontrado como argumento a su PHP en línea y luego eval
. Esto tiene exactamente los mismos problemas que eval
si permite que la entrada del usuario entre en su función. El código de ejemplo se puede reemplazar usando preg_replace_callback
. La ventaja de esto es que ha escrito su función, por lo que es más difícil para un atacante cambiar lo que se evalúa. Así que escribirías lo anterior como:
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
Permitir que la entrada del usuario create_function
s también es malo...
PHP también tiene la función create_function
. Esto ahora está obsoleto en PHP 7.2, pero era muy similar a eval
y tenía los mismos inconvenientes básicos: permite que una cadena (el segundo argumento) se convierta en PHP ejecutable. Y tenía los mismos riesgos: es demasiado fácil para usted darle accidentalmente a un cracker inteligente la capacidad de hacer cualquier cosa en su servidor si no tiene cuidado.
Sin embargo, este, si está en PHP por encima de 5.3, es incluso más fácil de arreglar que preg_replace
. Simplemente puede crear su propia función anónima sin usar cadenas como intermediarios. Esto es más seguro y más legible, al menos a mis ojos.
assert
es también eval
-Like
assert
no es una función que veo que usan muchos desarrolladores de PHP, en WordPress o fuera de él. Su intención es para afirmaciones muy ligeras sobre las condiciones previas de su código. Pero también admite una operación de tipo eval
. Por esa razón, debe tener tanto cuidado con él como con eval
. Las aserciones basadas en cadenas (el corazón de por qué esto es malo) también están obsoletas en PHP 7.2, lo que significa que debería ser menos preocupante en el futuro.
Incluir archivos variables es una forma de permitir la ejecución de PHP no controlada
Hemos explorado bastante bien por qué eval
es malo, pero algo como include
o require($filename'.php')
puede, especialmente donde $filename
se establece a partir de un valor controlable por el usuario, ser una mala noticia similar. La razón es sutilmente diferente de eval
. Los nombres de archivos variables a menudo se usan para cosas como el enrutamiento simple de URL a archivo en aplicaciones PHP que no son de WordPress. Pero también puede verlos utilizados en WordPress.
El corazón del problema es que cuando include
o require
(o include_once
o require_once
) está haciendo que su secuencia de comandos ejecute el archivo incluido. Es, más o menos, eval
conceptualmente ese archivo, aunque rara vez lo pensamos de esa manera.
Si ha escrito todos los archivos que su variable puede include
y ha considerado lo que sucedería cuando lo haga, está bien. Pero son malas noticias si no ha considerado lo que hará include
ing password.php
o wp-config.php
. También son malas noticias si alguien pudiera agregar un archivo malicioso y luego ejecutar su include
(aunque en ese momento probablemente tenga problemas mayores).
Sin embargo, la solución a esto no es demasiado difícil: el código duro incluye cuando puede. Cuando no pueda, tenga una lista blanca (mejor) o una lista negra de archivos que se pueden incluir. Si los archivos están en la lista blanca (es decir, ha auditado lo que hace cuando se agregan), sabrá que está a salvo. Si no está en su lista blanca, su script no lo incluirá. Con ese simple ajuste, estás bastante seguro. Una lista blanca se vería así:
$white_list = ['db.php', filter.php', 'condense.php'] If (in_array($white_list, $file_to_include)) { include($file_to_include); }
Nunca pase la entrada del usuario a shell_exec
y variantes
Este es un grande. shell_exec
, system
, exec
y backticks en PHP permiten que el código que está ejecutando hable con el shell subyacente (generalmente Unix). Esto es similar a lo que hace que eval
sea peligroso pero duplicado. Duplicado porque si deja que la entrada del usuario pase aquí sin cuidado, el atacante ni siquiera está sujeto a las restricciones de PHP.
La capacidad de ejecutar comandos de shell desde PHP puede ser muy útil como desarrollador. Pero si alguna vez dejas entrar la entrada de un usuario, tiene la capacidad de obtener subrepticiamente muchos poderes peligrosos. Así que iría tan lejos como para decir que la entrada del usuario nunca debe pasarse a las funciones de tipo shell_exec
.
La mejor manera que se me ocurrió para manejar este tipo de situación, si estuviera tentado a implementarla, sería proporcionar a los usuarios acceso a un pequeño conjunto de comandos de shell predefinidos y seguros. Eso podría ser posible de asegurar. Pero incluso entonces te advierto que tengas mucho cuidado.
Ver para unserialize
; Ejecuta código automáticamente
La acción central de llamar a serialize
en un objeto PHP vivo, almacenar esos datos en algún lugar y luego usar ese valor almacenado más tarde para unserialize
ese objeto de nuevo a la vida es genial. También es razonablemente común, pero puede ser riesgoso. ¿Por qué arriesgado? Si la entrada a esa llamada de unserialize
no es completamente segura (digamos que está almacenada como una cookie en lugar de en su base de datos...), un atacante puede cambiar el estado interno de su objeto de una manera que haga que la llamada de unserialize
haga algo malo.
Este exploit es más esotérico y es menos probable que se note que un problema de eval
. Pero si usa una cookie como mecanismo de almacenamiento para datos serializados, no use serialize
para esos datos. Use algo como json_encode
y json_decode
. Con esos dos, PHP nunca ejecutará automáticamente ningún código.
La vulnerabilidad central aquí es que cuando PHP unserialize
una cadena en una clase, llama al método mágico __wakeup
en esa clase. Si se permite que la entrada de usuario no validada no se unserialized
, algo como una llamada a la base de datos o la eliminación de un archivo en el método __wakeup
podría apuntar potencialmente a una ubicación peligrosa o no deseada.
unserialize
es distinto de las vulnerabilidades eval
porque requiere el uso de métodos mágicos en los objetos. En lugar de crear su propio código, el atacante se ve obligado a abusar de sus métodos ya escritos en un objeto. Los métodos mágicos __destruct
y __toString
en los objetos también son riesgosos, como explica esta página Wiki de OWASP.
En general, está bien si no usa los __wakeup
, __destruct
o __toString
en sus clases. Pero debido a que más tarde puede ver a alguien agregarlos a una clase, es una buena idea nunca permitir que un usuario se acerque a sus llamadas para serialize
y unserialize
y pasar todos los datos públicos para ese tipo de uso a través de algo como JSON ( json_encode
y json_decode
) donde hay nunca es la ejecución automática de código.
Obtener URL con file_get_contents
es arriesgado
Una práctica común cuando se escribe rápidamente código PHP que tiene que llamar a una URL externa es buscar file_get_contents
. Es rápido, es fácil, pero no es súper seguro.
El problema con file_get_contents
es sutil, pero es bastante común que los hosts a veces configuren PHP para que ni siquiera le permita acceder a URL externas. Esto está destinado a protegerte.
El problema aquí es que file_get_contents
buscará páginas remotas por usted. Pero cuando hace eso, no verifica la integridad de la conexión del protocolo HTTPS. Lo que eso significa es que su secuencia de comandos podría ser víctima de un ataque de hombre en el medio que permitiría a un atacante poner prácticamente lo que quiera en el resultado de su página file_get_contents
.
Este es un ataque más esotérico. Pero para protegerme cuando escribo PHP moderno (basado en Composer), casi siempre uso Guzzle para envolver la API cURL más segura. En WordPress, es aún más fácil: usa wp_remote_get
. Funciona de manera mucho más consistente que file_get_contents
y por defecto verificará las conexiones SSL. (Puedes desactivarlo, pero, um, tal vez no...) Mejor aún, pero algo más molesto de lo que hablar, son wp_safe_remote_get
, etc. Funcionan de manera idéntica a las funciones sin safe_
en su nombre, pero harán Asegúrese de que no se produzcan redireccionamientos y reenvíos inseguros en el camino.
No confíe ciegamente en la validación de URL de filter_var
Este es un poco oscuro, así que gracias a Chris Weigman por explicarlo en esta charla de WordCamp. En general, filter_var
de PHP es una excelente manera de validar o desinfectar datos. (Aunque, no se confunda sobre lo que está tratando de hacer...)
El problema aquí es bastante específico: si está tratando de usarlo para asegurarse de que una URL sea segura, filter_var
no valida el protocolo. Eso no suele ser un problema, pero si está pasando la entrada del usuario a este método para la validación y está usando FILTER_VALIDATE_URL
, algo como javascript://comment%0aalert(1)
pasará. Es decir, este puede ser un muy buen vector para un ataque XSS básico en un lugar que no esperaba.
Para validar una URL, la función esc_url
de WordPress tendrá un impacto similar, pero solo deja pasar los protocolos permitidos. javascript
no está en la lista predeterminada, por lo que lo mantendría a salvo. Sin embargo, a diferencia de filter_var
, devolverá una cadena vacía (no falsa) para un protocolo no permitido que se le pasa.
Funciones específicas de WordPress para vigilar
Además de las funciones potencialmente vulnerables de Core-PHP, hay algunas funciones específicas de WordPress que pueden ser un poco complicadas. Algunas de estas son muy similares a la variedad de funciones peligrosas enumeradas anteriormente, algunas un poco diferentes.
WordPress se deserializa con maybe_unserialize
Este es probablemente obvio si lees lo anterior. En WordPress hay una función llamada maybe_unserialize
y, como supondrás, deserializa lo que se le pasa si es necesario.
No hay ninguna vulnerabilidad nueva que esto presente, el problema es simplemente que, al igual que la función principal de unserialize
, esta puede hacer que un objeto vulnerable sea explotado cuando no se serializa.
is_admin
no responde si un usuario es administrador!
Este es bastante simple, pero el nombre de la función es ambiguo, por lo que es probable que confunda a las personas o que se pierda si tiene prisa. Siempre debe verificar que un usuario que intenta realizar una acción en WordPress tenga los derechos y privilegios necesarios para realizar esa acción. Para eso, debe usar la función current_user_can
.
Pero, por error, podría pensar que is_admin
le dirá si el usuario actual es una cuenta de nivel de administrador y, por lo tanto, debería poder configurar una opción que usa su complemento. Esto es un error. Lo que hace is_admin
en WordPress es, en cambio, decirle si la carga de la página actual está en el lado de administración del sitio (frente al lado frontal). Por lo tanto, todos los usuarios que puedan acceder a una página de administración (como el "Panel de control") podrán potencialmente pasar esta verificación. Mientras recuerde que is_admin
se trata del tipo de página, no del usuario actual, estará bien.
add_query_arg()
no desinfecta las URL
Esto no es tan común, pero hubo una gran ola de actualizaciones en el ecosistema de WordPress hace unos años porque la documentación pública sobre estas funciones no era correcta. El problema central es que la función add_query_arg
(y su inversa remove_query_arg
) no desinfecta automáticamente la URL del sitio si no se le pasó una URL, y la gente pensó que sí. El Codex había engañado a muchos complementos y, como resultado, los usaban de manera insegura.
Lo principal que tenían que hacer diferente: desinfectar el resultado de una llamada a esta función antes de usarla. Si haces eso, estás realmente a salvo de los ataques XSS que pensabas que eran. Así que se ve algo como:
echo esc_url( add_query_arg( 'foo', 'bar' ) );
$wpdb->query()
está abierto a un ataque de inyección SQL
Si conoce la inyección de SQL, esto puede parecer una tontería, incluso superfluo para enumerar. Porque la cuestión es que de cualquier forma que acceda a una base de datos (por ejemplo, usando los controladores de base de datos mysqli
o PDO de PHP) para crear consultas de base de datos que permitan ataques de inyección SQL.
La razón por la que llamo específicamente a $wpdb->query
es que algunos otros métodos (como insert
, delete
, etc.) en $wpdb
se encargan de los ataques de inyección por usted. Además, si está acostumbrado a realizar consultas básicas de la base de datos de WordPress con WP_Query
o similar, no tendrá que considerar la inyección de SQL. Es por eso que lo llamo: para asegurarme de que comprende que es posible un ataque de inyección en la base de datos cuando intenta usar $wpdb
por primera vez para hacer su propia consulta.
¿Qué hacer? Use $wpdb->prepare()
y luego $wpdb->query()
. También querrá asegurarse de prepararse antes de otros métodos de "obtención" de $wpdb
como get_row()
y get_var()
. De lo contrario, Bobby Tables podría atraparte.
esc_sql
tampoco lo protege de la inyección SQL
Para la mayoría de los desarrolladores de WordPress, diría que esc_sql
no se registra con ningún significado, pero debería. Como acabamos de mencionar, debe usar wpdb->prepare()
antes de realizar cualquier consulta a la base de datos. Esto te mantendrá a salvo. Pero es tentador y comprensible que un desarrollador pueda buscar esc_sql
en su lugar. Y podrían esperar que fuera seguro.
El problema es que esc_sql
no tiene protecciones tan sólidas contra la inyección de SQL. Es realmente una versión glorificada de la función add_slashes
de PHP, que se le ha desaconsejado usar para proteger su base de datos durante años.
Hay más que hacer, pero este es un gran comienzo
La seguridad es mucho más que solo estar atento a las funciones en su código que los atacantes pueden usar indebidamente. Por ejemplo, no discutimos en profundidad la necesidad de validar y desinfectar todos los datos que recibe de los usuarios y escaparlos antes de colocarlos en una página web (aunque recientemente publiqué un artículo sobre ese tema, "Protegiendo su WordPress sitio contra un ataque de secuencias de comandos entre sitios”). Pero puede y debe usar esto como parte de una estrategia de seguridad más amplia.
Mantener esta lista de funciones fáciles de mal uso a su lado mientras realiza una inspección final antes de implementar un nuevo complemento en WordPress es una gran idea. Pero también querrá asegurarse de confiar en la seguridad de su alojamiento, asegurarse de que sus usuarios tengan buenas contraseñas y mucho más.
Lo principal que debe recordar acerca de la seguridad es que su eslabón más débil es el que importa. Las buenas prácticas en un área refuerzan los posibles puntos débiles en otra, pero nunca pueden corregirlo por completo. Pero tenga cuidado con estas funciones y tendrá una ventaja en la mayoría.