Cómo manejar correctamente los tokens de suplantación de Windows — préstamo de privilegios por hilo y una forma segura de revertirlos

· Actualizado el: · · Windows, Security, AccessToken, Impersonation, Win32, .NET, CSharp, Operación, Aprovechamiento de activos existentes

1. Lo primero que hay que tener claro

Cuando se crea una aplicación o un servicio de Windows, surge la necesidad de “ejecutar solo esta parte del procesamiento como otro usuario”.

Por ejemplo, en situaciones como estas:

  • Un servicio de Windows necesita acceder a un servidor de archivos con los privilegios del propio usuario
  • Una aplicación de administración necesita comprobar únicamente lo que un usuario concreto puede ver
  • En Named Pipe, RPC, COM, IIS, ASP.NET Core, etc., se necesita realizar parte del procesamiento con los privilegios del usuario que hizo la llamada
  • Por motivos de aprovechamiento de activos existentes, se necesita cambiar la cuenta de Windows utilizada según la unidad de procesamiento

Aquí es donde aparecen la suplantación, el token de acceso y el token de suplantación.

Sin embargo, antes de nada quiero subrayar lo siguiente.

La suplantación en Windows no es “una magia para convertirse en administrador”. Es un mecanismo que cambia, principalmente por hilo, el contexto de seguridad que se usa en las comprobaciones de acceso.

Si se implementa sin tener clara esta diferencia, aparecen problemas como estos:

  • Se cree haber suplantado, pero el acceso a archivos devuelve Access denied
  • Se pueden leer los archivos locales, pero solo falla el recurso compartido de red
  • En algún punto de Task.Run o de un async, sin darse cuenta se vuelve a ejecutar como el usuario original
  • La suplantación sigue activa durante el registro de logs o el procesamiento posterior, y el límite de privilegios se vuelve ambiguo
  • Se confunde el token primario con el token de suplantación y falla el inicio del proceso
  • Se llega a un punto muerto con “el usuario está en Administrators, entonces por qué no puede escribir”

En este artículo se ordenan las ideas necesarias para manejar con seguridad, en el trabajo real, los tokens de suplantación de Windows.

No es un artículo sobre técnicas de ataque ni sobre apropiación de privilegios. Es un artículo sobre cómo manejar correctamente los límites de privilegios en aplicaciones de Windows, servicios de Windows y aplicaciones .NET.

El código que aparece en este artículo se publica en GitHub como un conjunto de ejemplos que se puede compilar (una biblioteca, una demo para ejecutar en Windows y pruebas unitarias de validación de argumentos y de comportamiento de las protecciones).

windows-impersonation-token - komurasoft-blog-samples (GitHub)

2. Qué es un token de acceso

En Windows, el token de acceso se usa para representar el contexto de seguridad de un usuario o de un proceso.

El token de acceso incluye, a grandes rasgos, esta información. Como son términos que aparecen repetidamente en los capítulos siguientes, se añade el significado línea por línea.

Elemento Significado
SID del usuario SID es la abreviatura de Security Identifier (identificador de seguridad), un identificador único de un usuario o un grupo. Las comprobaciones de acceso se hacen con este valor, no con el nombre que se muestra
Grupos a los que pertenece El conjunto de SID de los grupos a los que pertenece el usuario. Aquí también se refleja si pertenece a Administrators
Privilegios (Privilege) Privilegios como “operador de copia de seguridad” o “apagar el sistema”, que se otorgan aparte de la ACL de cada objeto individual. Tienen un estado habilitado o deshabilitado
Propietario predeterminado El SID que será el propietario de los objetos nuevos creados con este token
DACL predeterminada DACL es la abreviatura de Discretionary Access Control List (lista de control de acceso discrecional), una lista que indica a quién se le permite qué. Se asigna por defecto a los objetos recién creados
SID restringidos La lista de SID que se usa en los tokens restringidos. Sobre esta lista también se realiza una comprobación de acceso adicional, por lo que puede denegarse el acceso aunque se pertenezca al grupo
Nivel de integridad Una jerarquía como Low, Medium o High. Desde un nivel de integridad bajo no se puede escribir en objetos de un nivel más alto. Se trata en el capítulo 17
Estado de elevación En un entorno con UAC activado, indica si ese token está elevado o no. Se trata en el capítulo 17
Nivel de suplantación Un valor que solo tienen los tokens de suplantación. Determina si solo se puede identificar, si realmente se puede acceder, o si se puede delegar de forma remota. Se trata en el capítulo 7
Tipo de token La distinción entre token primario y token de suplantación. Se trata en el capítulo 6

Aquí conviene tener presente que lo que importa en la comprobación de acceso no es el nombre, sino el SID, los grupos y el nivel de integridad; esto facilita entender, en capítulos posteriores, por qué “el nombre es correcto pero aun así aparece Access denied”.

Muchos objetos de Windows —archivos, el registro, servicios, canalizaciones con nombre, procesos, hilos, eventos, mutexes, etc.— tienen un descriptor de seguridad.

Cuando un hilo intenta abrir un objeto protegido, Windows coteja la información del token con la ACL del objeto de destino.

Hilo que realiza la operación
  ↓
Con qué contexto de seguridad se accede
  ↓
Se examinan el usuario, los grupos y los privilegios del token
  ↓
Se coteja con la ACL del objeto de destino
  ↓
Se decide permitir / denegar

Comprender este “con qué contexto de seguridad se accede” es la puerta de entrada para entender los tokens de suplantación.

3. El proceso tiene un token primario

Cada proceso de Windows suele tener un token de acceso primario.

Por ejemplo, cuando un usuario inicia una aplicación desde el escritorio, ese proceso recibe un token primario que representa el contexto de seguridad de ese usuario.

En el caso de un servicio de Windows, se le asigna el token primario de la cuenta con la que se ejecuta el servicio.

Por ejemplo, algo así:

MyService.exe
  Primary Token: DOMAIN\svc-app

Si un hilo dentro de este servicio no está suplantando a nadie en particular, al acceder a archivos o al registro se usa el token primario del proceso.

Es decir, por defecto es así:

Thread A
  Impersonation Token: ninguno
  ↓
La comprobación de acceso usa el Primary Token del Process

En este estado, al abrir C:\Data\foo.txt, se comprueba si DOMAIN\svc-app tiene permiso de acceso.

4. El token de suplantación se asocia al hilo

Cuando comienza la suplantación, al hilo se le asocia un token de suplantación. Este es el punto importante: es más preciso pensar que la suplantación no hace que todo el proceso se convierta en otro usuario, sino que ese hilo en concreto recibe las comprobaciones de acceso bajo otro contexto de seguridad.

