PowerShell: Updating Local Admin Passwords Securely (3 of 3)

Below is an example of how to remotely (using PowerShell) update and change the Local Administrator password securely.  This is not my script, I received this script along with many others from the SANS SEC 505 course.  I have not altered it in any way.

Updating the Passwords of the Local Administrator on remote mahcines (You must have a Certificate either from your CA or another Certificate Authority):

This script will remove older local administrator passwords that you have created with the first part (1 of 3) which is decrypted by your Private Key Certificate in (2 of 3).

Copy this text into a PowerShell Script called CleanUp_PasswordArchive.ps1


####################################################################################
#.Synopsis
# Carefully delete only the correct password archive files previously
# created with the companion script named Update-PasswordArchive.ps1.
#
#.Description
# Deletes older password archive files while retaining a chosen number
# (default = 5) of prior successful and failed archive files for each
# unique combination of computer and user (total of 10 files by default
# for each unique combination). Scheduling this script will help to
# maintain a reasonable number of password archive files.
#
#.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. When
# specified, only the archives for that computer (or for those matching
# computers with a wildcard) will be cleaned up instead of the default,
# which is to clean up archive files for all computers.
#
#.Parameter ArchivesToKeep
# The number of current and prior password archive files to keep for
# each combination of computer name and user name. A single computer
# might have multiple local user accounts whose passwords are managed
# by these scripts, so the clean up must handle this gracefully. The
# default is 5 (5 successful files + 5 failures files = 10 files).
#
#.Parameter VerboseReporting
# Switch to show verbose information.
#
#.Parameter DoNotDelete
# Switch to do a -WhatIf dry run that will not actually delete any
# files. Use this switch along with -VerboseReporting for testing.
#
#.Parameter DeleteAllPasswordArchives
# Switch to delete all password archive files without exception.
#
#.Example
# .\CleanUp-PasswordArchive.ps1 -PasswordArchivePath \\server\share
#
# Deletes all but the last 5 password archive files for each computer
# and user combination. Also, deletes all but the last 5 failure-type
# files for each combination. Hence, if the last successful reset was
# six months ago, followed by many failures, the last 5 successful
# reset archive files will *not* be deleted.
#
#.Example
# .\CleanUp-PasswordArchive.ps1 -PasswordArchivePath \\server\share -ComputerName WKS*
#
# Only cleans up the archive files for computers which match the "WKS*" file
# pattern. A full computer name can be specified, wildcards are not required.
# Regular expressions are not supported.
#
#.Example
# .\CleanUp-PasswordArchive.ps1 -PasswordArchivePath \\server\share -DoNotDelete -VerboseReporting
#
# No files will be deleted, but a verbose report of files discovered will be shown.
#
#
#Requires -Version 2.0
#
#.Notes
# Author: Jason Fossen, Enclave Consulting (http://www.sans.org/windows-security/)
# Version: 1.0
# Updated: 11.Nov.2012
# 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 = "*", $ArchivesToKeep = 5, [Switch] $VerboseReporting, [Switch] $DoNotDelete, [Switch] $DeleteAllPasswordArchives)

# Test access to path.
if (Resolve-Path -Path $PasswordArchivePath)
{ $PasswordArchivePath = $(Resolve-Path -Path $PasswordArchivePath).Path }
else
{ "`nERROR: Cannot resolve path to archive folder: $PasswordArchivePath `n" ; exit }
# Sanity check on $ArchivesToKeep
if ($ArchivesToKeep -le 0) { "`nERROR: The -ArchivesToKeep argument must be 1 or larger.`n" ; exit }
# Get all matching files.
$filter = $($ComputerName + "*+*+*+*").Replace("**","*")
$files = dir -Path $PasswordArchivePath -Filter $filter
if ($VerboseReporting)
{
 "`nTotal number of matching ($ComputerName) archive files = " + $files.count
 $files | foreach { $_.fullname }
 "`n"
}
# Maybe just delete them all.
if ($DeleteAllPasswordArchives -and $DoNotDelete) { "`nERROR: Invalid combination of switches.`n" ; exit }
if ($DeleteAllPasswordArchives -and $ComputerName -ne "*") { "`nERROR: Invalid combination of switches.`n" ; exit }
if ($DeleteAllPasswordArchives)
{
 "`nDeleting all password archive files...`n"
 $files | foreach { remove-item $_ }
 exit
}
# Separate successful vs. failed password reset files.
$failurefiles = $files | where { $_.Name -like "*FAILURE" }
$successfiles = $files | where { $_.Name -notlike "*FAILURE" }
if ($VerboseReporting) { "Files for failed resets = " + $failurefiles.count }
if ($VerboseReporting) { "Files for successful resets = " + $successfiles.count }
# Build arrays of unique computername+username combinations.
$failurenames = @()
$successnames = @()

