Account Password Rotation | Randomly Generated
Table of Contents
INTRO
User account password rotation can be randomly generated with PowerShell and implemented with RMM tools. When working in the IT field it feels like you never hear the end about passwords. Security regarding passwords, age of passwords, password history, lockouts, complexity, character length, multi-factor, zero-trust, and the list goes on. With the use these days of RMM tools, things have gotten a little easier to manage. Best practices regarding passwords are all over the place but how many people actually follow them?
CONSIDERATIONS
When thinking about creating scripts to accomplish password rotation, here are some considerations:
- Have a password change on a schedule.
- Have a password be random and complex.
- Have access to the password set.
- Verify password set is not stored on the endpoint for attackers to potentially find.
RMM USAGE
I spent some time researching how to do this and put together and tested some scripts. Below you will find them. This example will be for Datto RMM. With Datto RMM we can schedule the job for whatever we want. When the job runs the PowerShell script that is running against an endpoint, the attempt is shown in the Security event log. The event log shows Datto copies the script down to the endpoint and runs it from the following location:
C:\ProgramData\CentraStage\Packages\d0b49e6e-9eb6-48bf-8e28-99271b329f93#\command.ps1
The “d0b49e6e-9eb6-48bf-8e28-99271b329f93#” part being the random unique identifier of the script path. The output of the command containing the password is not logged and stored on the endpoint. Once the job is completed, you can click the “StdOut” link next to the job in Datto RMM to view the output which contains the password that was generated that you can copy and paste, and when using Splashtop or Web remote to the endpoint and you can past clipboard as keystrokes to the password fields remotely.
The output was monitored and captured when it ran in memory but again is not stored on the endpoint itself. The nice thing about this is that you don’t need to store the password anywhere else. Someone might say, well what if malware was present on the machine monitoring this in memory? My answer would then be you’re already compromised and the malware obtaining the password doesn’t really matter anymore.
PASSWORD ROTATION
Here are some PowerShell scripts below that simply just rotate the password for either a domain or local account called “Administrator”. So, for DC servers, it would rotate the Domain Administrator and client endpoints, the Local Built-in Administrator account.
Domain User Account:
Get-ADUser -Identity "Administrator" -Properties PasswordLastSet,LastLogon,LastLogonDate
Function Create-String([Int]$Size = 16, [Char[]]$CharSets = "ULNS", [Char[]]$Exclude) {
$Chars = @(); $TokenSet = @()
If (!$TokenSets) {$Global:TokenSets = @{
U = [Char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZ' #Upper case
L = [Char[]]'abcdefghijklmnopqrstuvwxyz' #Lower case
N = [Char[]]'0123456789' #Numerals
S = [Char[]]'!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~' #Symbols
}}
$CharSets | ForEach {
$Tokens = $TokenSets."$_" | ForEach {If ($Exclude -cNotContains $_) {$_}}
If ($Tokens) {
$TokensSet += $Tokens
If ($_ -cle [Char]"Z") {$Chars += $Tokens | Get-Random} #Character sets defined in upper case are mandatory
}
}
While ($Chars.Count -lt $Size) {$Chars += $TokensSet | Get-Random}
($Chars | Sort-Object {Get-Random}) -Join "" #Mix the (mandatory) characters and output string
}; Set-Alias Create-Password Create-String -Description "Generate a random string (password)"; Write-Output ""
$Passwd = Create-Password 25 UNLS; Write-Output "This is the new randomly generated password for the Built-in Domain/Local Server Administrator account:"; Write-Output "";"";""; $Passwd
$PasswdSecStr = ConvertTo-SecureString $Passwd -AsPlainText -Force
Set-ADAccountPassword -Identity "Administrator" -Reset -NewPassword $PasswdSecStr
Get-ADUser -Identity "Administrator" -Properties PasswordLastSet | Format-Table PasswordLastSet
Local User Account:
Get-LocalUser | Where-Object {$_.Name -eq 'Administrator' -and $_.Name -NotLike '\Administrator'} | Select-Object PSComputername, Caption, Name, FullName, Description, Status, Disabled, Domain, LocalAccount, AccountType, Lockout, PasswordRequired, PasswordChangeable, PasswordExpires, SID, SIDType, Lastlogon, PasswordLastSet | Format-List
Function Create-String([Int]$Size = 16, [Char[]]$CharSets = "ULNS", [Char[]]$Exclude) {
$Chars = @(); $TokenSet = @()
If (!$TokenSets) {$Global:TokenSets = @{
U = [Char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZ' #Upper case
L = [Char[]]'abcdefghijklmnopqrstuvwxyz' #Lower case
N = [Char[]]'0123456789' #Numerals
S = [Char[]]'!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~' #Symbols
}}
$CharSets | ForEach {
$Tokens = $TokenSets."$_" | ForEach {If ($Exclude -cNotContains $_) {$_}}
If ($Tokens) {
$TokensSet += $Tokens
If ($_ -cle [Char]"Z") {$Chars += $Tokens | Get-Random} #Character sets defined in upper case are mandatory
}
}
While ($Chars.Count -lt $Size) {$Chars += $TokensSet | Get-Random}
($Chars | Sort-Object {Get-Random}) -Join "" #Mix the (mandatory) characters and output string
}; Set-Alias Create-Password Create-String -Description "Generate a random string (password)"; Write-Output ""
$Passwd = Create-Password 25 UNLS; Write-Output "This is the new randomly generated password for the local Built-in Administrator account:"; Write-Output "";"";""; $Passwd
$PasswdSecStr = ConvertTo-SecureString $Passwd -AsPlainText -Force
Set-LocalUser -Name "Administrator" -Password $PasswdSecStr
Get-LocalUser | Where-Object {$_.Name -eq 'Administrator' -and $_.Name -NotLike '\Administrator'} | Select-Object PasswordLastSet | Format-List
ACCOUNT CREATION & PASSWORD ROTATION
The password rotation scripts work well, but I find the best use of password rotation is when combined with account creation. So, if I take over a site with many endpoints, I want to have a local Administrator account for administrative purposes that is only for my use and the use of others in my MSP organization. I could use the local built-in Administrator account in Windows, but many cybersecurity organizations combined with insurance agencies, and other cybersecurity compliance standards such as PCI-DSS want the built-in Administrator account disabled and it makes sense.
So, what I do is create a custom one called Support. So below are scripts that will check for an account called Support and rotate the password, if the account is not found it’s created, and then the password is rotated.
Domain User Account:
$ErrorActionPreference = 'Stop'
$VerbosePreference = 'Continue'
$USERNAME = "Support"
Function Create-String([Int]$Size = 16, [Char[]]$CharSets = "ULNS", [Char[]]$Exclude) {
$Chars = @(); $TokenSet = @()
If (!$TokenSets) {$Global:TokenSets = @{
U = [Char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZ' #Upper case
L = [Char[]]'abcdefghijklmnopqrstuvwxyz' #Lower case
N = [Char[]]'0123456789' #Numerals
S = [Char[]]'!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~' #Symbols
}}
$CharSets | ForEach {
$Tokens = $TokenSets."$_" | ForEach {If ($Exclude -cNotContains $_) {$_}}
If ($Tokens) {
$TokensSet += $Tokens
If ($_ -cle [Char]"Z") {$Chars += $Tokens | Get-Random} #Character sets defined in upper case are mandatory
}
}
While ($Chars.Count -lt $Size) {$Chars += $TokensSet | Get-Random}
($Chars | Sort-Object {Get-Random}) -Join "" #Mix the (mandatory) characters and output string
}; Set-Alias Create-Password Create-String -Description "Generate a random string (password)"; Write-Output ""
# Generating a new random password.
$Passwd = Create-Password 25 UNLS
# Converting random generated password to a secure string for insertion.
$PasswdSecStr = ConvertTo-SecureString $Passwd -AsPlainText -Force
# Declare LocalUser Object
$ObjADUser = $null
try {
Write-Verbose "Searching for the AD user account named $($USERNAME) in Active Directory."
$ObjADUser = Get-ADUser -Identity $USERNAME
Write-Verbose "The domain user account named $($USERNAME) was found."
Write-Verbose "Gathering details on the $USERNAME account."
Get-ADUser -Identity $USERNAME -Properties * | Select-Object Name, DisplayName, Description, Enabled, LockedOut, PasswordNotRequired, PasswordNeverExpires, LastBadPasswordAttempt, SID, LastLogonDate, PasswordLastSet | Format-List
Write-Verbose "Setting the new randomly generated password."
Set-ADAccountPassword -Identity $USERNAME -Reset -NewPassword $PasswdSecStr
Write-Verbose "This is the newly random generated password for the AD user account named $($USERNAME):"; Write-Output ""; $Passwd
Get-ADUser -Identity $USERNAME -Properties * | Select-Object PasswordLastSet | Format-List
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
"The AD user account named $($USERNAME) was not found." | Write-Warning
}
catch {
"An unspecifed error has occurred." | Write-Error
Exit # Stop PowerShell!
}
# Create the AD user account if it was not found.
if (!$ObjADUser) {
Write-Verbose "Creating an AD user account named $($USERNAME) and setting the randomly generated password."
New-ADUser -Name $USERNAME -AccountPassword $PasswdSecStr -PasswordNeverExpires $True -Enabled $True
Write-Verbose "Adding the $($USERNAME) AD account to the Administrators Group."
Add-ADGroupMember -Identity Administrators -Members $USERNAME
Write-Verbose "Gathering details on the $USERNAME AD account."
Get-ADUser -Identity $USERNAME -Properties * | Select-Object Name, DisplayName, Description, Enabled, LockedOut, PasswordNotRequired, PasswordNeverExpires, LastBadPasswordAttempt, SID, LastLogonDate, PasswordLastSet | Format-List
Write-Verbose "This is the newly random generated password for the AD user account named $($USERNAME):"; Write-Output ""; $Passwd
Get-ADUser -Identity $USERNAME -Properties * | Select-Object PasswordLastSet | Format-List
}
Local User Account:
$ErrorActionPreference = 'Stop'
$VerbosePreference = 'Continue'
$USERNAME = "Support"
Function Create-String([Int]$Size = 16, [Char[]]$CharSets = "ULNS", [Char[]]$Exclude) {
$Chars = @(); $TokenSet = @()
If (!$TokenSets) {$Global:TokenSets = @{
U = [Char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZ' #Upper case
L = [Char[]]'abcdefghijklmnopqrstuvwxyz' #Lower case
N = [Char[]]'0123456789' #Numerals
S = [Char[]]'!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~' #Symbols
}}
$CharSets | ForEach {
$Tokens = $TokenSets."$_" | ForEach {If ($Exclude -cNotContains $_) {$_}}
If ($Tokens) {
$TokensSet += $Tokens
If ($_ -cle [Char]"Z") {$Chars += $Tokens | Get-Random} #Character sets defined in upper case are mandatory
}
}
While ($Chars.Count -lt $Size) {$Chars += $TokensSet | Get-Random}
($Chars | Sort-Object {Get-Random}) -Join "" #Mix the (mandatory) characters and output string
}; Set-Alias Create-Password Create-String -Description "Generate a random string (password)"; Write-Output ""
# Generating a new random password.
$Passwd = Create-Password 25 UNLS
# Converting random generated password to a secure string for insertion.
$PasswdSecStr = ConvertTo-SecureString $Passwd -AsPlainText -Force
# Declare LocalUser Object
$ObjLocalUser = $null
try {
Write-Verbose "Searching for the local user account named $($USERNAME) in the LocalUser database."
$ObjLocalUser = Get-LocalUser $USERNAME
Write-Verbose "The local user account named $($USERNAME) was found."
Write-Verbose "Gathering details on the $USERNAME account."
Get-LocalUser | Where-Object {$_.Name -eq $USERNAME -and $_.Name -NotLike '\$USERNAME'} | Select-Object Name, FullName, Description, Status, Disabled, Domain, LocalAccount, AccountType, Lockout, PasswordRequired, PasswordChangeable, PasswordExpires, SID, Lastlogon, PasswordLastSet | Format-List
Write-Verbose "Setting the new randomly generated password."
Set-LocalUser -Name $USERNAME -Password $PasswdSecStr
Write-Verbose "This is the newly random generated password for the local user account named $($USERNAME):"; Write-Output ""; $Passwd
Write-Output "";
Get-LocalUser | Where-Object {$_.Name -eq $USERNAME -and $_.Name -NotLike '\$USERNAME'} | Select-Object PasswordLastSet | Format-List
}
catch [Microsoft.PowerShell.Commands.UserNotFoundException] {
"The local user account named $($USERNAME) was not found." | Write-Warning
}
catch {
"An unspecifed error has occurred." | Write-Error
Exit # Stop PowerShell!
}
# Create the user account if it was not found.
if (!$ObjLocalUser) {
Write-Verbose "Creating a local user account named $($USERNAME) and setting the randomly generated password."
New-LocalUser $USERNAME -Password $PasswdSecStr -FullName $USERNAME -PasswordNeverExpires;
Write-Verbose "Adding the $($USERNAME) account to the Local Administrators Group."
Add-LocalGroupMember -Group Administrators -Member $USERNAME
Write-Verbose "Gathering details on the $USERNAME account."
Get-LocalUser | Where-Object {$_.Name -eq $USERNAME -and $_.Name -NotLike '\$USERNAME'} | Select-Object Name, FullName, Description, Status, Disabled, Domain, LocalAccount, AccountType, Lockout, PasswordRequired, PasswordChangeable, PasswordExpires, SID, Lastlogon, PasswordLastSet | Format-List
Write-Verbose "This is the newly random generated password for the local user account named $($USERNAME):"; Write-Output ""; $Passwd
Write-Output "";
Get-LocalUser | Where-Object {$_.Name -eq $USERNAME -and $_.Name -NotLike '\$USERNAME'} | Select-Object PasswordLastSet | Format-List
}
END RESULT
Datto RMM has removed the ability to view job output in Legacy UI under each device, it can still be viewed in legacy UI under the actual job itself, but then you must scroll for a while so below is the end result visible under activities in New UI. Personally, I like legacy UI better.
TROUBLESHOTING
I found that some Windows 8.1 and Windows Server 2012 R2 clients were not able to process these PowerShell scripts. After spending and hour or two researching, I discovered it’s because they need to have Windows Management Framework 5.1 installed. Once installed the scripts ran as expected.
Here is some untested code to install the patch silently, will need to check prerequisites first, have yet to confirm it works but it’s a start.
wusa.exe Win8.1AndW2K12R2-KB3191564-x64.msu /quiet /norestart
CONCLUSION
Well, there you go, give it a try and let me know what you think. The nice part about having something like this on a schedule is that if someone leaves the MSP, or you’re away, the password will be changed without any effort on your part. You could have this run every hour if you wanted to, and if any funny business goes down, the offender won’t have much time to act until the next rotation.
Do you know a better way to do this? If so, let me know!
My name is Dex Sandel, author at WinReflection, a blog which aims to help others on various IT and Christian related subjects. DON’T TREAD ON ME! The best is yet to come, and nothing can stop what’s coming!
You all have a greater destiny in Christ, should you choose to ‘follow’ Him, not just believe. Many of you feel lost, without drive, and lack a greater purpose in your life causing depression, sadness, anxiety, and loneliness. Working your 9-5 job isn’t your primary purpose. So, then what is? That’s for you to discover, but hopefully I can provide some new unlocks along your path.
What will ‘you’ do, and what will your destiny be?
John 3:16: For God so loved the world that he gave his one and only Son, that whoever believes in him shall not perish but have eternal life.
Leave a Reply
Want to join the discussion?Feel free to contribute!