Showing posts with label Security. Show all posts
Showing posts with label Security. Show all posts

Sunday, February 12, 2012

Fixing Unresolvable NTFS ACL Accounts

An interesting question came up on SuperUser the other day. The OP wanted to know how to change the account of a NTFS ACL but still retain all of the ACL settings (privileges, inheritance etc.…). This could be needed if the original account was deleted, or if the ACL was on a drive that was moved to another machine etc.…

When the ACL entry has an unresolvable account it will appear like this:

image

The first thing that came to mind was to edit the SDDL string. The SDDL is a not so easy to read string representation of the ACL. It looks like this:

Easy to read huh?

You can learn all about the format on MSDN, however the format is not important for our purpose here. All we need to do is replace the SID with the SID of the account we want.

So first lets use the Win32_Account WMI class to get a list of user account SIDs:

This will get all account types from the local machine and the domain if the machine is on a domain which could make this command take a really long time.

To make it go faster we’ll filter the query a little bit. There are a couple properties of this class we can use to reduce the run time.

  • Name
  • SidType

    (1) User
    (2) Group
    (3) Domain
    (4) Alias
    (5) WellKnownGroup
    (6) DeletedAccount
    (7) Invalid
    (8) Unknown
    (9) Computer

  • LocalAccount

So to get the SID of a local user account named NewAccount we’ll use:

To find the SID of the broken ACL entry we’ll use the Get-ACL cmdlet. The ACE’s are stored in the Access property returned by the Get-Acl object. To retrieve the SID of the entry which account is unknown we’ll use the Translate method to find that one that can’t translate to an NTAccount object. We also aren’t interested in ACE’s that are inherited so we’ll filter them out.

The invalid SIDs are stored in the array $invalidSids.

Now we can do a simple string search and replace in the SDDL string.

The last step is to set the ACL with the updated SDDL. To do this we’ll use the SetSecurityDescriptorSddlForm method.

And that’s it. The ACL is now updated with the account but all of the previous ACE settings are retained as we wanted.

image

Monday, October 3, 2011

PowerShell: Set-SecureAutoLogon

The module that controls the Windows login experience is called MSGina.dll. Code in this module is responsible for providing the Windows login screen. This code reads values from the registry key to control the experience:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon

Specifically the following values are important:

AutoAdminLogon

AutoLogonCount

DefaultUserName

DefaultPassword

DefaultDomainName

By setting the values above appropriately, you may enable automatic login for a particular user account. However DefaultPassword is a clear text string! Security vulnerabilities raised by the fact of enabling automatic login aside, the password is readable to anyone after the automatic login happens!

There is a way to store the password in a more secure way using the LsaStorePrivateData function from the Advapi32.lib Win32 module. This is a native code API which is not easily accessible to PowerShell. There is however a way to call this function which involves p/inoking (platform invoking) the native API using compiled code (c#/vb.net) using the Add-Type cmdlet introduced in PS v2. It is possible to do this PS v1 however it requires a lot more code.

So without further ado, see the Set-SecureAutoLogon fully implemented below.

[cmdletbinding()]
param (
[Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]
$Username,

[Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.Security.SecureString]
$Password,

[string]
$Domain,

[Int]
$AutoLogonCount,

[switch]
$RemoveLegalPrompt,

[System.IO.FileInfo]
$BackupFile
)

begin {

[string] $WinlogonPath = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon"
[string] $WinlogonBannerPolicyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"

[string] $Enable = 1
[string] $Disable = 0

#region C# Code to P-invoke LSA LsaStorePrivateData function.
Add-Type @"
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace ComputerSystem
{
public class LSAutil
{
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}

[StructLayout(LayoutKind.Sequential)]
private struct LSA_OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public LSA_UNICODE_STRING ObjectName;
public uint Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}

private 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)]
private static extern uint LsaRetrievePrivateData(
IntPtr PolicyHandle,
ref LSA_UNICODE_STRING KeyName,
out IntPtr PrivateData
);

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaStorePrivateData(
IntPtr policyHandle,
ref LSA_UNICODE_STRING KeyName,
ref LSA_UNICODE_STRING PrivateData
);

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private 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)]
private static extern uint LsaNtStatusToWinError(
uint status
);

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaClose(
IntPtr policyHandle
);

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaFreeMemory(
IntPtr buffer
);

private LSA_OBJECT_ATTRIBUTES objectAttributes;
private LSA_UNICODE_STRING localsystem;
private LSA_UNICODE_STRING secretName;

public LSAutil(string key)
{
if (key.Length == 0)
{
throw new Exception("Key lenght zero");
}

objectAttributes = new LSA_OBJECT_ATTRIBUTES();
objectAttributes.Length = 0;
objectAttributes.RootDirectory = IntPtr.Zero;
objectAttributes.Attributes = 0;
objectAttributes.SecurityDescriptor = IntPtr.Zero;
objectAttributes.SecurityQualityOfService = IntPtr.Zero;

localsystem = new LSA_UNICODE_STRING();
localsystem.Buffer = IntPtr.Zero;
localsystem.Length = 0;
localsystem.MaximumLength = 0;

secretName = new LSA_UNICODE_STRING();
secretName.Buffer = Marshal.StringToHGlobalUni(key);
secretName.Length = (UInt16)(key.Length * UnicodeEncoding.CharSize);
secretName.MaximumLength = (UInt16)((key.Length + 1) * UnicodeEncoding.CharSize);
}