foreach ($file in $failurefiles)
{
 $computer = $($file.name -split '\+')[0] + "+" + $($file.name -split '\+')[1]
 if ($computer -notin $failurenames){ $failurenames += $computer }
}
if ($VerboseReporting) { "`nUnique Computer+User combinations for failed resets = " + $failurenames.count }
if ($VerboseReporting) { $failurenames ; "`n" }

foreach ($file in $successfiles)
{
 $computer = $($file.name -split '\+')[0] + "+" + $($file.name -split '\+')[1]
 if ($computer -notin $successnames){ $successnames += $computer }
}
if ($VerboseReporting) { "Unique Computer+User combinations for successful resets = " + $successnames.count }
if ($VerboseReporting) { $successnames ; "`n" }
# Delete the non-keepers for each unique name.
$SuccessFileCounter = $FailureFileCounter = 0

foreach ($name in $failurenames)
{
 $targets = $failurefiles | where { $_.Name -like ($name + "+*") } | sort Name
 if ($targets.count -le $ArchivesToKeep) { continue }
 0..$($targets.count - $ArchivesToKeep - 1) |
 foreach { if (-not $DoNotDelete){remove-item $targets[$_].fullname ; if($?){$FailureFileCounter++}}}
}

foreach ($name in $successnames)
{
 $targets = $successfiles | where { $_.Name -like ($name + "+*") } | sort Name
 if ($targets.count -le $ArchivesToKeep) { continue }
 0..$($targets.count - $ArchivesToKeep - 1) |
 foreach { if (-not $DoNotDelete){remove-item $targets[$_].fullname ; if($?){$SuccessFileCounter++}}}
}
# Default Report
[String] $SuccessFileCounter + " files deleted out of the set of " + [String] $successfiles.count + " successful reset files."
[String] $FailureFileCounter + " files deleted out of the set of " + [String] $failurefiles.count + " failure reset files."
"`n"

 

Advertisements

PowerShell: Updating Local Admin Passwords Securely (2 of 3)

 Below is an example of how to remotely (using PowerShell) update and change the Local Administrator password securely.  This is not my script, I received this script along with many others from the SANS SEC 505 course.  I have not altered it in any way.

Updating the Passwords of the Local Administrator on remote mahcines (You must have a Certificate either from your CA or another Certificate Authority):

This script will recover the local administrator password that you have created with the first part (1 of 3) which is decrypted by your Private Key Certificate.

Copy this text into a PowerShell Script called 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.
#
#.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 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
#
# 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.
#
#.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.
#
#.Example
# .\Recover-PasswordArchive.ps1 -PasswordArchivePath \\server\share -ComputerName LAPTOP47 -ShowAll
#
# Instead of showing only the last password update, show all archived passwords
# in the \\server\share folder for the computer named LAPTOP47.
#
#
#Requires -Version 2.0
#
#.Notes
# Author: Jason Fossen, Enclave Consulting (http://www.sans.org/windows-security/)
# Version: 1.0
# Updated: 11.Nov.2012
# 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", [Switch] $ShowAll)

# 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 }

