Acceder a la caché de Internet Explorer (III)

RunAs

Impersonation => RunAs

En los dos primeros artículos pudimos ver los objetos del API de Windows que íbamos a usar para poder acceder a la caché de los archivos temporales de Internet, y como persistir estos datos en formato XML.

Hoy vamos a usar Impersonation para ejecutar nuestra aplicación con otras credenciales de usuario, de este modo nos conectaremos a una ubicación remota a la cual sólo tienen permisos para conectarse los administradores.

Supongamos que tenemos una ubicación similar a esta: \\RootDFS\Logs\IECacheQuery\ y que sólo los usuarios del grupo administradores pueden acceder a leer y modificar su contenido. Si nuestra aplicación se ejecuta con permisos del usuario o servicio local, no podremos guardar los archivos de registro. Sin embargo, en un momento dado nuestra aplicación puede elevar su nivel de privilegios usando las credenciales del administrador, ejecutar las tareas requeridas, y posteriormente volver a su nivel predeterminado para continuar con su ejecución como si tal cosa.

A este proceso se le llama impersonar o suplantar, aunque personalmente no me gusta demasiado la traducción por las posibles connotaciones negativas que conlleva. Y básicamente se trata de que durante un lapso de tiempo nuestra aplicación va a estar ejecutándose con un usuario distinto al usuario que ha iniciado la aplicación.


Artistas invitados: LogonUser y DuplicateToken

La primera de ellas verifica si las credenciales suministradas son correctas, validándolas contra un dominio (que puede ser la estación local) y devuelve un token, mientras que la segunda crea una copia de un token existente. Hay que mencionar un detalle importante y es que el token devuelto por DuplicateToken es lo que se llama un 'impersonation token', que no es válido para la función CreateProcessAsUser ya que ésta necesita un token primario. Si deseamos usar ésta función debemos usar como alternativa la función DuplicateTokenEx.

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string pszUsername, string pszDomain, string pszPassword,
    int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
 
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
    int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
 
 
Más información aquí:
 
LogonUser Function:

DuplicateToken Function:
http://msdn.microsoft.com/en-us/library/aa446616(VS.85).aspx

Bien, ahora que ya nos conocemos todos vamos a ver un fragmento del código de la aplicación, que usa estas funciones para impersonar un usuario y devolver un objeto de tipo WindowsImpersonationContext. Posteriormente podremos usar el método 'Undo' de este objeto para deshacer el contexto de suplantación y volver al contexto anterior, de forma que la aplicación seguirá ejecutándose con el usuario que la ha lanzado.