private IntPtr GetLsaPolicy(LSA_AccessPolicy access)
{
IntPtr LsaPolicyHandle;

uint ntsResult = LsaOpenPolicy(ref this.localsystem, ref this.objectAttributes, (uint)access, out LsaPolicyHandle);

uint winErrorCode = LsaNtStatusToWinError(ntsResult);
if (winErrorCode != 0)
{
throw new Exception("LsaOpenPolicy failed: " + winErrorCode);
}

return LsaPolicyHandle;
}

private static void ReleaseLsaPolicy(IntPtr LsaPolicyHandle)
{
uint ntsResult = LsaClose(LsaPolicyHandle);
uint winErrorCode = LsaNtStatusToWinError(ntsResult);
if (winErrorCode != 0)
{
throw new Exception("LsaClose failed: " + winErrorCode);
}
}

public void SetSecret(string value)
{
LSA_UNICODE_STRING lusSecretData = new LSA_UNICODE_STRING();

if (value.Length > 0)
{
//Create data and key
lusSecretData.Buffer = Marshal.StringToHGlobalUni(value);
lusSecretData.Length = (UInt16)(value.Length * UnicodeEncoding.CharSize);
lusSecretData.MaximumLength = (UInt16)((value.Length + 1) * UnicodeEncoding.CharSize);
}
else
{
//Delete data and key
lusSecretData.Buffer = IntPtr.Zero;
lusSecretData.Length = 0;
lusSecretData.MaximumLength = 0;
}

IntPtr LsaPolicyHandle = GetLsaPolicy(LSA_AccessPolicy.POLICY_CREATE_SECRET);
uint result = LsaStorePrivateData(LsaPolicyHandle, ref secretName, ref lusSecretData);
ReleaseLsaPolicy(LsaPolicyHandle);

uint winErrorCode = LsaNtStatusToWinError(result);
if (winErrorCode != 0)
{
throw new Exception("StorePrivateData failed: " + winErrorCode);
}
}
}
}
"@
#endregion
}

process {

try {
$ErrorActionPreference = "Stop"

$decryptedPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
)

if ($BackupFile) {
# Initialize the hash table with a string comparer to allow case sensitive keys.
# This allows differentiation between the winlogon and system policy logon banner strings.
$OrigionalSettings = New-Object System.Collections.Hashtable ([system.stringcomparer]::CurrentCulture)

$OrigionalSettings.AutoAdminLogon = (Get-ItemProperty $WinlogonPath ).AutoAdminLogon
$OrigionalSettings.ForceAutoLogon = (Get-ItemProperty $WinlogonPath).ForceAutoLogon
$OrigionalSettings.DefaultUserName = (Get-ItemProperty $WinlogonPath).DefaultUserName
$OrigionalSettings.DefaultDomainName = (Get-ItemProperty $WinlogonPath).DefaultDomainName
$OrigionalSettings.DefaultPassword = (Get-ItemProperty $WinlogonPath).DefaultPassword
$OrigionalSettings.AutoLogonCount = (Get-ItemProperty $WinlogonPath).AutoLogonCount

# The winlogon logon banner settings.
$OrigionalSettings.LegalNoticeCaption = (Get-ItemProperty $WinlogonPath).LegalNoticeCaption
$OrigionalSettings.LegalNoticeText = (Get-ItemProperty $WinlogonPath).LegalNoticeText

# The system policy logon banner settings.
$OrigionalSettings.legalnoticecaption = (Get-ItemProperty $WinlogonBannerPolicyPath).legalnoticecaption
$OrigionalSettings.legalnoticetext = (Get-ItemProperty $WinlogonBannerPolicyPath).legalnoticetext

$OrigionalSettings | Export-Clixml -Depth 10 -Path $BackupFile
}

# Store the password securely.
$lsaUtil = New-Object ComputerSystem.LSAutil -ArgumentList "DefaultPassword"
$lsaUtil.SetSecret($decryptedPass)

# Store the autologon registry settings.
Set-ItemProperty -Path $WinlogonPath -Name AutoAdminLogon -Value $Enable -Force

Set-ItemProperty -Path $WinlogonPath -Name DefaultUserName -Value $Username -Force
Set-ItemProperty -Path $WinlogonPath -Name DefaultDomainName -Value $Domain -Force

if ($AutoLogonCount) {
Set-ItemProperty -Path $WinlogonPath -Name AutoLogonCount -Value $AutoLogonCount -Force
} else {
Remove-ItemProperty -Path $WinlogonPath -Name AutoLogonCount -ErrorAction SilentlyContinue
}

if ($RemoveLegalPrompt) {
Set-ItemProperty -Path $WinlogonPath -Name LegalNoticeCaption -Value $null -Force
Set-ItemProperty -Path $WinlogonPath -Name LegalNoticeText -Value $null -Force

Set-ItemProperty -Path $WinlogonBannerPolicyPath -Name legalnoticecaption -Value $null -Force
Set-ItemProperty -Path $WinlogonBannerPolicyPath -Name legalnoticetext -Value $null -Force
}
} catch {
throw 'Failed to set auto logon. The error was: "{0}".' -f $_
}

}