# Get the latest archive file only, unless -ShowAll is used.
if (-not $ShowAll){ $files = @($files[-1]) }
# Load the current user's certificates and private keys.
$flags = new-object System.Security.Cryptography.X509Certificates.OpenFlags #ReadOnly
$store = new-object System.Security.Cryptography.X509Certificates.X509Store #CurrentUser
if (-not $? -or ($store.GetType().fullname -notlike "*X509Stor*")) { "`nERROR: Could not load your certificates and private keys.`n" ; exit }
$store.open($flags)
$certstore = $store.Certificates
$store.close()
if ($certstore.count -eq 0) { "`nERROR: You have no certificates or private keys.`n" ; exit }
# Process encrypted password archive files and $output objects.
foreach ($lastfile in $files) `
{
 $output = ($output = " " | select-object ComputerName,FilePath,UserName,TimeStamp,Thumbprint,Valid,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.Password = "ERROR: Try to use prior password(s) for this computer."
 $output
 continue
 }
 # Read in password archive binary file.
 [byte[]] $ciphertext = get-content -encoding byte -path $lastfile.fullname
 if (-not $?)
 {
 $output.Password = "ERROR: Failed to read " + $lastfile.fullname
 $output
 continue
 }
 # Load the correct certificate and test for possession of private key.
 $certpriv = $certstore | where { $_.thumbprint -eq $output.thumbprint }
 if (-not $certpriv.hasprivatekey)
 {
 $output.Password = "ERROR: You do not have the private key for this certificate."
 $output
 continue
 }
 # Attempt decryption with private key.
 $plaintextout = $certpriv.privatekey.decrypt($ciphertext,$false) #Must be $false for my smart card to work.
 if (-not $?) { $output.Password = "ERROR: Decryption failed." }
 else { $output.Password = ([char[]]$plaintextout -join "") }
 # Confirm that archive file name matches the nonce string encrypted into the file.
 # Nonce helps to thwart attackers and can be used for troubleshooting too.
 if ($lastfile.name -like $output.Password.substring(0,60) + "*")
 {
 $output.Password = $output.Password.substring(60) #Strip out the 60-char nonce.
 $output.Valid = $true
 }
 else
 {
 $output.Password = $output.Password.substring(60) #Strip out the 60-char nonce.
 $output.Password = "ERROR: Integrity check failure: " + $output.Password.substring(60) + " (" + $output.Password + ")"
 }
 $output
}

 

PowerShell: Updating Local Admin Passwords Securely (1 of 3)

So I haven’t posted in a bit but I’ve seen this request many times.  Below is an example of how to remotely (using PowerShell) update and change the Local Administrator password securely.  This is not my script, I received this script along with many others from the SANS SEC 505 course.  I have not altered it in any way.

Updating the Passwords of the Local Administrator on remote mahcines (You must have a Certificate either from your CA or another Certificate Authority):

This script will update the local administrator password with a unique 15 character, random, complex password, which is encrypted by your Public Key Certificate.

Copy this text into a PowerShell Script called Update_PasswordArchive.ps1

Also, see below for a readme file for more explanation!


####################################################################################
#.Synopsis
# Resets the password of a local user account with a 15-character, random,
# complex password, which is encrytped with a chosen 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-character, random,
# complex password, which is encrytped with a chosen 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.
#
#.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. Any certificate with any
# purpose(s) from any template can be used.
#
#.Parameter LocalUserName
# Name of the local user account on the computer where this script is run
# whose password should be reset to a 15-character, complex, random password.
# Do not include a "\" or "@" character, only local accounts are supported.
# Defaults to "Administrator", 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.
#
#
#.Example
# .\Update-PasswordArchive.ps1 -CertificateFilePath C:\certificate.cer -PasswordArchivePath C:\folder
#
# Resets the password of the Administrator account, encrypts that password
# with the public key in the certificate.cer file, and saves the encrypted
# archive file in C:\folder.
#
#.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 Administrator 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 (http://www.sans.org/windows-security/)
# Version: 1.0
# Updated: 11.Nov.2012
# 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 = "Administrator", $PasswordArchivePath = ".\")
####################################################################################
# Function Name: Generate-RandomPassword
# Argument(s): A single argument, an 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.
# Note: # and " and <space> are excluded to make the
# function play nice with other scripts. Extended
# ASCII characters are not included either. Zero and
# capital O are excluded to make it play nice with humans.
# Note: 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.
####################################################################################
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.

 Do {
 $password = $null
 $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 strong until tested otherwise.

 For ($i = $length; $i -gt 0; $i--)
 {
 $x = get-random -min 33 -max 126 #Random ASCII number for valid range of password characters.
 #The range eliminates the space character, which causes problems in other scripts.
 If ($x -eq 34) { $x-- } #Eliminates double-quote. This is also how it is possible to get "!" in a password character.
 If ($x -eq 39) { $x-- } #Eliminates single-quote, also causes problems in scripts.
 If ($x -eq 48 -or $x -eq 79) { $x++ } #Eliminates zero and capital O, which causes problems for humans.

 $password = $password + [System.Char] $x #Convert number to an ASCII character, append to password string.

If ($x -ge 65 -And $x -le 90) { $hasupper = $true }
 If ($x -ge 97 -And $x -le 122) { $haslower = $true }
 If ($x -ge 48 -And $x -le 57) { $hasnumber = $true }
 If (($x -ge 33 -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)

 $password
}

&nbsp;

####################################################################################
# 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.
$ErrorOnlyLogMessage = @"
$Message

CurrentPrincipal = $($CurrentPrincipal.Identity.Name)

CertificateFilePath = $CertificateFilePath

LocalUserName = $LocalUserName

PasswordArchivePath = $PasswordArchivePath

ArchiveFileName = $filename

NET.EXE Output = $netout
"@

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 }
}

&nbsp;
# 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 }

&nbsp;

# Confirm that the target local account exists and that NET.EXE is executable by this process.
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 }
$netusers = invoke-expression -Command $($env:systemroot + "\system32\net.exe user")
if (-not $($netusers | select-string "$LocalUserName" -quiet)) { write-statuslog -m "ERROR: Local user does not exist: $LocalUserName" -exit }

&nbsp;

# 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))
{
 [Byte[]] $certbytes = get-content -encoding byte -path $CertificateFilePath #Trick to support UNC paths here.
 $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 }

&nbsp;

# Construct name of the archive file, whose name will also be used as a nonce.
$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 }

&nbsp;

# Prepend first 60 characters of the $filename as a nonce to the new password (as a byte array).
$newpassword = "ConfirmThatNewPasswordIsRandom"
$newpassword = Generate-RandomPassword
if ($newpassword -eq "ConfirmThatNewPasswordIsRandom") { write-statuslog -m "ERROR: Password generation failure, password not reset." -exit }
$bytes = [byte[]][char[]] $filename.substring(0,60)
$bytes += [byte[]][char[]] $newpassword

&nbsp;

# Encrypt the nonce+password string.
$cipherbytes = $cert.publickey.key.encrypt($bytes,$false) #Must be $false for my smart card to work.
if (-not $? -or $cipherbytes.count -lt 60) { write-statuslog -m "ERROR: Encryption failed, password not reset." -exit }

&nbsp;

# Must save encrypted password before resetting, confirm that it actually worked.
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 }

&nbsp;

# Attempt to reset password by hopefully satisfying length and complexity requirements.
$netout = Invoke-Expression -Command $($env:systemroot + "\system32\net.exe user $LocalUserName " + '"' + $newpassword + '"')
if (-not $? -or ($LASTEXITCODE -ne 0) -or ($netout -notlike "*success*"))
{
 # Write failure file to the archive path.
 $filename = $env:computername + "+" + $LocalUserName + "+" + $(get-date).ticks + "+PASSWORD-RESET-FAILURE"
 "ERROR: Failed to reset password after creating a success file:`n" + $netout | set-content -path ($PasswordArchivePath + $filename)
 write-statuslog -m "ERROR: Failed to reset password after creating a success file:`n $netout" -exit
}
else
{
 remove-variable -name newpassword #Just tidying up, not really necessary at this point...
 write-statuslog -m "SUCCESS: $LocalUserName password reset and archive file saved."
}

