Security best practices for PowerShell Universal
This document provides security best practices and hardening options for PowerShell Universal.
We recommend enabling app token enhanced security. App tokens generated within PowerShell Universal will be hashed and stored in the database rather than persisted in plaintext. As app tokens are used, they are hashed and compared against the hashed values. Tokens are hashed with SHA256.
We recommend adjusting the default app token signing key. This setting is present in appsettings.json
. Changing the signing key will invalidate existing app tokens.
Consider using authentication.ps1
for configuring authentication methods that require secrets. When using authenticaiton.ps1, you can use secret variables and, in turn, secret vaults to load these values when starting up.
We recommend changing the default admin account password. By default, this password is stored within the PowerShell Universal database. You will receive a warning on the login page if the admin password is the default.
Ensure that role scripts are either disabled or do not simply return true. Returning true from a role script will grant any user access to that role. With authentication methods like Windows Authentication, any user that is a capable of logging into the domain will have acecss to PowerShell Universal if the roles are not properly configured.
Consider using a least privileged access model for roles within PowerShell Universal. Much of PowerShell Universal can be used with the execute or operator role. It isn't necessary to assign the administrator role to all users.
It may be desirable to limit identities to ones created within PowerShell Universal. This will prevent users from accessing PowerShell Universal if they have not been added on the identities page.
Consider creating triggers that will notify you of potential issues with authentication and authorization. The following triggers may be useful:
Revoked App Token Usage
User Login
New User Login
API Authentication Failed
By default, job runs are stored as sequential big integers. This means that bad actors can guess job runs by incrementing a number from the current job ID. As of PowerShell Universal 3.8, an experimental feature can be enabled to only allow access of jobs by run ID. Run IDs are globally unique identifiers and cannot be guessed.
Avoid running long running event handlers directly within dashboards. This can be an avenue for a denial-of-service attack. Consider running long running processes as jobs to take advantage of queuing.
If using the Database or PSUSecretStore vaults, we recommend changing the default passwords. Both systems use a symmetric encryption method for storing secrets. The Database secret stored encrypts values using the specified key into the database persistence layer. The PSUSecretStore creates a local file in the service account's profile with the secret values.
PowerShell Universal integrates with the Microsoft Secret Management module. This module provides the ability to interact with an extensible set of vaults. Many vault implementations can be found on the PowerShell Gallery.
We recommend using a secure vault, such as Cyberark or Azure KeyVault, to store secrets in an enterprise ready fashion.
Follow the documentation for secret variable vaults to learn how to configure an alternate vault.
Enabling HTTPS for the web server is highly recommended. If possible, we recommend storing a certificate within the Certificate Manager to avoid having to include a clear-text password for a local PFX file. Certificates are configured within appsettings.json
.
Consider configuring rate limiting to avoid denial of service attacks. PowerShell Universal executes PowerShell in many different ways. PowerShell can be slower to execute than compiled languages so rate limiting can be very beneficial, especially for PowerShell Universal instances accessible externally.
Learn more about rate limiting here.
Consider configuring the CORS hosts setting in appsettings.json
. You can learn more about the benefits of the CORS hosts setting here.
Consider utilizing service accounts with a least-privileged access model. It's possible to configure the PowerShell Universal service to run as a service account that does not have access to certain resources and utilize alternate accounts when running scripts that need to access resources.
Learn more about service accounts here.
We recommend the use of SQL server with integrated security or Azure managed identities. This avoids having to store passwords locally.
By default, the LiteDB database is not password protected. Actors that can physically access the PowerShell Universal server can copy the database file from disk and open it elsewhere. Consider providing an encryption key to the LiteDB connection string to enable encryption.
PowerShell Universal exposes PowerShell scripts remotely in different ways. It's important to follow strict PowerShell script security best practices to guard against potentional attacks such as script injection.
Some recommendations include:
Use of param blocks to validate input
Avoiding Invoke-Expression to help prevent injection
Code and security review processes on scripts
Learn more about scripting security here.
Consider using one-way git sync for changes in your production environment. This ensures that PowerShell Universal is read-only, and only validated changes will be picked up in production environments.