<#
.SYNOPSIS
Enables auto logon using the specified username and password.

.PARAMETER Username
The username of the user to automatically logon as.

.PARAMETER Password
The password for the user to automatically logon as.

.PARAMETER Domain
The domain of the user to automatically logon as.

.PARAMETER AutoLogonCount
The number of logons that auto logon will be enabled.

.PARAMETER RemoveLegalPrompt
Removes the system banner to ensure interventionless logon.

.PARAMETER BackupFile
If specified the existing settings such as the system banner text will be backed up to the specified file.

.EXAMPLE
PS C:\> Set-SecureAutoLogon `
-Username $env:USERNAME `
-Password (Read-Host -AsSecureString) `
-AutoLogonCount 2 `
-RemoveLegalPrompt `
-BackupFile "C:\WinlogonBackup.xml"

.INPUTS
None.

.OUTPUTS
None.

.NOTES
Revision History:
2011-04-19 : Andy Arismendi - Created.
2011-09-29 : Andy Arismendi - Changed to use LSA secrets to store password securely.

.LINK
http://support.microsoft.com/kb/324737

.LINK
http://msdn.microsoft.com/en-us/library/aa378750

#>

Thursday, September 29, 2011

PowerShell: Quick Easy Encryption/Decryption

Using the Windows API we can quickly encrypt and decrypt data without much hassle.

The Windows encryption API being used here is documented on MSDN here: http://msdn.microsoft.com/en-us/library/system.security.cryptography.protecteddata.aspx

# Convert a plain text string to a character array 
# and cast it to a byte array.
$bytes = "changeit".ToCharArray() | % {[byte] $_}

# Encrtyped the byte array.
$encryptedBytes = [System.Security.Cryptography.ProtectedData]::Protect(
$bytes,
$null,
[System.Security.Cryptography.DataProtectionScope]::CurrentUser)

Write-Host "Encrypted Data" -ForegroundColor Cyan
Write-Host ([string] $encryptedBytes) -ForegroundColor DarkGreen

# Unencrypt the data.
$bytes2 = [System.Security.Cryptography.ProtectedData]::Unprotect(
$encryptedBytes,
$null,
[System.Security.Cryptography.DataProtectionScope]::CurrentUser)

$bytes2 | % { $clearText += [char] $_}

Write-Host "Decrypted Data" -ForegroundColor Cyan
Write-Host ($clearText) -ForegroundColor Red

Thursday, September 1, 2011

Creating certificates with SANs using OpenSSL

IIS 7 provides some easy to use wizards to create SSL certificates, however not very powerful ones. What I needed to do was to create SSL certificates that included a x.509 V3 extension, namely subject alternative names, a.k.a SANs. What SANs do is allow the website certificate to validate incoming requests by more than one URL domain name. This is really important when the web server is running web services such as WCF services and when other web services connect to them over SSL connections as with service oriented architectures. Unless special code is added to the web services to override the default SSL validation handler routines, the common name (CN) of the certificate MUST match the incoming request URL domain. So if the request was made using an FQDN, the certificate must have the FQDN as a CN or a SAN, a IP address or just a hostname will cause an SSL validation error and the connection will fail.

SANs to the rescue… SANs support, among other things, DNS names and IP addresses. So by creating the certificate with SANs of the server FQDN and IP address, it increases the ways that other web services can connect.

There are a number of tools that can generate certificates: makecert.exe, keytool.exe (java), selfssl.exe and openssl.exe. In addition, starting with Windows Vista and Server 2008 Microsoft added the CertEnroll API which can also create certificates programmatically either through COM interfaces.

OpenSSL ended up doing exactly what I needed it to do. The process was fairly straight forward.

1) Construct an OpenSSL config file.

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = US
ST = VA
L = Somewhere
O = MyOrg
OU = MyOU
CN = MyServerName
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = MyServerName
DNS.2 = 10.0.1.34
IP.1 = 10.0.1.34
IP.2 = 192.167.20.1

2) Create x509 request with OpenSSL

openssl.exe req -x509 -nodes -days 730 -newkey rsa:2048 -keyout C:\cert.pem -out C:\cert.pem -config C:\PathToConfigFileAbove.txt

3) Create a PFX containing the keypair

openssl.exe pkcs12 -export -out C:\cert.pfx -in C:\cert.pem -name "My Cert" -passout pass:mypassword

4) Import the PFX into IIS using the import link in the server certificates area.

5) Bind the certificate to the IIS websites.

And viola, we know have a SSL certificate for IIS with SANs so we can connect using multiple domain names without certificate validation errors.