README.TXT

</pre>
**************************************
 Background:
**************************************
The passwords of local administrative accounts should be changed regularly and be different from one computer to the next. But how can this been done securely and conveniently?

The scripts in this project demonstrate how it can be done without spending a penny. The scripts are intended to be relatively easy to understand and modify (you don't have to be a PowerShell guru, only intermediate skills required). Error-checking was kept to a minimum to reduce clutter, but should be adequate for troubleshooting and preventing passwords from being reset when an archive file cannot be saved for it. All scripts are in the public domain. A sample certificate (CER) and private key file (PFX) are included for testing.

&nbsp;

**************************************
 Solution:
**************************************
A trusted administrator should obtain a certificate and private key, then export that certificate to a .CER file into a shared folder (\\server\share\cert.cer).

Copy the Update-PasswordArchive.ps1 script into that shared folder (\\server\share).

Using Group Policy, SCCM, a third-party EMS, SCHTASKS.EXE or some other technique, create a scheduled job on every computer that runs once per week (or every night) under Local System context that executes the following command: "powershell.exe \\server\share\update-passwordarchive.ps1 -certificatefilepath \\server\share\cert.cer -passwordarchivepath \\server\share". This resets the password on the local Administrator account, or whatever account is specified, with a 15-character, random complex password. The password is encrypted in memory with the public key of the certificate (cert.cer) and saved to an archive file to the specified share (\\server\share).

When a password for a computer (laptop47) needs to be recovered, the trusted administrator should run from their own local computer the following PowerShell script: "recover-passwordarchive.ps1 -passwordarchivepath \\server\share -computername laptop47". This downloads the necessary encrypted files and decrypts them locally in memory using the private key of the administrator, displaying the plaintext password within PowerShell.

&nbsp;

**************************************
 Notes:
**************************************
The password is never sent over the network in plaintext, never saved to disk in plaintext, and never exposed as a command-line argument, either when resetting the password or when recovering it later. The new password is generated randomly in the memory of the PowerShell process running on the computer where the password is reset. The process runs for less than a second in the background as Local System.

Different certificates can be used at different times, as long as their private keys are available to the administrator. When recovering a password, the correct certificate and private key will be used automatically. A smart card can be used.

If the shared folder is not accessible to the computer when the scheduled job runs, the password is not reset.

If multiple administrators must be able to recover the plaintext passwords, export the relevant certificate and private key to a PFX file and import it into each administrator's local profile.

&nbsp;

**************************************
 Threats and Recommendations:
**************************************
Keep the private key for the certificate used to encrypt the password archive files secure, such as on a smart card. This is the most important factor.

Prevent modification of the Update-PasswordArchive.ps1 script itself by digitally signing the script, enforcing script signature requirements, and using restrictive NTFS permissions. Only allow NTFS read access to the script to those identities (computer accounts) which need to run it or to edit it.

Attackers may try to delete or corrupt the existing password archive files to prevent access to current passwords. It's best to store the certificate and archive files in a shared folder whose NTFS permissions only allow the client computer accounts the following permissions:

Principal: Domain Computers
 Apply to: This folder, subfolders and files
 Allow: Full Control
 Deny: Delete subfolders and files
 Deny: Delete
 Deny: Change permissions
 Deny: Take ownership
 Deny: Create folders/append data

Principal: Domain Computers
 Apply to: Files only
 Deny: Create files/write data

The trusted administrator(s) can be granted Full Control to the archive files, certificates, and scripts as needed.

Attackers might also try to generate spoofed archive files and add them to the shared folder. The files are not digitally signed, but there is a crude integrity checking feature (signature-security through obscurity?) built into the scripts: the name of the archive file is also encrypted into the contents of the file along with the password. The Recover-PasswordArchive.ps1 script outputs objects which include a "Valid" property, which if false, indicates verification failure. To deal with spoofed files, you can delete all files with timestamps after the beginning of the attack which are Valid = False. NTFS auditing on the share will also log which computer(s) added the suspicious files and when, which provides another source of information to determine which new files are spoofed. Because attackers can presumably run commands as Local System on the machine(s) taken over, the attackers will be able to read the Update-PasswordArchive.ps1 script and the public key CER file out of the shared folder too, hence, the attackers will be able to defeat this crude integrity check if they wish to spoof the creation of new archive files. Adding true digital signatures to the archive files would add significant complexity with little benefit because we must assume the attackers can extract any signing keys from kernel memory on the computers they have already compromised. Realistically, though, this DoS attack would likely be of low value for an adversary expending this much effort, it would tip their hand, and the benefit to us of managing local administrative account passwords correctly far exceeds the potential negative of this sort of DoS attack.

Whole drive encryption of the server is recommended, but not really necessary since each password archive file is already encrypted.

IPSec or SMB encryption to the server is recommended, but not really necessary since each password archive file is already encrypted. On the other hand, IPSec port permissions which limit access to the SMB ports (TCP 139/445) is recommended, but not primarily for the encryption, but for restricting access based on group memberhips.

Back up the server with the shared folder every night and keep at least 30 days of prior backups to help deal with the threat of spoofing new archive files to flush out the current valid ones.

If the private key for the certificate is compromised, create a new key pair, replace the certificate file (.CER) in the shared folder, and immediately remotely trigger the scheduled job on all machines using Group Policy, SCHTASKS.EXE or some other technique.

&nbsp;

**************************************
 Tips:
**************************************
Keep the number of files in the archive folder manageable by using the CleanUp-PasswordArchives.ps1 script. Perhaps running this script as a scheduled job every two or four weeks.

To optimize the performance of the Recover-PasswordArchive.ps1 script when there are more than 100,000 files in the folder containing the password archives, disable 8.3 file name generation and strip all current 8.3 names on the volume containing that folder. Search the Internet on "fsutil.exe 8dot3name" to see how.

To maximize fault tolerance, use Distributed File System (DFS) shared folders across two or more servers.

The output of the Recover-PasswordArchive.ps1 script can be piped into other scripts to automate other tasks which require the plaintext password, such as executing commands, doing WMI queries, opening an RDP session, or immediately resetting the password again when finished.

You can also perform an immediate password update with commands like these wrapped in a function:

Copy-Item -Path .\PublicKeyCert.cer -Destination \\laptop47\c$
 Invoke-Command -ComputerName laptop47 -filepath .\Update-PasswordArchive.ps1 -argumentlist "C:\publickeycert.cer","Administrator","c:\"
 Copy-Item -Path \\laptop47\c$\laptop47+Administrator+* -Destination C:\LocalFolder
 Remove-Item -Path \\laptop47\c$\PublicKeyCert.cer
 Remove-Item -Path \\laptop47\c$\laptop47+Administrator+*

The above Invoke-Command can be done by specifying UNC paths, but this requires delegation of credentials to the remote computer, which is not ideal, so the certificate and archive files have to be copied back-and-forth manually.