MyService.exe
  Primary Token: DOMAIN\svc-app

Thread A
  Impersonation Token: DOMAIN\alice

Thread B
  Impersonation Token: ninguno

En este caso, cuando Thread A abre un archivo, la comprobación de acceso se hace con los privilegios de DOMAIN\alice.

Por otro lado, Thread B no está suplantando a nadie, así que la comprobación de acceso se hace con los privilegios de DOMAIN\svc-app.

Si no se entiende esta diferencia, se produce esta clase de confusión:

// Se cree haber suplantado en Thread A
StartImpersonation(token);

// Pero el procesamiento se envía a otro hilo
Task.Run(() =>
{
    File.ReadAllText(path);
});

// Y se revierte enseguida
RevertToSelf();

En este caso, no hay garantía de que el hilo que realmente lee el archivo se ejecute en el estado de suplantación esperado.

La suplantación debe manejarse dejando claro su ámbito, su relación con el hilo y con el procesamiento asíncrono.

5. La “suplantación” no es una escalada de privilegios

La palabra suplantación suena un poco fuerte, pero lo importante en el trabajo real es no confundirla con una “escalada de privilegios”.

Básicamente, lo que permite hacer la suplantación es esto:

Procesar con los privilegios del proceso servidor
  ↓
Solo una parte del procesamiento recibe la comprobación de acceso con los privilegios del usuario cliente

Por ejemplo, si se quiere usar tal cual la ACL de un servidor de archivos como control de permisos, y la aplicación servidora siempre lee los archivos con la cuenta de servicio, no se puede reflejar la ACL específica de cada usuario.

Por eso, solo una parte del procesamiento de la solicitud se suplanta como el usuario que hizo la llamada, y así se realiza el acceso al archivo.

Solicitud HTTP / RPC / Named Pipe
  User: DOMAIN\alice
      ↓
Aplicación servidora
  Process: DOMAIN\svc-app
      ↓
Solo la parte de acceso a archivos se suplanta como DOMAIN\alice
      ↓
Se permite / deniega según la ACL del servidor de archivos

Esto resulta útil cuando se quiere aprovechar la ACL existente de Windows en lugar de una lógica de autorización propia de la aplicación.

Sin embargo, aunque la suplantación es útil, si el diseño es incorrecto el límite de privilegios se vuelve difícil de entender.

  • Qué procesamiento se ejecuta como quién
  • Dónde se inició la suplantación
  • Dónde se revierte con seguridad
  • Con qué privilegios de usuario se emite cada log
  • Si se revierte también en caso de excepción
  • Si la suplantación sigue vigente hasta el final del procesamiento asíncrono

Es importante dejar todo esto claro en el código.

6. Distinguir el token primario del token de suplantación

Entre los tokens de Windows, lo que más se confunde es el token primario y el token de suplantación.

A grandes rasgos, se puede pensar así:

Token Uso principal Ejemplo representativo
Token primario Representa el contexto de seguridad del proceso Inicio de procesos, CreateProcessAsUser
Token de suplantación Se usa para que un hilo se ejecute bajo otro contexto de seguridad ImpersonateLoggedOnUser, SetThreadToken, suplantación del cliente en Named Pipe

Es especialmente importante tener claro que para iniciar un proceso, en principio se necesita un token primario.

El hecho de disponer de un token de suplantación no significa que se pueda usar directamente para iniciar el proceso de otro usuario.

Normalmente, el flujo es este:

Se suplanta al cliente
  ↓
Se obtiene el token de suplantación con OpenThreadToken
  ↓
Se crea un token primario con DuplicateTokenEx
  ↓
Se pasa a CreateProcessAsUser, etc.

Al contrario, si lo que se quiere es “hacer solo el acceso a archivos de este hilo como otro usuario”, no se trata de iniciar un proceso, sino de usar un token de suplantación.

Si se mezclan estos dos conceptos, aunque los argumentos de la API sean correctos, se acaba perdido en errores como Access denied o The parameter is incorrect.

7. Entender el nivel de suplantación

El token de suplantación tiene un nivel de suplantación.

Hay, típicamente, 4 niveles.

Nivel de suplantación Significado aproximado
Anonymous El servidor no puede obtener información de identificación del cliente
Identification El servidor puede identificar al cliente, pero no puede usar ese nivel para acceder a objetos con sus privilegios
Impersonation El servidor puede actuar con los privilegios del cliente en el sistema local
Delegation El servidor puede delegar los privilegios del cliente también hacia sistemas remotos

En el trabajo real, lo que más suele atascar es la diferencia entre Identification e Impersonation.

Como su nombre indica, Identification es un nivel que sirve para saber quién es la otra parte. No es suficiente para usos como abrir un archivo con los privilegios de ese usuario.

Por eso ocurre lo siguiente:

WindowsIdentity.GetCurrent().Name muestra el nombre de usuario esperado
  ↓
Pero el acceso al archivo devuelve Access denied

En este caso, no basta con mirar solo el nombre; también hay que comprobar el nivel de suplantación.

En .NET, WindowsIdentity.ImpersonationLevel da una pista.

using System.Security.Principal;

WindowsIdentity identity = WindowsIdentity.GetCurrent();

Console.WriteLine(identity.Name);
Console.WriteLine(identity.ImpersonationLevel);

El acceso a través de la red requiere aún más atención.

En configuraciones donde “un servidor web suplanta a un usuario y, como ese usuario, accede a otro servidor de archivos o de base de datos”, se puede tropezar con el llamado problema de doble salto (double hop).

En este caso, no siempre basta con suplantar desde el código de la aplicación. Hay que diseñar teniendo en cuenta Kerberos, el SPN, la delegación, la delegación restringida, la cuenta de servicio y el método de autenticación del destino de conexión.

8. La forma básica de la suplantación

La forma conceptual de manejar la suplantación con la API Win32 es esta:

1. Obtener el token que se usará para suplantar
2. Suplantar el hilo actual con ese token
3. Ejecutar solo el procesamiento necesario
4. Volver siempre al contexto de seguridad original
5. Cerrar el identificador del token

En forma de código, siempre debe ir en un try / finally.

if (!ImpersonateLoggedOnUser(tokenHandle))
{
    throw new Win32Exception(Marshal.GetLastWin32Error());
}

try
{
    // Solo esta parte se ejecuta como el usuario suplantado
    DoWorkAsImpersonatedUser();
}
finally
{
    if (!RevertToSelf())
    {
        // Un estado en el que no se puede revertir es peligroso: como mínimo, no se debe continuar el procesamiento
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }
}

En una línea de tiempo, queda así:

