-1

I have a long running Windows Service. In its deployment we often use custom domain user logon credentials to give addition rights to the service. As the service is running the credentials of the user that the service is running as may change or expire. I trying to write a feature to notify users that the service credentials have expired and manual intervention must be taken update the logon credentials.

My question is what is the best way to detect from a service running under an expired context that its context has expired?

Thanks

KithKann
  • 31
  • 3
  • how are you currently checking in regards to the `context` if he user is valid or not..? why can't you add some additional code to that portion that checks if the domain user's password / login are valid.. are you using `PrincipalContext along with AD` – MethodMan Jan 26 '15 at 19:34
  • In some cases we use the custom logon to get access to network drives. If the context has expired then connection to the network locations will fail. I was hoping there was a more generic way to test that the services context has expired. I don't have access to the username and pass since this is handled by windows. – KithKann Jan 26 '15 at 19:50
  • I think that depends ...depending how your service is setup if it looks at the domain username\password you do have access but I am not familiar with how you're currently structured.. are you familiar with `PrincipalContext.. or can you show a code snippet without exposing your actual domain how your service currently checks..? – MethodMan Jan 26 '15 at 20:13
  • I still can not find a way forward for this issue. From this ... https://msdn.microsoft.com/en-us/library/cc875826.aspx .. (Section Managing Service Account Password Changes) "After passwords are assigned, the SCM does not verify the passwords stored in that database and the password assigned to a user account in Active Directory will continue to match." It seems that once a process is running AD will just work and there is no way to know the context has expired. This is frustrating because if the system is restarted the Service will fail to start and no warning can be given to the user. – KithKann Feb 11 '15 at 16:27

1 Answers1

0

I would take a different approach and leave the service with a restricted user, such as Network Service. Then from the service, impersonate the currently logged on user, which is tricky as none or more than one user may be logged in. This answer provides sample code to duplicate the token.

Finally use that duplicated token to access the network share, like here.

EDIT: The following code works when run as Local System account:

class AccessShareAsLoggedOnUser
{
    public static bool CopyAsLocalUser(String sourceFile, string targetFile)
    {
        uint dwSessionId, winlogonPid = 0;
        IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;

        Debug.Print("CreateProcessInConsoleSession");
        // Log the client on to the local computer.
        dwSessionId = WTSGetActiveConsoleSessionId();

        // Find the winlogon process
        var procEntry = new PROCESSENTRY32();

        uint hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hSnap == INVALID_HANDLE_VALUE)
        {
            return false;
        }

        procEntry.dwSize = (uint)Marshal.SizeOf(procEntry); //sizeof(PROCESSENTRY32);

        if (Process32First(hSnap, ref procEntry) == 0)
        {
            return false;
        }

        String strCmp = "explorer.exe";
        do
        {
            if (strCmp.IndexOf(procEntry.szExeFile) == 0)
            {
                // We found a winlogon process...make sure it's running in the console session
                uint winlogonSessId = 0;
                if (ProcessIdToSessionId(procEntry.th32ProcessID, ref winlogonSessId) &&
                    winlogonSessId == dwSessionId)
                {
                    winlogonPid = procEntry.th32ProcessID;
                    break;
                }
            }
        }
        while (Process32Next(hSnap, ref procEntry) != 0);

        //Get the user token used by DuplicateTokenEx
        WTSQueryUserToken(dwSessionId, ref hUserToken);

        hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);

        if (
            !OpenProcessToken(hProcess,
                TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY
                | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE, ref hPToken))
        {
            Debug.Print(String.Format("CreateProcessInConsoleSession OpenProcessToken error: {0}",
                Marshal.GetLastWin32Error()));
        }

        var sa = new SECURITY_ATTRIBUTES();
        sa.Length = Marshal.SizeOf(sa);

        if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa,
                (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary,
                ref hUserTokenDup))
        {
            Debug.Print(
                String.Format(
                    "CreateProcessInConsoleSession DuplicateTokenEx error: {0} Token does not have the privilege.",
                    Marshal.GetLastWin32Error()));
            CloseHandle(hProcess);
            CloseHandle(hUserToken);
            CloseHandle(hPToken);
            return false;
        }

        try
        {
            using (WindowsImpersonationContext impersonationContext =
                new WindowsIdentity(hUserTokenDup).Impersonate())
            {
            // Put your network Code here.
                File.WriteAllText(targetFile // Somewhere with right permissions like @"C:\Users\xxx\output.txt"
                      , File.ReadAllText(sourceFile)); // like @"\\server\share\existingfile.txt"

                impersonationContext.Undo();
            }
            return true;
        }
        finally
        {
            if (hUserTokenDup != IntPtr.Zero)
            {
                if (!CloseHandle(hUserTokenDup))
                {
                    // Uncomment if you need to know this case.
                    ////throw new Win32Exception();
                }
            }

            //Close handles task
            CloseHandle(hProcess);
            CloseHandle(hUserToken);
            CloseHandle(hUserTokenDup);
            CloseHandle(hPToken);
        }
    }

    [DllImport("kernel32.dll")]
    private static extern int Process32First(uint hSnapshot, ref PROCESSENTRY32 lppe);

    [DllImport("kernel32.dll")]
    private static extern int Process32Next(uint hSnapshot, ref PROCESSENTRY32 lppe);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hSnapshot);

    [DllImport("kernel32.dll")]
    private static extern uint WTSGetActiveConsoleSessionId();

    [DllImport("Wtsapi32.dll")]
    private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);

    [DllImport("kernel32.dll")]
    private static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);

    [DllImport("kernel32.dll")]
    private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

    [DllImport("advapi32", SetLastError = true)]
    [SuppressUnmanagedCodeSecurity]
    private static extern bool OpenProcessToken(IntPtr ProcessHandle, // handle to process
        int DesiredAccess, // desired access to process
        ref IntPtr TokenHandle);

    [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
    public static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
        ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
        int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);


    #region Nested type: PROCESSENTRY32

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESSENTRY32
    {
        public uint dwSize;
        public readonly uint cntUsage;
        public readonly uint th32ProcessID;
        public readonly IntPtr th32DefaultHeapID;
        public readonly uint th32ModuleID;
        public readonly uint cntThreads;
        public readonly uint th32ParentProcessID;
        public readonly int pcPriClassBase;
        public readonly uint dwFlags;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public readonly string szExeFile;
    }

    #endregion


    #region Nested type: SECURITY_ATTRIBUTES

    [StructLayout(LayoutKind.Sequential)]
    public struct SECURITY_ATTRIBUTES
    {
        public int Length;
        public IntPtr lpSecurityDescriptor;
        public bool bInheritHandle;
    }

    #endregion

    #region Nested type: SECURITY_IMPERSONATION_LEVEL

    private enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous = 0,
        SecurityIdentification = 1,
        SecurityImpersonation = 2,
        SecurityDelegation = 3,
    }

    #endregion


    #region Nested type: TOKEN_TYPE

    private enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation = 2
    }

    #endregion

    public const int READ_CONTROL = 0x00020000;

    public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000;

    public const int STANDARD_RIGHTS_READ = READ_CONTROL;
    public const int STANDARD_RIGHTS_WRITE = READ_CONTROL;
    public const int STANDARD_RIGHTS_EXECUTE = READ_CONTROL;

    public const int STANDARD_RIGHTS_ALL = 0x001F0000;

    public const int SPECIFIC_RIGHTS_ALL = 0x0000FFFF;

    public const int TOKEN_ASSIGN_PRIMARY = 0x0001;
    public const int TOKEN_DUPLICATE = 0x0002;
    public const int TOKEN_IMPERSONATE = 0x0004;
    public const int TOKEN_QUERY = 0x0008;
    public const int TOKEN_QUERY_SOURCE = 0x0010;
    public const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
    public const int TOKEN_ADJUST_GROUPS = 0x0040;
    public const int TOKEN_ADJUST_DEFAULT = 0x0080;
    public const int TOKEN_ADJUST_SESSIONID = 0x0100;

    public const int TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;

    public const int TOKEN_WRITE = STANDARD_RIGHTS_WRITE |
                                   TOKEN_ADJUST_PRIVILEGES |
                                   TOKEN_ADJUST_GROUPS |
                                   TOKEN_ADJUST_DEFAULT;

    public const uint MAXIMUM_ALLOWED = 0x2000000;

    private const uint TH32CS_SNAPPROCESS = 0x00000002;

    public static int INVALID_HANDLE_VALUE = -1;

    // handle to open access token
}
Community
  • 1
  • 1
Gerardo Grignoli
  • 14,058
  • 7
  • 57
  • 68
  • I dont really like this as it requires the service to have access to the password in order to do elevation. this password needs to be stored somewhere network service has access, hence i feel this introduces a security vulnerability. If you are doing the elevation via something like kerberos you need to delegate that control to network service which again cause a bit of a security issue – undefined Jan 26 '15 at 20:50
  • No, it doesnt. The first link duplicates the token of logged on user without knowing the password. The second link uses that token to access network share. No password needed. – Gerardo Grignoli Jan 26 '15 at 20:54
  • This link has a cleaner way to get the logged-on user's token. It uses 2 API calls which you will easily convert from c++ to c#. http://stackoverflow.com/a/19796825/97471 – Gerardo Grignoli Jan 26 '15 at 20:58
  • This approach will not work for me unfortunately. My Windows Service is often running on a Server and no users are logged in. This is why we use the Windows Service - Run As functionality that Windows provides. – KithKann Jan 27 '15 at 16:51