Jump to content

Create Local Admin with GPO (not member of the domain)


petr0s
 Share

Recommended Posts

Καλημέρα και καλή βδομάδα σε όλους!

 

Εδώ και καιρό αντιμετωπίζω το εξής πρόβλημα, κανείς δε ξέρει τον κωδικό του εκάστοτε local admin. Οπότε κάθε φορά που ένα μηχάνημα πρέπει να βγει από το domain πρέπει να το "ξυρίζω" με κάποιο utility (πχ HBD) για να κάνω reset τον κωδικό του τοπικού admin (εκτός domain).

 

Από τη μία δε δέχομαι πως μέσω GPO δε μπορείς να φυτέψεις έναν τοπικό admin που να μην ανήκει στο domain. Από την άλλη από όσο έχω καταλάβει η MS έκοψε τη δυνατότητα αυτή σαν vulnerability.

 

Ξέρει κανείς κάτι σίγουρο; Αλλιώς η μόνη σωτηρία είναι να παίξω με Logon Script, φαντάζομαι.

Link to comment
Share on other sites

Συνεχίζοντας λοιπόν... το πρόβλημα στο να φυτέψεις local admin με GPO έχει όνομα και λέγεται Cpassword Attribute το οποίο έχει χαρακτηρηστεί ως security risk από την MS καθώς μπορεί να οδηγήσει σε elevation του χρήστη και αυτό γιατί η αποθήκευση του κωδικού δεν γινόταν με ασφαλή τρόπο στο GPO. Το πολύ ωραίο και κατατοπιστικό αρθράκι υπάρχει εδώ:

 

Τα παραπάνω μας οδηγούν σε 3 δρόμους, 1) PowerShell 2) VBScript & 3)Batch όπου το 3 θα θελα να το αποφύγω.

Πιο πολύ τείνω να κάνω remote execution κάποιο PowerShell Script. Αν βρω/φτιάξω κάτι (και δουλέψει) θα το βάλω και εδώ.

Link to comment
Share on other sites

Update:

 

To PowerShell απορρίφθηκε. Απαιτείται GPO για να ανοίξει το remote execution για το PS καθώς και κάποιο ανάλογο service και... επειδή δε μου αρέσει το "ανοίγει remote execution" έφαγε πόρτα ως ιδέα. Το κομμάτι του VBScript απλά το προσπέρασα καθώς σκέφτηκα πως όσο πιο απλό τόσο πιο καλό. Ερχόμενος σε αντίθεση με το προηγούμενο post μου προχώρησα με το batch. Απλό, λιτό και λειτουργικό όμως πρέπει να ναι γρήγορο και αόρατο.

 

Έτσι λοιπόν έχουμε τον παρακάτω κώδικα:

@echo off
setlocal
set NewAdmin=Tsakalakos
set NewPassword=Tsakalakos
set NewComment=Local Admin
echo Creating user account '%NewAdmin%' ...
net.exe user "%NewAdmin%" "%NewPassword%" /add /comment:"%NewComment%"
echo Adding '%NewAdmin%' to local administrators ...
net.exe localgroup Administrators "%NewAdmin%" /add
REM *** If the "wmic.exe" line is executed, it will disable the "Must change password" option ("/logonpasswordchg:YES" in "net user /add"), because these two are mutually exclusive.
echo Setting password of '%NewAdmin%' to never expire ...
wmic.exe useraccount WHERE "Name='%NewAdmin%'" SET PasswordExpires=FALSE
pause

Το pause όποιος θέλει το σβήνει, εγώ το χω εκεί για τις δοκιμές μου που έκανα. Το όμορφο αυτό batch φτιάχνει έναν local admin ονόματι Tsakalakos με password Tsakalakos, στο Build-In Administrator Group του μηχανήματος με κωδικό που δε λήγει ποτέ.

Tested and verified σε περιβάλλον Windows 7 Pro 64 bit.

 

Αν έχει κάποιος να προσθέσει κάτι please be guest!

Link to comment
Share on other sites

μπράβο για το μοίρασμα Πέτρο!!!

το μόνο security flaw που υπάρχει εδώ είναι ότι τα στέλνεις όλα σε clear text , οπότε και τα credentials!

Παρόλα αυτά...για την δουλειά τπου το θες, μπορείς να το χρησιμοποιήσεις για τα μηχανήματα που θες μία φορά και να το απενεργοποιήσεις μέχρι να ξαναχρειαστεί!

Link to comment
Share on other sites

  • 4 weeks later...

 LAPS 1) αποθηκεύει τους κωδικούς πρόσβασης plaintext στο Active Directory database, και χρησιμοποιεί AD permissions για να περιορίσει την πρόσβαση στα passwords, 2) απαιτεί update στο Active Directory schema, 3) απαιτεί Group Policy client-side extension (an MSI package) σε ολους τους managed hosts, 4) δεν παιζει για stand-alone servers η workstations λογο Active Directory and Group Policy components, 5) μπορεί να χρησιμοποιηθεί μόνο για να διαχειριστεί το πολύ δύο local user accounts σε καθε μηχανημα, οχι παραπανω 6) εννοειτε πως δεν εχουμε τον πηγαιο κωδικα για να κανουμε αλλαγες (C++ source code LAPS client-side extension) 

 

Υπαρχουν powershell scripts που ειναι πολυ καλυτερα απο το LAPS....Και δεν χρειαζετε να κανει κανεις τιποτα απο τα παραπανω....

Link to comment
Share on other sites

Πάμε από την αρχή..

 