ArchivoWindowsHilo de procesamientoLlamadorArchivoWindowsHilo de procesamientoLlamadorA partir de aquí las comprobaciones de accesose hacen como el usuario aliceA partir de aquí vuelve a la cuentade servicio svc-appSolicita el procesamientoAsocia el token con ImpersonateLoggedOnUserAbre el archivoLa ACL se evalúa con los privilegios de aliceQuita el token con RevertToSelfDevuelve el resultado

La suplantación solo está activa en el tramo que va de ImpersonateLoggedOnUser a RevertToSelf. La E/S realizada fuera de este tramo se evalúa con la cuenta del proceso, no con la del usuario suplantado. La trampa del procesamiento asíncrono que se trata en el capítulo 14 se entiende mejor si se piensa como el problema de que la E/S real se sale de este tramo.

Lo importante no es tanto el inicio de la suplantación como asegurarse de revertirla. Si se olvida revertir, el procesamiento posterior en ese hilo sigue ejecutándose como el usuario suplantado.

En aplicaciones que usan un pool de hilos en particular, lo que se pensaba como un único procesamiento puede terminar afectando a otra solicitud u a otro procesamiento distinto.

Por eso, la suplantación no debe pensarse como “empezar y luego revertir”, sino como algo que hay que confinar en un ámbito pequeño.

9. En .NET, usar WindowsIdentity.RunImpersonated

En .NET, siempre que sea posible conviene usar WindowsIdentity.RunImpersonated, que facilita expresar el ámbito de la suplantación directamente en el código.

Si se dispone de un SafeAccessTokenHandle, se puede escribir así:

using Microsoft.Win32.SafeHandles;
using System.Security.Principal;

static string ReadFileAsUser(SafeAccessTokenHandle token, string path)
{
    return WindowsIdentity.RunImpersonated(token, () =>
    {
        return File.ReadAllText(path);
    });
}

Lo bueno de esta forma es que el ámbito de la suplantación queda contenido dentro de la expresión lambda.

RunImpersonated(token, () =>
{
    // Solo aquí hay suplantación
});

// A partir de aquí, fuera, es el contexto original

Para acceso a archivos, acceso al registro, llamadas a bibliotecas existentes y otros casos en los que solo se quiere suplantar un procesamiento concreto, esta forma es legible y segura.

Para procesamiento asíncrono, se usa RunImpersonatedAsync.

using Microsoft.Win32.SafeHandles;
using System.Security.Principal;

static Task WriteFileAsUserAsync(
    SafeAccessTokenHandle token,
    string path,
    string text,
    CancellationToken cancellationToken)
{
    return WindowsIdentity.RunImpersonatedAsync(token, async () =>
    {
        await File.WriteAllTextAsync(path, text, cancellationToken);
    });
}

Lo que hay que evitar es lanzar una tarea fire-and-forget desde dentro del ámbito de suplantación.

// Mal ejemplo
WindowsIdentity.RunImpersonated(token, () =>
{
    _ = Task.Run(() =>
    {
        File.WriteAllText(path, text);
    });
});

Con este código resulta difícil saber cuándo, y bajo qué contexto de ejecución, se ejecuta realmente “el procesamiento que escribe el archivo”.

El procesamiento asíncrono que se quiere ejecutar con suplantación debe esperarse (await) dentro de RunImpersonatedAsync, de modo que se salga del ámbito solo una vez completado el procesamiento.

10. Cuando se obtiene el token con LogonUser

LogonUser es una API representativa para obtener un token a partir de las credenciales de otro usuario, pero hay que manejarla con cuidado.

A LogonUser se le pasan el nombre de usuario, el dominio y la contraseña. Es decir, la propia aplicación pasa a manejar credenciales.

En el trabajo real, hay que prestar atención a esto:

  • No dejar la contraseña en texto plano en el código ni en archivos de configuración
  • Si es posible, usar la autenticación del sistema operativo, cuentas de servicio, delegación o la autenticación de Windows ya existente
  • Gestionar la información secreta en un almacén de secretos adecuado o en la plataforma operativa
  • Cerrar siempre el identificador del token
  • No emitir en los logs información secreta más allá del nombre de usuario
  • Minimizar el ámbito en el que se suplanta

Más adelante se muestra un ejemplo mínimo, pero evite, por favor, copiarlo tal cual y escribir la contraseña como un literal de cadena en password. Las credenciales escritas en el código fuente permanecen en el historial del repositorio, en los artefactos de compilación y en cualquier resultado de descompilación. Dejarlas en texto plano en un archivo de configuración tiene el mismo problema. Se retoma este punto en 21.6.

Entonces, ¿dónde guardar la contraseña? Se organizan las opciones a continuación.

Método de almacenamiento Cuándo usarlo Puntos a tener en cuenta
No tener contraseña en absoluto Primera opción. Resolverlo con autenticación de Windows, cuentas de servicio o delegación Hay que decidirlo en la fase de diseño. Quitarlo después es difícil
Pedirla de forma interactiva en tiempo de ejecución Herramientas operativas que un administrador ejecuta manualmente No sirve para ejecución desatendida ni para servicios. La cadena leída debe soltarse justo después de usarla
Administrador de credenciales de Windows Uso repetido con la misma cuenta y el mismo PC Es un almacén por usuario. Si se usa con una cuenta de servicio, hay que registrarlo con esa cuenta
DPAPI Se quiere proteger el valor de un archivo de configuración dentro de un único PC Hay que prestar atención al ámbito de ProtectedData. Con CurrentUser, solo puede descifrarlo el usuario que cifró; con LocalMachine, también pueden descifrarlo otros usuarios de ese mismo PC
Plataformas de gestión de secretos como Key Vault Hay varios servidores, CI o integración con la nube Requiere autenticación en la propia plataforma. El manejo en memoria del secreto obtenido hay que pensarlo aparte

La forma concreta de usar DPAPI se explica en otro artículo, cómo guardar de forma segura la información sensible de un archivo de configuración.

Sea cual sea el método, algo común a todos es plantearse, antes de elegir el método de almacenamiento, si la aplicación realmente necesita recibir la contraseña.

Se muestra un ejemplo con la configuración mínima.

using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;

internal static class NativeMethods
{
    private const int LOGON32_LOGON_INTERACTIVE = 2;
    private const int LOGON32_PROVIDER_DEFAULT = 0;

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    internal static extern bool LogonUser(
        string lpszUsername,
        string? lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        out SafeAccessTokenHandle phToken);