public WindowsImpersonationContext ImpersonateUser(
    string sUsername, string sDomain, string sPassword)
{
    IntPtr pExistingTokenHandle = new IntPtr(0);
    IntPtr pDuplicateTokenHandle = new IntPtr(0);
    pExistingTokenHandle = IntPtr.Zero;
    pDuplicateTokenHandle = IntPtr.Zero;
 
    if (sDomain == "") sDomain = System.Environment.MachineName;
    try
    {
        string sResult = null;
        const int LOGON32_PROVIDER_DEFAULT = 0;
        const int LOGON32_LOGON_INTERACTIVE = 2;
        bool bImpersonated = LogonUser(sUsername, sDomain, sPassword,
            LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, 
            ref pExistingTokenHandle);
 
        if (false == bImpersonated)
        {
            int nErrorCode = Marshal.GetLastWin32Error();
            sResult = "LogonUser() failed with error code: " + nErrorCode + "\r\n";
            throw new Exception(sResult);
        }
 
        sResult += "Before impersonation: " + WindowsIdentity.GetCurrent().Name + "\r\n";
        bool bRetVal = DuplicateToken(pExistingTokenHandle,
            (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, 
            ref pDuplicateTokenHandle);
 
        if (false == bRetVal)
        {
            int nErrorCode = Marshal.GetLastWin32Error();
            CloseHandle(pExistingTokenHandle);
            sResult += "DuplicateToken() failed with error code: " + nErrorCode + "\r\n";
            throw new Exception(sResult);
            return null;
        }
        else
        {
            WindowsIdentity newId = new WindowsIdentity(pDuplicateTokenHandle);
            WindowsImpersonationContext impersonatedUser = newId.Impersonate();
            sResult += "After impersonation: " + 
                WindowsIdentity.GetCurrent().Name + "\r\n";
            this.ImpersonationContext = impersonatedUser;
            return impersonatedUser;
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        if (pExistingTokenHandle != IntPtr.Zero)
            CloseHandle(pExistingTokenHandle);
        if (pDuplicateTokenHandle != IntPtr.Zero)
            CloseHandle(pDuplicateTokenHandle);
    }
}


Vaya, esto es Impersionante...

Cabe destacar que en la llamada a LogonUser, se pasa un puntero al token de usuario 'pExistingTokenHandle' que posteriormente se usa en la llamada a DuplicateToken. Ésta devuelve el duplicado mediante 'pDuplicateTokenHandle', que posteriormente es usado para crear una nueva identidad y llamar a su método Impersonate, que es realmente la que se encarga de realizar la suplantación con el nuevo token.

(*) El siguiente código no funcionará en plataformas Windows 98 o Windows Me, ya que no poseen la posibilidad de trabajar con tokens de usuarios.

Una vez realizado el trabajo que requería de la suplantación de identidad, para volver al contexto predeterminado basta con invocar al método Undo del objeto WindowsImpersonationContext del siguiente modo:

Security.SecurityContext sec = new Security.SecurityContext();
 
if (Properties.Settings.Default.SaveResultsUsingImpersonation)
{
    sec.ImpersonateUser(
        Properties.Settings.Default.ImpersonateUser,
        Properties.Settings.Default.ImpersonateDomain,
        Properties.Settings.Default.ImpersonatePwd);
    appEventLog.WriteEntry(
        string.Format("Begin Impersonation, Current user as {0}",
        WindowsIdentity.GetCurrent().Name));
}
//
//Realizar acciones que requieren suplantación..
//
if (Properties.Settings.Default.SaveResultsUsingImpersonation)
{
    sec.ImpersonationContext.Undo();
    appEventLog.WriteEntry(
        string.Format("End Impersonation, Current user as {0}",
        WindowsIdentity.GetCurrent().Name));
}

Conclusión

Espero que este post haya clarificado cómo realizar este proceso. En el siguiente veremos cómo encapsular toda la aplicación para ejecutarse en forma de servicio. Ah! y antes que me lo diga alguno, ya se que no sería el mejor ejemplo para mostrar un servicio de Windows (porque corriendo con permisos de LOCALSYSTEM no se tiene permiso a la caché del usuario), pero tenía un post al respecto pendiente desde hace tiempo. Así que lo juntaremos todo en un mix o refrito o como queráis llamarlo, para que sirva de ejemplo.

También al final de la serie, postearé un recopilatorio de los pasos que hemos seguido y el código completo del ejemplo, por si alguien está tan loco como para querer probarlo :-P

Ahora, a impersonar todos. Pero si lo hacéis, por favor hacerlo bien... no de este modo:

blas

Saludos desde Andorra

Published 26/5/2008 16:04 por Lluis Franco
Archivado en: ,,,,,
Comparte este post:

Comentarios

# re: Acceder a la caché de Internet Explorer (III)

Wednesday, August 13, 2008 12:47 AM por Alfonso

Hola Luis:

Estuve revisando tu blog y vi este apartado sobre el uso de la API advapi32.dll y la use para validar un usuario y password de un dominio determinado, con la variante que lo hice VB.Net para una aplicacion web. Lo probe en mi maquina y funciona muy bien pero....... pero como suele suceder la publique en un servidor web y no funciona. Te pongo el codigo que escribi y/o copie.

<DllImport("advapi32.dll", SetLastError:=False)> _

   Shared Function LogonUser(ByVal pszUsername As String, ByVal pszDomain As String, ByVal pszPassword As String, ByVal dwLogonType As Integer, ByVal dwLogonProvider As Integer, ByRef phtoken As IntPtr) As Boolean

   End Function

teniendo estas variables como globales

   Const LOGON32_PROVIDER_DEFAULT = 0

   Const LOGON32_LOGON_INTERACTIVE = 2

   Dim resultado As Boolean

   Dim mitoken As IntPtr

llame la funcion desde un boton

Dim nErrorCode As Integer

resultado = LogonUser(Trim(TextBox2.Text), "dominio", Trim(TextBox1.Text), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, mitoken)

       If resultado = False Then

           nErrorCode = Marshal.GetLastWin32Error()

           Label1.Text = Trim(nErrorCode)

       Else

           Label1.Text = Trim(resultado)

       End If

Cuando lo corro en mi maquina en mi ambiente de desarrollo VS2005 la variable resultado varia de acuerdo a los datos de usuario y password efectivamente cuando es correcto el password es TRUE. Pero publicado lo ejecuta no marca error pero resultado = FALSE siempre,   Marshal.GetLastWin32Error manda el error 1008

podrias de favor comentarme si estoy haciendo algo mal o me falta.

Muchas gracias por tu ayuda

Tambien intente usar la API winmm.dll con PlaySound para emitir sonidos pero igual funciona bien en mi maquina pero publicada ya no hace nada tambien no marca nada pero el resultado es FALSE

# re: Acceder a la caché de Internet Explorer (III)

Thursday, August 28, 2008 3:13 AM por gobart

Usa este código, el código anterior no toma posesión real del usuario windows, este si.

Public Sub conectarUsuarioWindows(Optional ByVal usuario As String = "usgesseg", _

                                         Optional ByVal clave As String = "usgesseg", _

                                         Optional ByVal dominio As String = "ICETEL")

           tokenDeUsuario = New IntPtr(0) ' Inicializa un token en cero.

           tokenDeUsuario = IntPtr.Zero

           Dim conectado As Boolean = False

           Try

               conectado = MetodosNativos.LogonUser(usuario.Trim, _

                                                   dominio.Trim, _

                                                   clave.Trim, _

                                                   TIPO_LOGEO.LOGON32_LOGON_NETWORK, _

                                                   PROVEEDOR_LOGEO.LOGON32_PROVIDER_DEFAULT, _

                                                   tokenDeUsuario)

               If conectado Then

                   ' Conecta un nuevo usuario de red, con las credenciales anteriores.

                   usuarioNuevo = New WindowsIdentity(tokenDeUsuario)

                   ' Permite que el usuario conectado tome el control de la aplicación.

                   principal = New WindowsPrincipal(usuarioNuevo)

                   MetodosNativos.CloseHandle(tokenDeUsuario)

                   ' Cambia el usuario conectado en el IIS.

                   impersonalizar()

               End If

           Catch ex As Exception

               Throw New Exception("Error de ejecución. " & vbCr & _

                                   "Descripción: " & ex.Message & vbCr & _

                                   "Origen: " & ex.TargetSite.Name)

           End Try

       End Sub