1)αποθηκεύει τους κωδικούς πρόσβασης plaintext στο Active Directory database, και χρησιμοποιεί AD permissions για να περιορίσει την πρόσβαση στα passwords

 

Ε και; Αν κάποιος είναι ήδη Admin στο AD τότε θα μπορούσε να κάνει τα πάντα , αν πήρε το NTDS.DIT τότε κλάφτα

 

2) απαιτεί update στο Active Directory schema

 

Fully Supported , δεν είναι 3rd party

 

3-4) απαιτεί Group Policy client-side extension (an MSI package) σε ολους τους managed hosts, 4) δεν παιζει για stand-alone servers η workstations λογο Active Directory and Group Policy components

 

Ε αυτό εννοείτε , εαν δεν ήταν managed τότε θα το έκανες με το ΧΕΡΙ έτσι και αλλιώς.

 

5) μπορεί να χρησιμοποιηθεί μόνο για να διαχειριστεί το πολύ δύο local user accounts σε καθε μηχανημα

 

Καλά εαν υπάρχουν περισσότερα των δυο account ,κάτι γίνεται λάθος

 

6) εννοειτε πως δεν εχουμε τον πηγαιο κωδικα για να κανουμε αλλαγες (C++ source code LAPS client-side extension)

 

Καλά δεν έχουμε τον κώδικα για το τίποτε στην MS , μαθημένοι είμαστε

 

Υ.Γ. αν έχεις πάντως scripts κάντα μια copy να τα έχουμε

Link to comment
Share on other sites

Όταν αποθηκεύεις σε plaintext τα passwords τότε δεν υπάρχει "e και"..Ακόμα και όταν είσαι admin, εδώ κολλάει το σχόλιο σου "κάτι γίνετε λάθος". Για το δεύτερο δεν είμαι σίγουρος αλλα σχόλια στο internet που έχω διαβάσει αυτό λένε...Όταν έχεις πολλά μηχανήματα νομίζω ότι το "xερατo" το ξεχνάς και ψάχνεις μια λύση που θα σε κάλυψη...Σε μεγάλες εταιρίες/οργανισμούς υπάρχουν αρκετά μηχανήματα που μπορεί να έχεις παραπάνω από δυο admin. Συνφωνω μαζί σου αλλα καμια φορα δεν γίνετε αλλιώς...

 

Update-PasswordArchive.ps1

####################################################################################
#.Synopsis 
#    Resets the password of a local user account with a random password which is 
#    then encrytped with your pubic key certificate. The plaintext password is 
#    displayed with the Recover-PasswordArchive.ps1 script. 
#
#.Description 
#    Resets the password of a local user account with a 15-25 character, random, 
#    complex password, which is encrytped with your own pubic key certificate. 
#    Recovery of the encrypted password from the file requires possession of the
#    private key corresponding to the chosen public key certificate.  The password
#    is never transmitted or stored in plaintext anywhere. The plaintext password 
#    is recovered with the companion Recover-PasswordArchive.ps1 script.  The
#    script must be run with administrative or local System privileges.  The
#    script is also not compatible with FIPS Mode being enabled in Windows.
#
#.Parameter CertificateFilePath 
#    The local or UNC path to the .CER file containing the public key 
#    certificate which will be used to encrypt the password.  The .CER
#    file can be DER- or Base64-encoded.  (But note that the private
#    key for the certificate cannot be managed by a Cryptography Next
#    Generation (CNG) Key Storage Provider, hence, do not use the Microsoft 
#    Software Key Storage Provider in the template for the certificate.)
#    In the properties of this certificate, under 'Key Usage', it must
#    have 'Key Encipherment' listed as one of the uses.  This is set on
#    the certificate template used by the CA: on the Request Handling tab
#    the template must include Encryption as an allowed purpose.
#
#.Parameter LocalUserName
#    Name of the local user account on the computer where this script is run
#    whose password should be reset to a 15-25 character, complex, random password.
#    Do not include a "\" or "@" character, only local accounts are supported.
#    Defaults to "Guest", but any name can be specified.
#
#.Parameter PasswordArchivePath
#    The local or UNC path to the folder where the archive files containing
#    encrypted passwords will be stored.
#
#.Parameter MinimumPasswordLength
#    The minimum length of the random password.  Default is 15.  The exact length
#    used is randomly chosen to increase the workload of an attacker who can see
#    the contents of this script.  Maximum password length defaults to 25.  The
#    smallest acceptable minimum length is 4 due to complexity requirements.
#
#.Parameter MaximumPasswordLength
#    The maximum length of the random password.  Default is 25.  Max is 127.
#    The minimum and maximum values can be identical.    
#
#.Example 
#    .\Update-PasswordArchive.ps1 -CertificateFilePath C:\certificate.cer -PasswordArchivePath C:\folder
#
#    Resets the password of the Guest account, encrypts that password 
#    with the public key in the certificate.cer file, and saves the encrypted
#    archive file in C:\folder.  Choose a different account with -LocalUserName.
#
#.Example 
#    .\Update-PasswordArchive.ps1 -CertificateFilePath \\server\share\certificate.cer -PasswordArchivePath \\server\share
#
#    UNC network paths can be used instead of local file system paths.  Password 
#    is not reset until after network access to the shared folder is confirmed.
#    The certificate and archive folders do not have to be the same.
#
#.Example 
#    .\Update-PasswordArchive.ps1 -LocalUserName HelpDeskUser -CertificateFilePath \\server\share\certificate.cer -PasswordArchivePath \\server\share
#
#    The local Guest account's password is reset by default, but any
#    local user name can be specified instead.
#
#
#Requires -Version 2.0 
#
#.Notes 
#  Author: Jason Fossen, Enclave Consulting LLC (http://www.sans.org/sec505)  
# Version: 5.3
# Updated: 4.Aug.2015
#   LEGAL: PUBLIC DOMAIN.  SCRIPT PROVIDED "AS IS" WITH NO WARRANTIES OR GUARANTEES OF 
#          ANY KIND, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY AND/OR FITNESS FOR
#          A PARTICULAR PURPOSE.  ALL RISKS OF DAMAGE REMAINS WITH THE USER, EVEN IF
#          THE AUTHOR, SUPPLIER OR DISTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF
#          ANY SUCH DAMAGE.  IF YOUR STATE DOES NOT PERMIT THE COMPLETE LIMITATION OF
#          LIABILITY, THEN DELETE THIS FILE SINCE YOU ARE NOW PROHIBITED TO HAVE IT.
####################################################################################