    public static SafeAccessTokenHandle Logon(
        string userName,
        string? domain,
        string password)
    {
        bool ok = LogonUser(
            userName,
            domain,
            password,
            LOGON32_LOGON_INTERACTIVE,
            LOGON32_PROVIDER_DEFAULT,
            out SafeAccessTokenHandle token);

        if (!ok)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        return token;
    }
}

public static string ReadFileWithExplicitCredential(
    string userName,
    string? domain,
    string password,
    string path)
{
    using SafeAccessTokenHandle token = NativeMethods.Logon(userName, domain, password);

    return WindowsIdentity.RunImpersonated(token, () =>
    {
        return File.ReadAllText(path);
    });
}

Este ejemplo solo pretende mostrar la forma de la API.

En el trabajo real, primero hay que plantearse si el diseño en el que la aplicación recibe la contraseña es realmente adecuado.

En muchos casos, es más seguro considerar alternativas como estas.

Lo que se quiere hacer Alternativa
Que todo el servicio acceda a un recurso concreto Asignar a una cuenta de servicio dedicada la ACL mínima necesaria
Acceder a un archivo con los privilegios del propio usuario Usar autenticación de Windows y un diseño de delegación
Realizar una operación que requiere privilegios de administrador Establecer en el lado del servicio una API de administración clara y controlarla con autorización en el lado de la aplicación
Usar otra cuenta solo para una parte del procesamiento Confinar el ámbito de suplantación al nivel de método y dejar registro de auditoría

11. Prestar atención al tipo de inicio de sesión de LogonUser

Las propiedades del token obtenido con LogonUser cambian según el tipo de inicio de sesión.

Si no se entiende esta diferencia y simplemente se copia y pega, el comportamiento no será el esperado.

Tipo de inicio de sesión Puntos a tener en cuenta
Interactive Similar a un inicio de sesión interactivo. Fácil de usar para operaciones locales, pero depende del entorno de ejecución y de los privilegios
Network Pensado para inicio de sesión por red. El token devuelto puede no servir directamente para iniciar un proceso
NewCredentials En local se comporta como las credenciales actuales, y se usa a veces para que, al conectar de forma remota, se empleen las credenciales indicadas

Lo que se quiere transmitir aquí no es memorizar un tipo de inicio de sesión concreto.

Lo importante son estos dos puntos:

  1. Según el tipo de inicio de sesión, cambia el comportamiento en el acceso local, en el acceso por red y en el inicio de procesos
  2. Hay que comprobar si el token devuelto es un token primario o un token de suplantación

Por ejemplo, es un error típico obtener un token con LOGON32_LOGON_NETWORK y pasarlo tal cual a CreateProcessAsUser, lo cual falla.

Si el objetivo es iniciar un proceso, se necesita un token primario. Según convenga, el diseño pasa por crear un token primario con DuplicateTokenEx.

12. No subestimar RevertToSelf

Cuando se inicia la suplantación con la API Win32, RevertToSelf la finaliza. Esta operación de reversión no es una simple tarea de limpieza; es un procesamiento importante para devolver el límite de seguridad a su estado original.

Un mal ejemplo:

ImpersonateLoggedOnUser(token);
DoWork();
RevertToSelf();

A primera vista parece no tener problemas, pero si DoWork() lanza una excepción, RevertToSelf() no llega a ejecutarse.

Siempre debe ir en un finally.

if (!ImpersonateLoggedOnUser(token))
{
    throw new Win32Exception(Marshal.GetLastWin32Error());
}

try
{
    DoWork();
}
finally
{
    if (!RevertToSelf())
    {
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }
}

Continuar el procesamiento tal cual cuando RevertToSelf falla también es peligroso. Si se continúa el procesamiento posterior sin haber vuelto realmente a los privilegios originales, ese procesamiento sigue con los privilegios de un usuario no previsto. Como mínimo, esa unidad de procesamiento debe tratarse como un fallo y actuar del lado seguro.

RunImpersonated / RunImpersonatedAsync de .NET son útiles como forma de expresar el ámbito precisamente para evitar este olvido de revertir.

13. Reducir todo lo posible el ámbito de la suplantación

El principio de diseño más importante en la suplantación es suplantar solo el procesamiento necesario.

Un mal ejemplo:

WindowsIdentity.RunImpersonated(token, () =>
{
    ValidateRequest();
    LoadConfiguration();
    WriteDebugLog();
    ReadUserFile();
    UpdateDatabase();
    SendNotification();
});

Suplantar un ámbito tan amplio como este hace difícil saber con qué privilegios se realiza cada operación.

Por ejemplo, el acceso al destino de los logs podría hacerse con los privilegios del usuario suplantado y fallar la escritura del log. La conexión a la base de datos podría intentarse con las credenciales del usuario suplantado en lugar de las de la cuenta de servicio. Incluso el procesamiento de notificaciones o la creación de archivos temporales podrían verse afectados por un contexto de privilegios innecesario.

Un buen ejemplo es aislar solo las operaciones que necesitan suplantación.

ValidateRequest();
LoadConfiguration();

string content = WindowsIdentity.RunImpersonated(token, () =>
{
    return File.ReadAllText(userFilePath);
});

UpdateDatabase(content);
WriteAuditLog(userName, userFilePath, success: true);

Con esta forma queda claro que lo único que necesita suplantación es la parte de File.ReadAllText.

La suplantación es útil, pero cuanto más se amplía, más difícil de leer y más propensa a errores se vuelve.

14. En procesamiento asíncrono, comprobar “si sigue suplantado hasta el final”

En las aplicaciones .NET actuales, gran parte del procesamiento —archivos, HTTP, base de datos, colas, almacenamiento, etc.— es asíncrono.

Por eso, la combinación de suplantación con async / await requiere cuidado.

La política básica es solo esta:

El procesamiento asíncrono que necesita suplantación se espera (await) dentro de RunImpersonatedAsync

Se muestra, con el mismo formato que el diagrama del capítulo 8, qué ocurre si no se respeta esto. Pero antes conviene tener claro algo: no siempre es cierto que “enviar el trabajo a otro hilo hace que se pierda la suplantación”. Según con qué mecanismo se suplantó, la forma de romperse es la opuesta.

Forma de suplantar El procesamiento enviado a Task.Run Qué ocurre
RunImpersonated / RunImpersonatedAsync de .NET sigue ejecutándose suplantado La suplantación se extiende más allá del ámbito que muestra el código. Quien lanzó la tarea no recibe ni su finalización ni sus excepciones
Llamar directamente a ImpersonateLoggedOnUser de Win32 se ejecuta con la cuenta del proceso La E/S real queda fuera del tramo de suplantación y se produce Access denied

