Detección de ataques de Inyección de SQL
Recibí un alerta dos días antes de año nuevo. Fue poco antes de la media noche el 30 de diciembre de 2021. Era una alerta de una anomalía diaria relacionada al backend de la base de datos de un viejo sitio web, pero claramente un ataque.
Como se vio luego, este fue el primero de muchos intentos de ataques. Hubo tres más en enero de 2022, dos más en febrero, y uno más en agosto, octubre, y noviembre. Considerándolo todo, nueve ataques similares en el curso de un año.
Antecedentes
Usamos WordPress (un sistema de gestión de contenidos de código abierto) en muchos de nuestros sitios webs y siempre con un backend MySQL (la configuración más popular). El sitio web en questión era viejo, y debido a problemas de compatibilidad, no habíamos actualizado algunos de los componentes de software en un tiempo. Entonces cuando vi esta alerta, pareció altamente plausible que había una vulnerabilidad que derivó en un ataque exitoso.
Como luego resultó, esto era un falso positivo. La razón de este falso positivo fue que adicional a una vieja versión de WordPress y plugins, este sitio web también usaba una vieja versión de Core Audit. Pero más de esto luego.
Ninguno de nuestros sitios web contiene información confidencial, pero como mostrará este documento, proteger la base de datos de cualquier aplicación con Core Audit es un medio muy eficaz para detectar ataques y proteguer la aplicación.
La Anomalía
El alerta de anomalía tiene 168 líneas, y la primera línea fue esta:
SELECT * FROM wp_users WHERE user_login =
'') UNION ALL SELECT NULL-- HupP'
Mientras cada línea fue diferente, cada línea empezó con uno de estos:
SELECT * FROM wp_users WHERE user_login =''
SELECT * FROM wp_users
INNER JOIN wp_usermeta ON user_id = ID WHERE meta_key = '' AND user_login = ''
Lo que hizo claro que se trataba de un ataque fueron las muchas variaciones de SQLs que lucen como esto:
;SELECT SLEEP(9)#' LIMIT 9
) AND SLEEP(9) AND (\\\''=\\\''
WAITFOR DELAY \\\'' AND \\\''=\\\'' LIMIT 9
;SELECT SLEEP(9)#'
);SELECT PG_SLEEP(9)--'
;SELECT PG_SLEEP(9)--' LIMIT 9
);WAITFOR DELAY \\\''--'
;WAITFOR DELAY \\\''--' LIMIT 9
) AND 9=(SELECT 9 FROM PG_SLEEP(9))
UNION ALL SELECT NULL-- HupP'
) UNION ALL SELECT NULL-- HupP' LIMIT 9
UNION ALL SELECT NULL,NULL,NULL-- iIWs'
UNION ALL SELECT NULL,NULL,NULL,NULL-- ynbe'
Tener en cuenta que las cuerdas vacías (''
) y los 9s no son parte del SQL original. Ellos se relacionan en cómo la seguridad del repositorio de Core Audit opera. Este repositorio automáticamente colecciona todos los SQLs en la base de datos, entonces para reducir espacio y eliminar anomalías de literales embebidos, automaticamente elimina todos los números y cuerdas.
Los SQLs vistos anteriormente son el resultado de un atacante que intentó escanear la aplicación en búsqueda de una vulnerabilidad de inyección de SQL. Intentaban detectar si la inyección SQL era posible en esta aplicación y encontrar información adicional que facilitara el ataque:
- Cada instrucción SLEEP/DELAY solo funcionaría en un tipo de base de datos. Así que un retraso en la respuesta le dice al atacante que la inyección fue exitosa y el tipo de base de datos utilizada.
- Las diversas permutaciones UNION y NULL estaban tratando de determinar el número de campos en la query y si podrían agregar datos de otras tablas.
Adicional a muchas más variaciones de SLEEP y UNION, hubo expresiones llenas de color como:
) AND (SELECT 9 FROM(SELECT COUNT(*),CONCAT(0x7178707671,(SELECT (ELT(9=9,9))),0x7171767171,FLOOR(RAND(9)*9))x FROM INFORMATION_SCHEMA.CHARAC
) AND 9=CAST((CHR(9)||CHR(9)||CHR(9)||CHR(9)||CHR(9))||(SELECT (CASE WHEN (9=9) THEN 9 ELSE 9 END))::text||(CHR(9)||CHR(9)||CHR(9)||CHR(9)||C
) AND 9=CAST((CHR(9)||CHR(9)||CHR(9)||CHR(9)||CHR(9))||(SELECT (CASE WHEN (9=9) THEN 9 ELSE 9 END))::text||(CHR(9)||CHR(9)||CHR(9 )||CHR(9)||CHR(9)) AS NUMERIC) AND (\\\''= \\\''
;SELECT DBMS_PIPE.RECEIVE_MESSAGE(CHR(9)||CHR(9)||CHR(9)||CHR(9),9) FROM DUAL--'
AND 9=CONVERT(INT,(SELECT CHAR(9)+CHAR(9)+CHAR(9)+CHAR(9)+CHAR(9)+(SELECT (CASE WHEN (9=9) THEN CHAR(9) ELSE CHAR(9) END))+CHA R(9)+CHAR(9)+C
) AND (SELECT 9 FROM(SELECT COUNT(*),CONCAT(0x7178707671,(SELECT (ELT(9=9,9))),0x7171767171,FLOOR(RAND(9)*9))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a) AND (\\\''=\\\''
) AND 9=CONVERT(INT,(SELECT CHAR(9)+CHAR(9)+CHAR(9)+CHAR(9)+CHAR(9)+(SELECT (CASE WHEN (9=9) THEN CHAR(9) ELSE CHAR(9) END))+CHAR(9)+CHAR(9)+CHAR(9)+CHAR(9)+CHAR(9))) AND (\\\''=\\\''
Estos SQLs eran claramente inusuales y parecen intentar eludir un sistema de protección de inyección de SQL como un WAF.
Forences
Experimentamos un ataque. El alerta no deja ninguna duda sobre esto. Las dos preguntas remanentes fueron:
- ¿Fue exitoso el ataque?
- ¿Qué parte de la aplicación fue objetivo?
Base de datos forences
La primera pregunta fue fácil de responder. Empecé por localizar las queries en la vista forense de SQL reducida. El repositorio reducido de SQL tiene una resolución de 5 minutos, y todos estos SQLs fueron ejecutados en la ventana de 8:20 am a 8:25 am.
Una vez que localicé las queries, también vi las buenas noticias – todos fueron exitosos (sin errores), y los números de las filas recuperadas fueron cero para todas.
¿Por qué son buenas noticias las ejecuciones exitosas? Porque este ataque estaba tratando de testar si la aplicación era vulnerable a una inyección de SQL. En este escaneo, se supone que muchas de estas queries fallen, y pocas exitosas indicando el método para explotar una vulnerabilidad. Por ejemplo, los SQLs con PG_SLEEP()
nunca pueden ser exitosos en nuestra base de datos MySQL desde que esta función solo existe en base de datos PostgreSQL. Por lo tanto, ejecuciones exitosas significan el intento de inyección fallido de modificar la SQL.
Adicionalmente, estas SQLs no recuperan datos, entonces allí no hubo filtración. Mientras la mayoría de estos SQLs no estaban intentando extraer nada, es confortante saber que nada fue recuperado.
En otras palabras – este ataque no logró romper el límite literal. Volveremos a este asunto después y también responder la pregunta más interesante de porqué recibimos el alerta de anomalía en primer lugar.
Aplicaciones Forences
Ahora que tenemos más información, es fácil buscar los logs de Apache para más detalles en el ataque y a qué dirigió.
El ataque fue entre las 8:20 am y 8:25 am, y accedió a la tabla wp_users
. Mirando al log de Apache, podemos ver que este ataque empezó exactamente a las 8:20 am:
[29/Dec/2021:08:20:00] "GET /wp-login.php?log=1&pwd=-9696%20UNION%20ALL%20SELECT%2024%2C24--%20ptzf"
Este ataque fue una pregunta GET al script wp-login.php. Esta es la página de logueo de WordPress. El ataque entregó su intento de inyección a través del campo de contraseña que, en este primer intento, incluyó:
-9696 UNION ALL SELECT 24,24-- ptzf
El último intento de ataque fue usando este requerimiento POST a las 8:21:51 am:
[29/Dec/2021:08:21:51] "POST /wp-login.php?Phws%3D5963%20AND%201%3D1%20UNION%20ALL%20SELECT%201%2CNULL%2C%27%3Cscript%3Ealert%28%22XSS%22%29%3C%2Fscript%3E%27%2Ctable_name%20FROM%20information_schema.tables%20WHERE%202%3E1--%2F%2A%2A%2F%3B%20EXEC%20xp_cmdshell%28%27cat%20..%2F..%2F..%2Fetc%2Fpasswd%27%29%23"
Los logs de Apache no graban los parámetros POST, pero podemos ver esta carga en el requerimiento:
Phws=5963 AND 1=1 UNION ALL SELECT 1,NULL,'<script>alert("XSS")</script>',table_name FROM information_schema.tables WHERE 2>1--/**/; EXEC xp_cmdshell('cat ../../../etc/passwd')#
Esta carga útil es un poco graciosa dado que contiene una inyección de SQL con un escaneo de secuencias de comandos entre sitios (alert(“XSS”)) y un intento de que SQL Server ejecute un comando de shell (EXEC xp_cmdshell) con un comando Unix/Linux imprimiendo el contenido de un archivo de contraseña Unix/Linux (cat …/passwd).
Este es una mezcla de ataques fragmentados que podrían nunca funcionar juntos. Y hay muchas otras cosas mal con el último requerimiento.
Esto indica que la persona corriendo el ataque tiene poco entendimiento de lo que está haciendo. Combinada con el hecho de que todo el escaneo duró menos de 2 minutos, sugiere esto fue un script o, más probable, muchos scripts ellos descargaron fuera de Internet y ejecutaron contra varios sitios.
Falso Positivo
Entonces, ¿por qué falló el ataque?, ¿y por qué recibimos una alerta de anomalía de cualquier forma?
Un ataque de inyección de SQL intenta modificar la estructura SQL al quebrar a través de los límites literales. En otras palabras, cuando un SQL contiene un literal como este:
SELECT * FROM wp_users WHERE user_login = 'JOHN'
Un ataque de inyección de SQL intenta mandar una cadena otra que “JOHN
” entonces la estructura SQL cambia. Por ejemplo, la cadena “X' or 'Y'='Y
” resultará in este SQL:
SELECT * FROM wp_users WHERE user_login ='X' or 'Y'='Y'
Al cambiar el SQL estructura y agrega “or 'Y'='Y'
“, la base de datos correrá algo no intencionado por el desarrollador que escribió este código. Colocando una etiqueta ('
) en el aporte rompió a través de límites literales y permitió al atacante alterar la estructura SQL. Escapar los literales antes de incrustarlos en un SQL previene esta vulnerabilidad.
En el SQL estandard, puedes escapar etiquetas ('
) al usar etiquetas dobles (''
). Cuando se escapa, el ejemplo de más arriba producirá:
SELECT * FROM wp_users WHERE user_login = 'X'' or ''Y''=''Y'
En este caso, la base de datos comparará el campo user_login
a la cadena entera, con la palabra OR
es solo parte de un nombre de usuario, no parte de una estructura SQL.
WordPress debe haber escapado de la entrada correctamente en nuestro ataque, y la estructura SQL no fue modificada. Es por esto que los SQLs se ejecutaron sin error, y el ataque falló.
Pero, ¿por qué recibimos un alerta de anomalía si el ataque no quebró los límites literales?
A diferencia de otras bases de datos, en MySQL, hay 2 formas de escapar de las etiquetas en las cadenas. La primera es con etiquetas dobles (''
) de acuerdo con el SQL estándard. Pero hay un segundo método de preceder la etiqueta con un backslash (\'
). WordPress usa el segundo método.
Acá es donde la vieja versión de Core Audit entra a jugar. Aquella vieja versión fue liberada cortamente luego de la liberación del soporte de MySQL, y la eliminación literal en el repositorio de SQL reducido no admitía el método de escape de barra invertida para bases de datos MySQL.
Una vez que descubrimos el consecuente no intencionado de detectar falsamante estos ataques de inyección de SQL, nosotros mantenemos a propósito la vieja versión de Core Audit en ese sitio web. Queríamos ver cómo muchos más ataques fallido experimentaríamos. Como comentamos antes, este viejo sitio web experimentó nueve intentos a lo largo del siguiente año. Una vez que actualizamos la versión de Core Audit, paramos de recibir estas alertas de falso positivo.
Area de superficie de ataque de la aplicación
Tal vez te preguntes por qué alguien intentó un ataque de inyección de SQL que no funcionó contra WordPress. Los atacantes, como cualquier otro, tenían acceso a WordPress. Entonces, ¿por qué no sabían que este ataque fallaría?
Esta es una pregunta interesante que resalta los complejidad de la cadena de suministro en algunas aplicaciones de terceros, como WordPress.
El primer pensamiento que viene a mi mente es que quizás algunas versiones de WordPress son susceptibles de ataque. Como sea, la inyección de SQL es una amenaza bien conocida, y el equipo de desarrollo de WordPress es estricto sobre escapando entradas antes de literales incrustados en SQLs. Mientras usar variables incrustadas sería más seguro, desarrolladores web tienen una inclinación por literales embebidos.
Como sea, es mundialmente conocido que múltiples versiones y parches significativamente aumentan el área de la superficie de ataque. La razón es que muchas organizaciones actualizan y emparchan aplicaciones de tanto en tanto y nunca pueden estar seguras qué vulnerabilidades eliminaron o introdujeron y en qué punto.
Pero desde que es poco probable que ninguna versión de WordPress fue susceptible a una inyección de SQL en la pantalla de login, que lleva otra interesante característica de WordPress – Plugins.
Una gran parte del poder y flexibilidad de WordPress son más de decenas de miles de plugins que pueden extenderlo. Estos plugins son código que puede adjuntarse a varios lugares en WordPress y modificar su comportamiento en casi cualquier manera inimaginable.
Plugins ofrece poder y flexibilidad a los usuarios, pero también muestran un riesgo de seguridad. WordPress plugins introducen proveedores adicionales, códigos estándar, cambios al modelo de la base de datos, nuevos caminos en la ejecución de la aplicación, y, por lo tanto, nuevas vulnerabilidades.
El riesgo en plugins no es solo vulnerabilidades en software de terceros pero también el riesgo de ataques en la cadena de suministro. Estos ataques pueden ser iniciados por el autor del plugin o por un hacker que alteró el código fuente.
Es poco probable que WordPress fuera susceptible de este ataque, pero es altamente plausible que algunos de los plugins lo sean. El atacante estaba apuntando a un plugin que no fue instalado en nuestro WordPress.
Ideas finales
Las inyecciones de SQL son notoriamente difíciles de detectar y, aún más, de prevenir. Como sea, el análisis de anomalías de Core Audit fue capaz de alertar sobre estos ataques.
No solo fue un análisis de anomalías capaz de detectar un ataque, pero se hizo sin ninguna firma de ataque o soporte para PHP o WordPress. Y, realmente, sin siquiera buscar un ataque de inyección de SQL.
Y esta es la razón por la que el análisis de anomalías es tan efectivo contra la inyección de SQL – no es buscando especialmente por esto. Es buscando por SQLs que son nuevos a la aplicación, por lo tanto, sospechosos. La inyección de SQL puede mascarar en muchas formas pero, por definición, no es parte del vocabulario SQL de la aplicación. Por lo tanto, el análisis de anomalías siempre detectará el ataque.