Param ($CertificateFilePath = ".\PublicKeyCert.cer", $LocalUserName = "Guest", $PasswordArchivePath = ".\", $MinimumPasswordLength = 15, $MaximumPasswordLength = 25) 


####################################################################################
# Function Name: Generate-RandomPassword
#   Argument(s): Integer for the desired length of password.
#       Returns: Pseudo-random complex password that has at least one of each of the 
#                following character types: uppercase letter, lowercase letter, 
#                number, and legal non-alphanumeric for a Windows password.
#         Notes: If the argument/password is less than 4 characters long, the 
#                function will return a 4-character password anyway.  Otherwise, the
#                complexity requirements won't be satisfiable.  Integers are 
#                generated, converted to Unicode code points (chars), and then
#                encoded as a UTF16LE string so that the function can be easily 
#                modified by users who are not using US-EN keyboards.  For the
#                sake of script compatibility, various characters are excluded
#                even though this reduces randomness.  
####################################################################################
function Generate-RandomPassword ($length = 15) 
{
    If ($length -lt 4) { $length = 4 }   #Password must be at least 4 characters long in order to satisfy complexity requirements.

    #Use the .NET crypto random number generator, not the weaker System.Random class with Get-Random:
    $RngProv = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
    [byte[]] $onebyte = @(255)
    [Int32] $x = 0

    Do {
        [byte[]] $password = @() 
        
        $hasupper =     $false    #Has uppercase letter character flag.
        $haslower =     $false    #Has lowercase letter character flag.
        $hasnumber =    $false    #Has number character flag.
        $hasnonalpha =  $false    #Has non-alphanumeric character flag.
        $isstrong =     $false    #Assume password is not complex until tested otherwise.
        
        For ($i = $length; $i -gt 0; $i--)
        {                                                         
            While ($true)
            {   
                #Generate a random US-ASCII code point number.
                $RngProv.GetNonZeroBytes( $onebyte ) 
                [Int32] $x = $onebyte[0]                  
                if ($x -ge 32 -and $x -le 126){ break }   
            }
            
            # Even though it reduces randomness, eliminate problem characters to preserve sanity while debugging.
            # If you're worried, increase the length of the password or comment out the desired line(s):
            If ($x -eq 32) { $x++ }    #Eliminates the space character; causes problems for other scripts/tools.
            If ($x -eq 34) { $x-- }    #Eliminates double-quote; causes problems for other scripts/tools.
            If ($x -eq 39) { $x-- }    #Eliminates single-quote; causes problems for other scripts/tools.
            If ($x -eq 47) { $x-- }    #Eliminates the forward slash; causes problems for net.exe.
            If ($x -eq 96) { $x-- }    #Eliminates the backtick; causes problems for PowerShell.
            If ($x -eq 48) { $x++ }    #Eliminates zero; causes problems for humans who see capital O.
            If ($x -eq 79) { $x++ }    #Eliminates capital O; causes problems for humans who see zero. 
            
            $password += [System.BitConverter]::GetBytes( [System.Char] $x ) 

            If ($x -ge 65 -And $x -le 90)  { $hasupper = $true }   #Non-USA users may wish to customize the code point numbers by hand,
            If ($x -ge 97 -And $x -le 122) { $haslower = $true }   #which is why we don't use functions like IsLower() or IsUpper() here.
            If ($x -ge 48 -And $x -le 57)  { $hasnumber = $true } 
            If (($x -ge 32 -And $x -le 47) -Or ($x -ge 58 -And $x -le 64) -Or ($x -ge 91 -And $x -le 96) -Or ($x -ge 123 -And $x -le 126)) { $hasnonalpha = $true } 
            If ($hasupper -And $haslower -And $hasnumber -And $hasnonalpha) { $isstrong = $true } 
        } 
    } While ($isstrong -eq $false)

    #$RngProv.Dispose() #Not compatible with PowerShell 2.0.

    ([System.Text.Encoding]::Unicode).GetString($password) #Make sure output is encoded as UTF16LE. 
}




####################################################################################
# Returns $True if certificate's public key may be used by this script.  If $False,
# check the certificate template being used by your Certification Authority (CA): 
# the template must have Encryption listed as an allowed purpose on the Request 
# Handling tab in the properties of the template.
####################################################################################
function Confirm-KeyEnciphermentKeyUsage ([System.Security.Cryptography.X509Certificates.X509Certificate2] $Cert )
{
    #.DESCRIPTION
    #  The properties of a certificate include a property named "Key Usage" (OID = 2.5.29.15).
    #  One of the possible Key Usage items is named "KeyEncipherment" (http://tools.ietf.org/html/rfc5280#section-4.2.1.3).
    #  This function returns $True if the $Cert has the KeyEncipherment key usage; returns $False otherwise.

    $KeyEncipherFlag = [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyEncipherment

    $result = $Cert.Extensions | where { $_.oid.value -eq '2.5.29.15' } | where { ($_.keyusages -band $KeyEncipherFlag) -eq $KeyEncipherFlag }

    if ($result) { $True } else { $False } 
}




####################################################################################
# Returns true if password reset accepted, false if there is an error.
# Only works on local computer, but can be modified to work remotely too.
####################################################################################
Function Reset-LocalUserPassword ($UserName, $NewPassword)
{
    Try 
    {
        $ADSI = [ADSI]("WinNT://" + $env:ComputerName + ",computer")
        $User = $ADSI.PSbase.Children.Find($UserName)
        $User.PSbase.Invoke("SetPassword",$NewPassword)
        $User.PSbase.CommitChanges()
        $User = $null 
        $ADSI = $null
        $True
    }
    Catch
    { $False } 
}



####################################################################################
# Writes to console, writes to Application event log, optionally exits.
# Event log: Application, Source: "PasswordArchive", Event ID: 9013
####################################################################################
function Write-StatusLog ( $Message, [Switch] $Exit )
{
    # Define the Source attribute for when this script writes to the Application event log.
    New-EventLog -LogName Application -Source PasswordArchive -ErrorAction SilentlyContinue

    "`n" + $Message + "`n"

#The following here-string is written to the Application log only when there is an error, 
#but it contains information that could be useful to an attacker with access to the log.
#The data is written for troubleshooting purposes, but feel free change it if concerned.
#It does not contain any passwords of course.
$ErrorOnlyLogMessage = @"
$Message 

CurrentPrincipal = $($CurrentPrincipal.Identity.Name)

CertificateFilePath = $CertificateFilePath 

LocalUserName = $LocalUserName

PasswordArchivePath = $PasswordArchivePath

ArchiveFileName = $filename
"@

    if ($Exit)
    { write-eventlog -logname Application -source PasswordArchive -eventID 9013 -message $ErrorOnlyLogMessage -EntryType Error }
    else
    { write-eventlog -logname Application -source PasswordArchive -eventID 9013 -message $Message -EntryType Information }

    if ($Exit) { exit } 
}


# Sanity check the two password lengths:
if ($MinimumPasswordLength -le 3) { $MinimumPasswordLength = 4 } 
if ($MaximumPasswordLength -gt 127) { $MaximumPasswordLength = 127 } 
if ($MinimumPasswordLength -gt 127) { $MinimumPasswordLength = 127 } 
if ($MaximumPasswordLength -lt $MinimumPasswordLength) { $MaximumPasswordLength = $MinimumPasswordLength }



# Confirm that this process has administrative privileges to reset a local password.
$CurrentWindowsID = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$CurrentPrincipal = new-object System.Security.Principal.WindowsPrincipal($CurrentWindowsID)
if (-not $? -or -not $CurrentPrincipal.IsInRole("Administrators")) 
   { write-statuslog -m "ERROR: This process lacks the privileges necessary to reset a password." -exit }



# Confirm that the target local account exists and that ADSI is accessible.
if ($LocalUserName -match '[\\@]')  { write-statuslog -m "ERROR: This script can only be used to reset the passwords of LOCAL user accounts, please specify a simple username without an '@' or '\' character in it." -exit }  
try 
{ 
    $ADSI = [ADSI]("WinNT://" + $env:ComputerName + ",computer") 
    $User = $ADSI.PSbase.Children.Find($LocalUserName)
    $User = $null
    $ADSI = $null 
}
catch 
{ write-statuslog -m "ERROR: Local user does not exist: $LocalUserName" -exit } 



# Get the public key certificate.
if (Resolve-Path -Path $CertificateFilePath)
{ $CertificateFilePath = $(Resolve-Path -Path $CertificateFilePath).Path }
else
{ write-statuslog -m "ERROR: Cannot resolve path to certificate file: $CertificateFilePath" -exit }


if ($CertificateFilePath -ne $null -and $(test-path -path $CertificateFilePath))
{
    if ($CertificateFilePath -like '*.p7b')
    { Write-StatusLog -m "ERROR: Certificate must be a DER- or BASE64-encoded X.509 certificate, not PKCS #7." -exit }
     
    [Byte[]] $certbytes = get-content -encoding byte -path $CertificateFilePath 
    $cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2(,$certbytes)
    if (-not $? -or ($cert.GetType().fullname -notlike "*X509*")) 
       { write-statuslog -m "ERROR: Invalid or corrupt certificate file: $CertificateFilePath" -exit }  
}
else
{ write-statuslog -m "ERROR: Could not find the certificate file: $CertificateFilePath" -exit }


# Confirm that the certificate has "Key Encipherment" as an allowed Key Usage in the properties of the cert.
if (-not (Confirm-KeyEnciphermentKeyUsage -Cert $cert))
{  write-statuslog -m "ERROR: This public key certificate cannot be used because it does not have 'Key Encipherment' listed under 'Key Usage' in its properties.  Check the certificate template used by the Certification Authority (CA) to create your certificate and confirm that 'Encryption' is listed as one of the allowed purposes on the 'Request Handling' tab in the properties of the template." -Exit }


# Construct name of the archive file, whose name will also be used as a nonce.
# Ticks string will be 18 characters, certificate SHA* hash will be at least 40 characters, plus some user/computer name bytes.
$filename = $env:computername + "+" + $LocalUserName + "+" + $(get-date).ticks + "+" + $cert.thumbprint
if ($filename.length -le 60) { write-statuslog -m "ERROR: The archive file name is invalid (too short): $filename " -exit } 


# Generate and test new random password with min and max lengths.
$newpassword = "ConfirmThatNewPasswordIsRandom"

if ($MinimumPasswordLength -eq $MaximumPasswordLength)
{  
    $newpassword = Generate-RandomPassword -Length $MaximumPasswordLength
} 
else
{ 
    $newpassword = Generate-RandomPassword -Length $(Get-Random -Minimum $MinimumPasswordLength -Maximum $MaximumPasswordLength) 
}

# Users outside USA might modify the Generate-RandomPassword function, hence this check.
if ($newpassword -eq "ConfirmThatNewPasswordIsRandom") 
{ write-statuslog -m "ERROR: Password generation failure, password not reset." -exit } 


# Construct the array of bytes to be hashed and then encrypted.
# Prepend first 60 characters of the $filename as a nonce to the new password.
# This is done for the sake of integrity checking later in the Recover-PasswordArchive.ps1 script.
[byte[]] $bytes = @() 
$bytes += [System.Text.Encoding]::Unicode.GetBytes( $filename.substring(0,60) ) 
$bytes += [System.Text.Encoding]::Unicode.GetBytes( $newpassword ) 


# Get the SHA256 hash of the bytes to be encrypted and prepend them to $bytes array.
$SHA256Hasher = [System.Security.Cryptography.SHA256]::Create()
[Byte[]] $hash = $SHA256Hasher.ComputeHash( $bytes )  #Produces 32 bytes.
$bytes = ($hash += $bytes) 
$SHA256Hasher = $null  #.Dispose() is not supported in PowerShell 2.0


# Hence, the $bytes array at this point now has this format:
#     32 bytes : SHA256 hash of the rest of the bytes.
#    120 bytes : Computer+User+Ticks+Thumbprint (60 two-byte chars = 120 bytes).
#     ?? bytes : All remaining bytes are for the UTF16 password, which is variable in length.


# Encrypt $bytes with 256-bit Rijnael key (can't use AES directly, it requires .NET 3.5 or later).
$Rijndael = New-Object -TypeName System.Security.Cryptography.RijndaelManaged
$Rijndael.GenerateKey()
$Rijndael.GenerateIV()
$Rijndael.Padding = [System.Security.Cryptography.PaddingMode]::ISO10126 
$Encryptor = $Rijndael.CreateEncryptor()
$MemoryStream = New-Object -TypeName System.IO.MemoryStream
$StreamMode = [System.Security.Cryptography.CryptoStreamMode]::Write  
$CryptoStream = New-Object -TypeName System.Security.Cryptography.CryptoStream -ArgumentList $MemoryStream,$Encryptor,$StreamMode
$CryptoStream.Write($bytes, 0, $bytes.Length) #Fill MemoryStream with encrypted bytes.
$CryptoStream.Dispose() #Must come after the Write() or else "padding error" when decrypting.
[byte[]] $EncryptedBytes = $MemoryStream.ToArray() 
$MemoryStream.Dispose()


# Encrypt the Rijndael key and IV (32 + 16 bytes) with the public key, then
# append the Rijndael-encrypted payload to the end of it:
[byte[]] $cipherbytes = $cert.publickey.key.encrypt(($Rijndael.Key + $Rijndael.IV),$false) #Must be $false for smart card to work.
if (-not $? -or $cipherbytes.count -lt 40) { write-statuslog -m "ERROR: Encryption of symmetric key failed, password not reset." -exit } 
$cipherbytes = $cipherbytes + $EncryptedBytes
if (-not $? -or $cipherbytes.count -lt 280) { write-statuslog -m "ERROR: Encryption of payload failed, password not reset." -exit } 


# Hence, the $cipherbytes array at this point now has this format (280 byte min):
#     ?? bytes : Encrypted Rijndael key and IV, variable in length, same size as public key, but at least 128 bytes for 1024-bit pub key.
#     32 bytes : SHA256 hash of the rest of the bytes.
#    120 bytes : Computer+User+Ticks+Thumbprint (60 two-byte chars = 120 bytes).
#     ?? bytes : All remaining bytes are for the UTF16 password, which is variable in length.


# Must save encrypted password file before resetting the password, confirm before proceeding.
if (Resolve-Path -Path $PasswordArchivePath)
{ $PasswordArchivePath = $(Resolve-Path -Path $PasswordArchivePath).Path }
else
{ write-statuslog -m "ERROR: Cannot resolve path to archive folder: $PasswordArchivePath" -exit }

if (-not $(test-path -pathtype container -path $PasswordArchivePath)) 
{ write-statuslog -m "ERROR: Archive path not accessible: $PasswordArchivePath" -exit } 

if ($PasswordArchivePath -notlike "*\") { $PasswordArchivePath = $PasswordArchivePath + "\" } 

$cipherbytes | set-content -encoding byte -path ($PasswordArchivePath + $filename) 
if (-not $?) { write-statuslog -m "ERROR: Failed to save archive file, password not reset." -exit } 

if (-not $(test-path -pathtype leaf -path $($PasswordArchivePath + $filename))){ write-statuslog -m "ERROR: Failed to find archive file, password not reset." -exit } 



# Attempt to reset the password.
if ( Reset-LocalUserPassword -UserName $LocalUserName -NewPassword $newpassword )
{
    remove-variable -name newpassword  #Just tidying up, not really necessary at this point...
    write-statuslog -m "SUCCESS: $LocalUserName password reset and archive file saved."  
}
else
{
    # Write the RESET-FAILURE file to the archive path; these failure files are used by the other scripts too.
    $filename = $env:computername + "+" + $LocalUserName + "+" + $(get-date).ticks + "+PASSWORD-RESET-FAILURE"
    "ERROR: Failed to reset password after creating a success file:`n`n" + $error[0] | set-content -path ($PasswordArchivePath + $filename) 
    write-statuslog -m "ERROR: Failed to reset password after creating a success file:`n`n $error[0]" -exit 
} 


# FIN

Recover-PasswordArchive.ps1

####################################################################################
#.Synopsis 
#    Recover the plaintext password from an encrypted file originally
#    created with the companion script named Update-PasswordArchive.ps1. 
#
#.Description 
#    Recover the plaintext password from an encrypted file originally
#    created with the companion script named Update-PasswordArchive.ps1. The
#    file is encrypted with a public key chosen by the administrator. The
#    password generated by Update-PasswordArchive.ps1 is random.  Recovery
#    of the encrypted password from the file requires possession of the
#    private key corresponding to the chosen public key certificate.  (Note
#    that CNG key storage providers are not supported, hence, do not use the
#    Microsoft Software Key Storage Provider in the template for the original
#    certificate request.)  
#
#.Parameter PasswordArchivePath 
#    The local or UNC path to where the encrypted password files are kept. 
#
#.Parameter ComputerName
#    Name of the computer with the local account whose password was reset
#    and whose password was encrypted and saved to a file.  The computer
#    name will match the names of files in the PasswordArchivePath.  This
#    parameter can accept a computer name with a wildcard in it.
#
#.Parameter UserName
#    Name of the local user account whose password was reset and whose password
#    was encrypted and saved to a file.  The username will match the names of
#    files in the PasswordArchivePath.  Default is "Administrator".  If you
#    are not certain, just enter "*" and the last reset will be used, whatever
#    username that may be, or you might use the -ShowAll switch instead.
#
#.Parameter ShowAll
#    Without this switch, only the most recent plaintext password is shown.
#    With this switch, all archived passwords for the computer are shown.
#    This might be necessary when the passwords of multiple local user 
#    accounts are being managed with these scripts.
#
#
#.Example 
#    .\Recover-PasswordArchive.ps1 -ComputerName LAPTOP47 -UserName Administrator
#
#    Displays in plaintext the last recorded password updated on LAPTOP47.
#    The user running this script must have loaded into their local cache
#    the certificate AND private key corresponding to the certificate used
#    to originally encrypt the password archive files in the present
#    working directory.  A smart card may be used instead.  The default 
#    username is "Administrator", so this argument was not actually required.
#
#.Example 
#    .\Recover-PasswordArchive.ps1 -PasswordArchivePath \\server\share -ComputerName WKS*
#
#    Instead of the present working directory of the script, search the
#    password archive files located in \\server\share.  Another local
#    folder can be specified instead of a UNC network path.  The wildcard
#    in the computer name will show the most recent password updates for
#    all matching computer names in \\server\share for the Administrator.
# 
#.Example 
#    .\Recover-PasswordArchive.ps1 -PasswordArchivePath \\server\share -ComputerName LAPTOP47 -ShowAll
#
#    Instead of showing only the last password update for the Administrator account, 
#    show all archived passwords in the \\server\share folder for LAPTOP47.
#
# 
#Requires -Version 2.0 
#
#.Notes 
#  Author: Jason Fossen, Enclave Consulting LLC (http://www.sans.org/sec505)  
# Version: 5.2
# Updated: 10.Jul.2015
#   LEGAL: PUBLIC DOMAIN.  SCRIPT PROVIDED "AS IS" WITH NO WARRANTIES OR GUARANTEES OF 
#          ANY KIND, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY AND/OR FITNESS FOR
#          A PARTICULAR PURPOSE.  ALL RISKS OF DAMAGE REMAINS WITH THE USER, EVEN IF
#          THE AUTHOR, SUPPLIER OR DISTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF
#          ANY SUCH DAMAGE.  IF YOUR STATE DOES NOT PERMIT THE COMPLETE LIMITATION OF
#          LIABILITY, THEN DELETE THIS FILE SINCE YOU ARE NOW PROHIBITED TO HAVE IT.
####################################################################################

Param ($PasswordArchivePath = ".\", $ComputerName = "$env:computername", $UserName = "Guest", [Switch] $ShowAll) 


# Rijndael decryption function used after $Key is decrypted with private key of cert.
# Why not use AES explicitly?  That requires .NET Framework 3.5 or later.
function Decrypt-KeyPlusIV ([byte[]] $Key, [byte[]] $IV, [byte[]] $CipherBytes)
{
    $Rijndael = New-Object -TypeName System.Security.Cryptography.RijndaelManaged
    $Rijndael.Key = $Key
    $Rijndael.IV = $IV 
    $Rijndael.Padding = [System.Security.Cryptography.PaddingMode]::ISO10126 
    $Decryptor = $Rijndael.CreateDecryptor()
    $MemoryStream = New-Object -TypeName System.IO.MemoryStream
    $StreamMode = [System.Security.Cryptography.CryptoStreamMode]::Write
    $CryptoStream = New-Object -TypeName System.Security.Cryptography.CryptoStream -ArgumentList $MemoryStream,$Decryptor,$StreamMode
    $CryptoStream.Write($CipherBytes, 0, $CipherBytes.Count) 
    $CryptoStream.Dispose() #Must come after the Write() or else "padding error" when decrypting.
    [byte[]] $MemoryStream.ToArray()
    $MemoryStream.Dispose()
}



# Construct and test path to encrypted password files.
$PasswordArchivePath = $(resolve-path -path $PasswordArchivePath).path
if ($PasswordArchivePath -notlike "*\") { $PasswordArchivePath = $PasswordArchivePath + "\" } 
if (-not $(test-path -path $PasswordArchivePath)) { "`nERROR: Cannot find path: " + $PasswordArchivePath + "`n" ; exit } 


# Get encrypted password files and sort by name, which sorts by tick number, i.e., by creation timestamp.
$files = @(dir ($PasswordArchivePath + "$ComputerName+*+*+*") | sort Name) 
if ($files.count -eq 0) { "`nERROR: No password archives for " + $ComputerName + "`n" ; exit } 


# Filter by UserName and get the latest archive file only, unless -ShowAll is used.
if (-not $ShowAll)
{ 
    $files = @( $files | where { $_.name -like "*+$($UserName.Trim())+*+*" } )
    if ($files.count -eq 0) { "`nERROR: No password archives for " + $ComputerName + "\" + $UserName + "`n" ; exit }  
    $files = $files[-1]
} 


# Load the current user's certificates and private keys.
try
{
    $readonlyflag = [System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly
    $currentuser =  [System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser
    $usercertstore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $currentuser
    $usercertstore.Open($readonlyflag) 
    $usercertificates = $usercertstore.Certificates
}
catch
{
    "`nERROR: Could not open your certificates store. `n"
    exit
}
finally
{
    $usercertstore.Close() 
}

if ($usercertificates.count -eq 0) { "`nERROR: You have no certificates or private keys.`n" ; exit }



# Process each encrypted password archive file.
foreach ($lastfile in $files) `
{
    $output = ($output = " " | select-object ComputerName,FilePath,UserName,TimeStamp,Thumbprint,Valid,StatusMessage,Password)

    $output.ComputerName = $($lastfile.Name -split '\+')[0]
    $output.FilePath =     $lastfile.fullname
    $output.UserName =     $($lastfile.Name -split '\+')[1]
    $output.TimeStamp =    [DateTime][Int64]$($lastfile.Name -split '\+')[2]
    $output.Valid =        $false  #Assume password recovery will fail.
    $output.Thumbprint =   $($lastfile.Name -split '\+')[3]


    # Check for password reset failure files.
    if ($output.Thumbprint -eq "PASSWORD-RESET-FAILURE") 
    { 
        $output.StatusMessage = "ERROR: Try to use prior password(s) for this computer."
        $output.Valid = $false
        $output
        continue 
    } 


    # Read in password archive binary file.
    [byte[]] $ciphertext = get-content -encoding byte -path $lastfile.fullname 
    if (-not $?) 
    { 
        $output.StatusMessage = "ERROR: Failed to read " + $lastfile.fullname
        $output.Valid = $false
        $output
        continue 
    }  


    # Sanity check size of archive file just read in (test with 1-char password and 1024-bit pub key).
    if ($ciphertext.count -lt 287) 
    { 
        $output.StatusMessage = "ERROR: Too small to be a valid file: " + $lastfile.fullname
        $output.Valid = $false
        $output
        continue 
    }  


    # Load the correct certificate and test for possession of private key.
    $thecert = $usercertificates | where { $_.thumbprint -eq $output.thumbprint } 
    if (-not $thecert.hasprivatekey) 
    { 
        $output.StatusMessage = "ERROR: You do not have the private key for this certificate."
        $output.Valid = $false
        $output
        continue
    } 


    # Test to confirm that the private key can be accessed, not just that it exists.  The
    # problem is that it is not a trivial task to allow .NET or PowerShell to use
    # private keys managed by Crytography Next Generation (CNG) key storage providers, hence,
    # these scripts are only compatible with the older Cryptographic Service Providers (CSPs), such
    # as the "Microsoft Enhanced Cryptographic Provider", but not the newer CNG "Microsoft
    # Software Key Storage Provider".  Sorry...
    if ($thecert.privatekey -eq $null) 
    { 
        $output.StatusMessage = "ERROR: This script is not compatible with CNG key storage providers."
        $output.Valid = $false
        $output
        continue
    } 


    # Size of the public key is needed to compute sizes of fields in the archive file.
    $pubkeysize = $thecert.publickey.key.keysize / 8   #Size in bytes.


    # Extract encrypted Key+IV from the ciphertext and decrypt them with private key.
    # If this raises a "Bad Key" error, then likely the certificate originally used to encrypt the data
    # does not have "Key Encipherment" listed under "Key Usage" in the properties of the cert.  The
    # cert template must include Encryption as an allowed purpose on the Request Handling tab.
    [byte[]] $KeyPlusIV = $thecert.privatekey.decrypt( [byte[]] @($ciphertext[0..$($pubkeysize - 1)]), $false)  #Must be $false for smart card to work.
    
    if (-not $? -or $KeyPlusIV.count -lt 48) 
    { 
        $output.StatusMessage = "ERROR: Decryption of symmetric key and IV failed, possibly because the certificate has a Key Usage which does not allow Key Encipherment.  Check the certificate template being used by your Certification Authority (CA): the template must have Encryption listed as an allowed purpose on the Request Handling tab in the properties of the template." 
        $output.Valid = $false
        $output
        continue 
    }
    

    # Remove Key+IV from $ciphertext to make offset calculations easier (can ignore pub key size now).
    $ciphertext = $ciphertext[$pubkeysize..($ciphertext.count - 1)] 


    # Decrypt the rest of the file with the Key and IV.
    [byte[]] $plaintextout = Decrypt-KeyPlusIV -Key $KeyPlusIV[0..31] -IV $KeyPlusIV[32..47] -CipherBytes $ciphertext
 
    if (-not $? -or $plaintextout.count -lt 152) #32-byte hash and 120-byte path at least. 
    { 
        $output.StatusMessage = "ERROR: Decryption of hash failed, possible archive file corruption." 
        $output.Valid = $false
        $output
        continue 
    }


    # Parse out the SHA256 hash, filename nonce, and password bytes (UTF16 = 2 bytes per char).
    [byte[]] $savedhash = $plaintextout[0..31]
    [byte[]] $savedpath = $plaintextout[32..151]
    [byte[]] $password =  $plaintextout[152..($plaintextout.Count - 1)] 
    

    # Convert password byte array back into UTF16LE.
    $output.Password = ([System.Text.Encoding]::Unicode).GetString($password) 
    if ($?) { $output.StatusMessage = "Success" } 


    # Confirm that the saved hash matches the current hash.
    $SHA256Hasher = [System.Security.Cryptography.SHA256]::Create()
    [Byte[]] $newhash = $SHA256Hasher.ComputeHash( $savedpath + $password ) 
    $SHA256Hasher = $null   #.Dispose() not supported in PowerShell 2.0


    if (compare-object -ReferenceObject $savedhash -DifferenceObject $newhash)
    { 
        $output.Valid = $false
        $output.StatusMessage = "ERROR: Hash integrity check failure, but password may still work."
        $output
        continue
    } 
    else
    { 
        #Compare-Object only produces output if there is a difference.
        $output.Valid = $true
    } 


    # Confirm that archive file name matches the path string in the file.
    # This string can also be used for troubleshooting if the files are renamed.
    $savedpathstring = ([System.Text.Encoding]::Unicode).GetString($savedpath)

    if ($lastfile.name -notlike ($savedpathstring + "*")) 
    { 
        $output.Valid = $false 
        $output.StatusMessage = "ERROR: Path check failure, but password may still work."
        $output
        continue 
    } 


    # Emit completed object, goto next archive file.
    $output
}



# FIN
Link to comment
Share on other sites

Ωραία η ιδέα των certificate για το generate , πάντως , επειδή αναγκάστηκα να ψάξω μια λύση για κάτι αρκετά πιο πολύπλοκο κατέληξα πως η local password db δεν θεωρείται από κανέναν σοβαρή. Τι θέλω να πω , φαντάσου το εξής σενάριο : είσαι hosting provider και πρέπει να δώσεις μια λύση για password reset στους tenants. Δεν θέλεις να ξέρεις το Password (φυσικά το να το ξέρεις είναι απαράδεκτο) , θέλεις απλά κάποιος που έχει ένα account σε μια άλλη DB να μπορεί να  κάνει reset το local admin password του VM. Αυτό παίζει μόνο στο Azure , δεν υπάρχει σαν feature στις εκδόσεις του Server. Μάλιστα δεν υπάρχει και κανένα προιόν που να κάνει reset σε local accounts από διάσπαρτα μηχανάκια, ούτε ο Forefront Identity Manager.

 

Όταν έχεις βέβαια ένα domain τα πράγματα γίνονται πολύ πιο εύκολα , είτε με script είτε με το LAPS είτε με 3rd party, ενδιαφέρον επίσης έχει και η υλοποίηση του Cloud Platform System από την MS και τη Dell καθώς αναφέρεται ως feature το local admin password management μέσω scripts (είναι μέσα στο πακέτο του 1.5M Euro).

 

Αυτό με το Certificate πάντως ενδιαφέρον , θα δω μήπως μπορώ να γράψω κάτι παρόμοιο στο Powershell και να μοιράσω certs σε USB για το recover.

Link to comment
Share on other sites

  • 2 weeks later...

Επίσης να σημειωθεί ότι το LAPS αποτελούσε μέχρι πρότινος λύση που την παρείχε η Microsoft σε πολύ Premium πελάτες, θεωρώ ότι αυτοί πρέπει να είναι ιδιαίτερα απαιτήτικοί στο κομμάτι της ασφάλειας διαφορετικά δεν θα γινόταν αποδεκτή η λύση, μιλάμε για τράπεζες και τεράστιους οργανισμούς με απίστευτες απαιτήσεις.

 

My 2 cents ;-)

 

Τα PS Scripts παραπάνω είναι πολύ ενδιαφέροντα, καλή δουλειά έχει γίνει (θέλω λίγω να τα μελετήσω παραπάνω βέβαια αλλά μόλις το επιτρέψει ο χρόνος μου).

Link to comment
Share on other sites

"θεωρώ ότι αυτοί πρέπει να είναι ιδιαίτερα απαιτήτικοί στο κομμάτι της ασφάλειας διαφορετικά δεν θα γινόταν αποδεκτή η λύση, μιλάμε για τράπεζες και τεράστιους οργανισμούς με απίστευτες απαιτήσεις."

 

Σαν θεωρημα καλο ειναι στην πραξη κολαει απο παντου ομως...

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

×
×
  • Create New...