En el lado de .NET ocurre esto porque RunImpersonated transporta el token de suplantación en un AsyncLocal. El valor de AsyncLocal viaja junto con el ExecutionContext, y Task.Run captura el ExecutionContext vigente en el momento de la llamada y lo restaura en el hilo del pool. El controlador de cambios que se ejecuta en cada restauración vuelve a llamar a ImpersonateLoggedOnUser en ese hilo, así que el hilo del pool queda con la misma suplantación. Así es como está implementado en el runtime (s_currentImpersonatedToken y CurrentImpersonatedTokenChanged de WindowsIdentity).

En cambio, el token de Win32 solo está asociado al hilo y no se copia por sí solo a otro hilo.

Si se sigue la línea de tiempo del lado de .NET, queda así:

ArchivoOtro hilo del pool de hilosHilo que llamaArchivoOtro hilo del pool de hilosHilo que llamaEl token de suplantación viajajunto con el ExecutionContextSolo se reviertela suplantación de este hiloSigue como el usuario suplantado.Quien lo lanzó no sabe cuándo terminaInicia la suplantación con RunImpersonatedEnvía la escritura con Task.RunSale del ámbito y revierte la suplantaciónLa escritura real se ejecuta después de estoNi el resultado ni la excepción los recibe nadie

En el diagrama del capítulo 8, la E/S quedaba dentro del tramo de suplantación. En este diagrama, en el momento en que se envía a Task.Run el procesamiento pasa a otro hilo, y la escritura real queda fuera del tramo que muestra el código. El peligro no está en que se pierda la suplantación, sino en que no se pierda y, además, deje de poder rastrearse: ese es el problema de esta forma. En concreto, ocurren tres cosas a la vez:

  • Deja de poder leerse el alcance de la suplantación. El ámbito escrito como “desde aquí hasta aquí” y el ámbito que realmente se ejecuta suplantado no coinciden
  • Los fallos quedan silenciados. Como nadie hace await de la tarea lanzada, aunque se produzca Access denied la excepción no llega a ningún sitio
  • No queda claro cuándo se puede cerrar el token. Si se cierra SafeAccessTokenHandle con using, se acaba cerrando mientras el procesamiento todavía sigue en marcha

Si se suplanta directamente con Win32, ocurre lo contrario: el procesamiento lanzado se ejecuta con la cuenta del proceso. Esto tiende a manifestarse como que en el entorno de pruebas la cuenta de servicio también tiene privilegios y todo funciona, y solo en producción aparece por primera vez el Access denied.

En cualquiera de los dos casos, esperar (await) hasta la finalización dentro de RunImpersonatedAsync evita que ocurra.

Un buen ejemplo:

await WindowsIdentity.RunImpersonatedAsync(token, async () =>
{
    await using FileStream stream = File.OpenRead(path);
    using var reader = new StreamReader(stream);
    string text = await reader.ReadToEndAsync();

    await ProcessTextAsync(text);
});

Sin embargo, incluso con esta forma hay algo que considerar.

Si conviene que ProcessTextAsync también se ejecute como el usuario suplantado.

Si basta con suplantar solo la parte que lee el archivo, es más seguro separarlo así:

string text = await WindowsIdentity.RunImpersonatedAsync(token, async () =>
{
    return await File.ReadAllTextAsync(path);
});

await ProcessTextAsync(text);

El hecho de poder hacer await dentro del ámbito de suplantación no significa que se pueda meter cualquier cosa ahí dentro.

También en el procesamiento asíncrono, el ámbito de la suplantación debe minimizarse.

15. ASP.NET Core y la suplantación

Al usar autenticación de Windows en ASP.NET Core, también hay que prestar atención al manejo de la suplantación.

Es peligroso dar por hecho que “como se ha iniciado sesión con autenticación de Windows, todo el procesamiento de la solicitud se ejecuta como ese usuario”.

En general, el propio proceso de la aplicación se ejecuta con la identidad del grupo de aplicaciones o con la cuenta de ejecución del servicio. La identidad de Windows del usuario se obtiene como información de autenticación, pero eso no significa que todo el procesamiento pase automáticamente a tener los privilegios del usuario.

Si se necesita realizar una acción concreta con los privilegios del usuario, hay que crear explícitamente el ámbito con RunImpersonated / RunImpersonatedAsync.

La idea del código es esta:

app.MapGet("/download", async (HttpContext context) =>
{
    if (context.User.Identity is not WindowsIdentity user)
    {
        return Results.Unauthorized();
    }

    string path = GetPathFromRequest(context);

    byte[] bytes = await WindowsIdentity.RunImpersonatedAsync(
        user.AccessToken,
        async () => await File.ReadAllBytesAsync(path));

    return Results.File(bytes, "application/octet-stream");
});

También en este ejemplo, lo único que se suplanta es la parte de lectura del archivo.

Hay que pensar con cuidado si es necesario meter en el ámbito de suplantación también la generación de la respuesta, el registro de logs y la lógica de autorización propia de la aplicación.

16. Cómo interpretar Access denied

En implementaciones que usan suplantación, el error que aparece con frecuencia es Access denied.

Si al ver este error solo se piensa que “ha fallado la suplantación”, se toma un camino más largo de lo necesario.

Se separan los puntos de vista a comprobar.

Punto de vista Qué comprobar
Si realmente se está suplantando Comprobar WindowsIdentity.GetCurrent().Name dentro del ámbito de suplantación
Si el nivel de suplantación es suficiente Si es Identification en lugar del nivel necesario
Si la ACL del recurso de destino es correcta Si el usuario suplantado tiene permiso de lectura / escritura
Si es local o remoto Si funciona con archivos locales pero falla solo con UNC
Si es un problema de doble salto Si se intenta ir del servidor web al servidor de archivos con los privilegios del usuario
Si se ha salido del ámbito de suplantación Si la E/S real se ejecuta fuera del ámbito o en otra tarea
Si el tipo de token es el correcto Si se está pasando un token de suplantación para iniciar un proceso
Si hay influencia de UAC / nivel de integridad Si, aunque se pertenezca a Administrators, es un token no elevado

Es especialmente importante no quedarse tranquilo con solo comprobar el nombre.

WindowsIdentity identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);

Este log es útil, pero no es suficiente.

Como mínimo, conviene revisar también esto:

Console.WriteLine(identity.ImpersonationLevel);
Console.WriteLine(identity.IsAuthenticated);

Además, si el destino es un recurso compartido de red, hay que revisar no solo el código de la aplicación, sino también el método de autenticación, la configuración de delegación, el SPN, la cuenta de servicio y la ACL del lado del servidor de archivos.

17. UAC y el problema de “falla aunque sea administrador”

