Design Before Coding: Gathering Requirements

If you have worked in IT for any duration, I’m sure you have overheard or been asked to build a tool to complete X or Y. Creating tools with PowerShell is fun, but it can become daunting when you create a tool that does not meet its intended purpose.  Without understanding the full requirements, you may waste time and energy on developing a tool that no one will use.

Creating tools with PowerShell to automate a manual process or to help an internal stakeholder accomplish a desired result, typically does not need to turn into a large initiative with a Project Manager or Project Management Office (PMO). Being tasked with creating these tools usually comes in the form a short conversation or through an email.  Out of habit, we usually dive right into writing a script or function to solve the problem.  This approach can cause a lot of re-work or redesign of our tool once complete.  Even though we believe we understand all the requirements, it is better to have the stakeholder create a “Goal Statement” that defines the intended purpose of the tool.  The “Goal Statement” helps everyone involved understand when the initiative is done.

A “Goal Statement” does not need to be a large body of work, it can simply be a couple of sentences or paragraphs.  Personally, I take the Scrum/Kanban approach and use User Stories.

User Stories are typically designed in the following format (there are different styles, but in my experience this the simplest form):

As a <type of user>, I want <goal> so that I <receive benefit>.

Having a defined User Story reduces re-work and ensures that all stakeholders involved agree on the intended uses of this new tool.  Agreement on the intended results of this new initiative may not solve the problem completely, but it’s a great start!

At this point, we have not written any scripts, functions, modules, classes, etc.  You may want to dive right into writing a POC (Proof of Concept), but I recommend that you hold-off.  Once all stakeholders have agreed on our User Story, we should move to the design phase (Part 3 coming soon).

Let’s say that we work on a “Automation Team” in our organization that focuses on building tools to streamline processes for both IT and business teams.  We have been tasked with helping the organizations IT managers identify and verify that all Active Directory groups in our Forest have the correct owners associated with them.  As we start our requirements gathering phase, we ask all stakeholders to provide our team with agreed upon User Stories.  Our team receives the following:

As a manager, I want to know all Active Directory groups owned by myself so I can ensure that they are correct. 

As a manager, I want to know all Active Directory groups owned by my employees so that I can ensure that they should have access. 

As a manager, I want to know all Active Directory groups that do not have an owner but reside in my Active Directory OU so that I can assign the correct owner.

Now that we have our agreed upon User Stories, we can begin the next phase; designing the “look and feel” of our new tool based on the requirements we have been given.  Having a general idea of what our stakeholders are needing reduces our work effort, as well as setting clear expectations that are agreed upon.

Understanding why we need to gather requirements is the first step.  The next post in this series I will discuss how you can use Kanban, digitally and manually, to organize our tasks so that you or your team can keep track of the status/progress along the way.

The final post in this series, we will begin designing our code layout.  This will help us and our stakeholders understand what parameters need to be present, what objects should be accepted in the pipeline, what return objects should look like, and how the new tool will be used.

Advertisements

PowerShell Phishing Response Toolkit (PPRT)

Yesterday I gave a talk at ShowMeCon in St. Louis regarding PPRT.  I also gave this talk at CircleCityCon, but had some technical issues. 🙂  I wanted to write this quick post to share out my PowerPoint Slides from this presentation.  If you have any questions about PPRT, please reach out via this blog or create an issue on my GitHub page: https://github.com/MSAdministrator/PPRT—PowerShell-Phishing-Response-Toolkit

Enjoy!

Slides: PowerShell Phishing Response Toolkit

PhishReporter: PowerShell Module

If you work in the Info Sec world you know that phishing is a pain in the neck, especially when you’ve been targeted by a large phishing campaign.  I’ve been through these massive phishing attacks, and it is not fun!

One such attack, about a year ago, was especially difficult because the attackers were using very unique tactics and spoofing lots of internal e-mail communications.

Our procedure was to go and find the company that was hosting these phishing URLs and e-mail them to tell them to shut the site down immediately.  Hosting companies like weebly.com, jimdo.com, webs.com, etc. were pretty good at shutting these sites down as soon as they were put online, but this process would take about 5 to 10 minutes and sucked!

Since this last attack, I decided to automate this process.  This new script/tool is a PowerShell Module called PhishReporter.

The PowerShell module can be downloaded on GitHub: https://github.com/MSAdministrator/PhishReporter

This PowerShell Module is designed to send notifications to hosting companies that host phishing URLs by utilizing the major WHOIS/RDAP Abuse Point of Contact (POC) information.

  1. This function takes in a .msg file and strips links from a phishing URL.
  2. After getting the phishig email, it is then converted to it’s IP Address.
  3. Once the IP Address of the hosting website is identified, then we check which WHOIS/RDAP to search.
  4. Each major WHOIS/RDAP is represented: ARIN, APNIC, AFRNIC, LACNIC, & RIPE.
  5. We call the specific WHOIS/RDAP’s API to determine the Abuse POC.
  6. Once we have the POC, we send them an email telling them to shut the website down. This email contains the original email as an attachment, the original phishing link, and e-mail body telling them to remove the website.

This Module came out of necessity. I was sick of trying to contact these individual sites, so I have began automating our response time to these events.

The next steps for this project are to fully integrate into Outlook and automate this even further by enabling a simple text search or based on a selected ‘folder’ event. Please share with the Security community and contribute/improve as you deem fit. I only ask that you share your edits back with this project.

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.