En Windows, que un usuario pertenezca al grupo Administrators y que el token actual esté elevado no es lo mismo.

En un entorno con UAC activado, incluso un usuario administrador suele ejecutar procesos con un token restringido, y las operaciones que requieren privilegios de administrador necesitan una elevación.

Por eso ocurre esto:

El usuario pertenece a Administrators
  ↓
Pero el token actual no está elevado
  ↓
Access denied al escribir en Program Files o en HKLM

Con la suplantación pasa lo mismo.

En lugar de pensar “el usuario suplantado es administrador, así que debería poder escribir”, hay que comprobar en qué estado se encuentra realmente el token que se está pasando.

En la depuración, se revisan estos puntos:

  • Grupos a los que pertenece
  • Si los privilegios (Privilege) están habilitados o deshabilitados
  • Nivel de integridad
  • Estado de elevación
  • Si es un token restringido
  • Si existe un token de elevación vinculado

Con la API Win32 se puede usar GetTokenInformation para comprobar TokenType, TokenImpersonationLevel, TokenElevationType, TokenIntegrityLevel, etc.

Sin embargo, como diseño práctico, es más seguro confinar las operaciones mínimas necesarias en un servicio o una API de administración dedicados, en lugar de “suplantar a un usuario administrador para hacer cualquier cosa”.

18. Recursos compartidos de red y el doble salto

Una consulta muy habitual sobre suplantación es el acceso a recursos compartidos de red.

PC del cliente
  ↓ Autenticación de Windows
Servidor web / servidor de API
  ↓ Se quiere acceder suplantando
Servidor de archivos

En esta configuración, puede pasar que “en el servidor web se obtiene el nombre de usuario, pero al llegar al servidor de archivos falla”.

Esto es un problema de si se puede redelegar las credenciales del usuario a otro servidor.

La suplantación en el propio servidor local y la delegación hacia otro servidor no son lo mismo.

El nivel Impersonation puede servir para operaciones locales, pero puede no ser suficiente para actuar como cliente frente a un servidor remoto.

Si se quiere usar los privilegios del propio usuario a través de la red, hace falta diseñar la delegación Kerberos, la delegación restringida, el SPN, la cuenta de servicio y el método de autenticación.

Aquí es fácil llegar a un punto muerto, así que se muestran a continuación las opciones para el siguiente paso.

Dirección Qué hacer Casos en que conviene Puntos a tener en cuenta
Delegación restringida de Kerberos Configurar en la cuenta del servidor intermedio “se permite delegar solo a este servicio” El servidor intermedio y el servidor de archivos están en el mismo dominio y se cuenta con la colaboración del administrador de dominio La configuración se hace en el dominio, no en la aplicación. Hay que enumerar explícitamente el SPN de destino de la delegación
Delegación restringida basada en recursos Poner la configuración que permite la delegación en la cuenta del lado al que se delega, es decir, el servidor de archivos Cuando se cruzan dominios o cuando el administrador del recurso quiere llevar la iniciativa Es un mecanismo de Windows Server 2012 en adelante. El lugar donde se configura es el inverso de la delegación restringida tradicional
Pasar credenciales de forma explícita Conectarse con las credenciales de una cuenta dedicada de uso limitado, en lugar de las del propio usuario Cuando no se puede configurar la delegación, o cuando “ser el propio usuario” no es un requisito de negocio Se necesita almacenar las credenciales. Consulte la tabla del capítulo 10
Diseñar sin retransmisión Concentrar el acceso al servidor de archivos en la cuenta de servicio y realizar la autorización en el lado de la aplicación Cuando las reglas de negocio están del lado de la aplicación La ACL deja de ser la decisión final. El diseño del registro de auditoría se vuelve importante
Dejar que el cliente acceda directamente Sin pasar por el servidor, dejar que el propio PC cliente acceda directamente al servidor de archivos Cuando basta con abrir la carpeta compartida desde la pantalla Se pierde el control centralizado y la recopilación de logs desde el lado del servidor

En cuanto al orden de las decisiones, primero hay que determinar si realmente hace falta llegar al servidor de archivos con los privilegios propios de Windows del usuario. Si hace falta, se entra en el diseño de la delegación; si no hace falta, se opta por un diseño sin retransmisión. Como la configuración de la delegación no la puede completar solo el desarrollador de la aplicación, es realista consultar cuanto antes con el administrador de dominio en el momento en que se determina que es necesaria.

Por otro lado, según los requisitos de negocio, también hay casos en los que no hace falta llegar al servidor de archivos con los privilegios propios de Windows del usuario.

En ese caso, un diseño como este es más simple.

La autenticación y autorización del usuario las hace la aplicación
  ↓
El acceso al servidor de archivos se hace con una cuenta de servicio dedicada
  ↓
En el log de operaciones se registran el ID del usuario y el archivo de destino

Esto es un diseño en el que la decisión final no es “la ACL del sistema operativo”, sino “la autorización de la aplicación”.

Cuál de los dos es correcto depende de los requisitos de negocio.

Sin embargo, si no queda claro cuál de los dos métodos se ha elegido, la suplantación, la delegación, la ACL y la autorización de la aplicación se mezclan y se vuelven difíciles de entender.

19. El ciclo de vida del identificador del token

Un token es un identificador (handle) hacia un objeto del kernel, así que hay que cerrarlo en cuanto deja de ser necesario tras obtenerlo.

En .NET, lo básico es usar SafeAccessTokenHandle y gestionar el ámbito con using.

using SafeAccessTokenHandle token = NativeMethods.Logon(userName, domain, password);

string result = WindowsIdentity.RunImpersonated(token, () =>
{
    return File.ReadAllText(path);
});

Un mal ejemplo:

// Mal ejemplo: mantener el token de forma global
private static SafeAccessTokenHandle? _cachedToken;

Mantener un token durante mucho tiempo lleva a problemas como estos:

  • Fuga de identificadores (handle leak)
  • Dejar de saber qué procesamiento usa qué token
  • Perder claridad sobre la coherencia con la desactivación de la cuenta o el cambio de privilegios
  • Tender a un diseño que mantiene las credenciales de autenticación durante mucho tiempo
  • Ser difícil de justificar en una auditoría

El principio es este:

Obtenerlo cuando se necesita
  ↓
Usarlo en el ámbito mínimo
  ↓
Cerrarlo siempre

Por supuesto, según el coste de autenticación o los requisitos operativos, en algunos casos se puede plantear el uso de una caché. Pero incluso en ese caso, hay que incluir en el diseño el tiempo de vida, la eliminación, el cambio de cuenta, el registro de auditoría y el tratamiento en un cambio de privilegios.

20. Qué dejar en el registro de auditoría

En el procesamiento que usa suplantación, el diseño del log también es importante.

Como mínimo, dejar disponible la información de esta tabla facilita la investigación posterior.

Elemento Ejemplo
Usuario que hizo la solicitud DOMAIN\alice
Cuenta del proceso en ejecución DOMAIN\svc-app
Cuenta suplantada DOMAIN\alice o una cuenta dedicada
Recurso de destino Ruta de archivo, nombre del recurso compartido, clave del registro, etc.
Operación Read, Write, Delete, CreateProcess, etc.
Resultado Success, AccessDenied, Timeout, UnexpectedError
Código de error Código de error Win32, HRESULT, tipo de excepción
Ámbito de suplantación En qué método y en qué unidad de operación se suplantó

Sin embargo, también hay cosas que no deben aparecer en el log.

  • Contraseñas
  • El valor del token de acceso
  • Cabeceras de autenticación
  • Tickets Kerberos o las propias credenciales
  • Contenido de archivos que incluya información personal

El objetivo del log es poder rastrear después “a petición de quién, como qué cuenta, qué se intentó y cómo terminó, con éxito o con fallo”.

No hace falta registrar las propias credenciales.

21. Antipatrones habituales

Se organizan aquí las implementaciones peligrosas que se ven con frecuencia en torno a la suplantación.

21.1 Suplantar toda la aplicación

WindowsIdentity.RunImpersonated(token, () =>
{
    RunEntireApplication();
});

Suplantar toda la aplicación hace imposible saber con qué privilegios se ejecuta cada operación.

La suplantación debe limitarse a la E/S necesaria o a llamadas concretas a la API.

21.2 Revertir sin finally

ImpersonateLoggedOnUser(token);
DoWork();
RevertToSelf();

Es peligroso porque, si hay una excepción, no se revierte.

Siempre hay que usar try / finally, o bien RunImpersonated.

21.3 Hacer fire-and-forget durante la suplantación

WindowsIdentity.RunImpersonated(token, () =>
{
    _ = Task.Run(DoWorkAsync);
});

Se sale del ámbito de suplantación antes de que el procesamiento termine. Con esta forma de escribirlo, la tarea lanzada (como se vio en el capítulo 14) sigue ejecutándose suplantada, así que el tramo de suplantación que muestra el código y el tramo real de suplantación quedan desalineados. Además, como nadie hace await, si falla no aparece ninguna excepción. Si se suplanta directamente con Win32, ocurre lo contrario: se ejecuta con la cuenta del proceso.

Si se necesita suplantación, hay que hacer await dentro de RunImpersonatedAsync.

21.4 Dar por exitoso solo con el nombre de usuario

Console.WriteLine(WindowsIdentity.GetCurrent().Name);

Aunque el nombre de usuario sea el esperado, puede faltar el nivel de suplantación o los privilegios necesarios.

También hay que revisar ImpersonationLevel, la ACL de destino, el tipo de inicio de sesión y la delegación de red.

21.5 Suplantar una cuenta de administrador como “cuenta cómoda”

Un diseño como “no quiero que falle este procesamiento, así que lo suplanto con una cuenta de administrador” es peligroso.

Es más seguro preparar una cuenta dedicada con los privilegios mínimos necesarios y limitar el alcance de la operación.

21.6 Poner la contraseña en un archivo de configuración

{
  "UserName": "DOMAIN\\admin",
  "Password": "P@ssw0rd!"
}

Esto debe evitarse.

Si se manejan credenciales, hay que usar un mecanismo adecuado al entorno: un almacén de secretos, el Administrador de credenciales de Windows, DPAPI, un Key Vault en la nube, o la gestión de secretos de la plataforma operativa.

21.7 Tratar como el mismo tema el inicio de procesos y el acceso a archivos

Para el simple acceso a archivos, a veces basta con un token de suplantación.

Por otro lado, si se quiere iniciar un proceso como otro usuario, aparecen otros aspectos: el token primario, el perfil, el escritorio, las variables de entorno, la sesión, los privilegios, etc.

Si se usa CreateProcessAsUser o CreateProcessWithTokenW, hay que tratarlo como un diseño distinto al de la suplantación.

22. Puntos de vista para las pruebas

El procesamiento con suplantación, si solo se comprueba en un entorno de administrador local, deja pasar casos por alto.

Como mínimo, hay que preparar estos casos de prueba.

Caso Qué comprobar
Usuario con permisos Que se puede leer / escribir el archivo de destino
Usuario sin permisos Que falla correctamente como Access denied
Usuario inexistente Que se trata como un fallo de autenticación
Contraseña incorrecta Que falla sin exponer información secreta en el log
Recurso compartido de red Comprobar la diferencia de comportamiento entre local y UNC
Procesamiento asíncrono Que, incluso después del await, funciona dentro del ámbito esperado
Excepción producida Que la suplantación siempre se revierte
Solicitudes concurrentes Que la suplantación de un usuario no se mezcla con la de otro
Ejecución como servicio Que funciona con la cuenta de servicio real, no solo en el entorno de inicio de sesión interactivo del desarrollador

En particular, estas dos no se pueden dejar fuera.

Que el éxito funcione
Que el fallo, cuando debe fallar, falle

En el procesamiento con suplantación, no basta con probar los casos de éxito; también hay que probar que un usuario sin permisos sea rechazado con seguridad.

Si una operación que debería rechazarse termina teniendo éxito, es posible que el diseño de la suplantación o de la autorización esté equivocado.

23. Lista de verificación de implementación

Antes y después de implementar, se revisa esta lista de verificación.

Punto de vista Qué comprobar
Objetivo Se puede explicar por qué hace falta la suplantación
Alternativas Se ha considerado si no basta con una cuenta de servicio o con la autorización de la aplicación
Alcance El ámbito de suplantación es mínimo
Reversión Se revierte con seguridad incluso en caso de excepción
Asíncrono Se hace await hasta la finalización dentro de RunImpersonatedAsync
Token No se confunde el token primario con el token de suplantación
Nivel de suplantación Se distingue entre Identification e Impersonation / Delegation
Red Se ha comprobado si hace falta UNC, doble salto o delegación Kerberos
UAC No se confunde pertenecer a Administrators con estar elevado
Información secreta No se guarda la contraseña en texto plano
Identificador SafeAccessTokenHandle se cierra con using
Log Se puede rastrear el usuario, la cuenta suplantada, el destino y el resultado
Pruebas Se han comprobado los casos con permisos / sin permisos / excepción / concurrencia

Si hay muchos elementos que fallan en esta lista, conviene revisar el diseño antes de escribir el código.

24. Identificar dónde conviene usarla

El token de suplantación es potente, pero no siempre es la primera opción que hay que elegir.

Como diseño, conviene pensarlo separando los casos de uso, así se pierde menos.

24.1 Cuando se quiere usar tal cual la ACL del sistema operativo

Si la ACL del servidor de archivos o de la carpeta compartida es el centro de la regla de negocio, y la aplicación también debe seguir esa decisión, tiene sentido suplantar como el propio usuario.

Acceder como el propio usuario
  ↓
La ACL de Windows es la decisión final

En este caso, hay que diseñar teniendo en cuenta la autenticación de Windows, el nivel de suplantación, la delegación y la configuración de red.

24.2 Cuando se quiere autorizar del lado de la aplicación

Si las reglas de negocio están del lado de la aplicación y el servidor de archivos o la base de datos están bajo el control de la aplicación, a veces es más claro acceder con una cuenta de servicio y autorizar del lado de la aplicación.

Se autentica al usuario
  ↓
Se autoriza en la aplicación
  ↓
Se accede al recurso con la cuenta de servicio
  ↓
Se deja el ID del usuario en el log de auditoría

En este esquema, en lugar de usar suplantación, cobra importancia la lógica de autorización de la aplicación y el registro de auditoría.

24.3 Cuando se quiere realizar operaciones administrativas

En lugar de ejecutar las operaciones administrativas directamente con el token del usuario, suele ser más seguro preparar un servicio o una API dedicados a las operaciones administrativas, donde se realicen la autorización, la validación de entrada, la auditoría y la posibilidad de revertir.

Cliente
  ↓
Solicita a la API de administración
  ↓
La API de administración autoriza
  ↓
Opera con los privilegios mínimos necesarios
  ↓
Registro de auditoría

“Suplantar al administrador por ahora” parece cómodo a corto plazo.

Pero a largo plazo, se vuelve doloroso en la auditoría, la investigación de incidentes, el cambio de privilegios y las revisiones de seguridad.

25. Resumen

El token de suplantación de Windows es un mecanismo importante para usar correctamente la gestión de privilegios de Windows.

Sin embargo, también es un área en la que, si se usa sin entenderla, es fácil acabar con código que parece funcionar pero es peligroso.

Se enumeran los puntos que hay que tener claros.

  • El token de acceso representa el contexto de seguridad: usuario, grupos, privilegios, etc.
  • El proceso tiene un token primario
  • El token de suplantación se asocia principalmente al hilo y se usa en las comprobaciones de acceso
  • La suplantación no es una escalada de privilegios
  • El token primario y el token de suplantación tienen usos distintos
  • Según el nivel de suplantación cambia si solo se puede identificar, si se puede acceder realmente, o si se puede delegar de forma remota
  • Al suplantar con la API Win32, siempre hay que llamar a RevertToSelf dentro de un try / finally
  • En .NET, hay que expresar el ámbito de forma reducida con WindowsIdentity.RunImpersonated / RunImpersonatedAsync
  • Al usar LogonUser, hay que prestar atención a la gestión de credenciales y al tipo de inicio de sesión
  • En los recursos compartidos de red, no solo importa la suplantación; también entran en juego la delegación Kerberos y el diseño de la cuenta de servicio
  • El identificador del token se gestiona con SafeAccessTokenHandle y using
  • Hay que probar no solo los casos de éxito, sino también los casos que deben ser rechazados

Lo importante en una implementación con tokens de suplantación no es el único punto de “se ejecutó como otro usuario”, sino dejar la implementación en un estado en el que se pueda explicar todo esto.

Qué procesamiento
A petición de quién
Como qué cuenta
Con qué alcance se ejecutó
Dónde se revirtió
Cómo se registran el éxito y el fallo

Si se tiene esto ordenado, la suplantación deja de ser un mecanismo temible.

Se convierte en una herramienta práctica para aprovechar la ACL de Windows, las cuentas de servicio, los servidores de archivos existentes y los activos del dominio corporativo.

Referencias

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿Qué es un token de suplantación en Windows?
Un token de suplantación es el token que se asocia a un hilo para que ese hilo reciba las comprobaciones de acceso bajo un contexto de seguridad distinto. No es que todo el proceso pase a ser otro usuario: solo el hilo que está suplantando recibe las comprobaciones de acceso a archivos, al registro, etc., con los privilegios de ese usuario. No se trata de una "magia para convertirse en administrador", es decir, no es una escalada de privilegios, sino un mecanismo para que solo una parte del procesamiento del proceso servidor reciba las comprobaciones de acceso con los privilegios del usuario cliente.
¿En qué se diferencian el token primario y el token de suplantación?
El token primario representa el contexto de seguridad del proceso y se usa para iniciar procesos, por ejemplo con CreateProcessAsUser. El token de suplantación se usa para que un hilo se ejecute bajo otro contexto de seguridad y aparece en escenarios como ImpersonateLoggedOnUser o la suplantación del cliente en Named Pipe. Si lo que se quiere es iniciar un proceso, en principio se necesita un token primario; si solo se dispone de un token de suplantación, el flujo habitual es crear un token primario con DuplicateTokenEx. Confundir ambos tipos lleva a errores como Access denied que resultan difíciles de diagnosticar.
¿Por qué aparece Access denied aunque se esté suplantando?
Hay varios puntos que conviene revisar. Dentro del alcance de suplantación, hay que comprobar no solo el Name de WindowsIdentity.GetCurrent(), sino también el ImpersonationLevel, para verificar que sea Impersonation o superior y no solo Identification. También hay que revisar la ACL del recurso objetivo, si la E/S real se está ejecutando fuera del alcance de suplantación o en otra tarea, si se trata del problema de doble salto (donde el acceso local funciona pero el acceso por UNC falla), y el efecto de un token no elevado por UAC. El nombre puede ser el esperado y, aun así, faltar privilegios.
¿Cómo se implementa la suplantación de forma segura en .NET?
Lo básico es usar WindowsIdentity.RunImpersonated / RunImpersonatedAsync y confinar el alcance de la suplantación dentro de una expresión lambda. El procesamiento asíncrono que necesite suplantación debe esperarse (await) dentro de RunImpersonatedAsync, evitando lanzar tareas fire-and-forget desde dentro del alcance de suplantación. Cuando se suplanta con la API Win32, siempre hay que llamar a RevertToSelf dentro de un try/finally. El alcance de la suplantación debe reducirse al mínimo necesario de E/S, y el identificador del token debe gestionarse con SafeAccessTokenHandle y using.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog