Powershell JEA Security hole with commands embedded in functions? - powershell

This seems bonkers so I'm hoping I didn't find a big security gap... I have Powershell JEA (just enough administration) successfully set up on a server to allow only certain administrative functions. Specifically, I don't have the "net" command allowed at all. If I do the below:
Invoke-Command -computername MYSERVER -configurationname MYCONFIG -scriptblock {
net stop "My windows service"
}
Then I get the error below as expected:
The term 'net.exe' 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: (net.exe:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
BUT, if I wrap my "net.exe" usage inside a function, it actually works:
Invoke-Command -computername MYSERVER -configurationname MYCONFIG -scriptblock {
function StopService($servicename) {
net stop "$($servicename)"
}
StopService "My windows service"
}
The above does not throw an error and actually stops the service. WTF?
This is more than the "net" command. Another example: "Out-File" is not allowed. The below code fails:
Invoke-Command -computername MYSERVER -configurationname MYCONFIG -scriptblock {
"hacked you" | Out-File C:\test.txt
}
With the error:
The term 'Out-File' 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: (Out-File:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
+ PSComputerName : vxcazdev01
But if I do it this way, it works:
Invoke-Command -computername MYSERVER -configurationname MYCONFIG -scriptblock {
function DoIt() {
"hacked you" | Out-File C:\test.txt
}
DoIt
}
Why is this happening? Am I missing something? The JEA project on github is now read-only so I can't open an issue there.
Edit to add: the same problem happens if I use Enter-PSSession instead of Invoke-Command.
Edit to add relevant session config pieces: My session config file only has a few customizations from the default file produced by the New-PSSessionConfigurationFile command:
SessionType = 'Default'
RunAsVirtualAccount = $true
RoleDefinitions = #{
'MYDOMAIN\MYADGROUP' = #{ RoleCapabilities = 'MyCustomRole' }
}
MYADGROUP is the only group my test user is a member of. And then this is registered on the server like so:
Register-PSSessionConfiguration -Path "C:\Program Files\WindowsPowerShell\Modules\MyJEAModule\VSM.pssc" -Name 'MYCONFIG' -Force

I'm just going to answer this question myself with the answer: security hole by design.
The documentation says this:
The body (script block) of custom functions runs in the default language mode for the system and isn't subject to JEA's language constraints.
It's rather surprising, to me at least, that JEA will let you lock down actions on a server in security sandbox, but as soon as one writes their own custom functions they have full administrative rights to the machine and have broken out of the box. Allowing or restricting the creation of custom functions via the language mode is one thing, but bypassing the set security permissions is another. In my opinion, user-written custom functions should be subject to full security limitations; custom functions written in the role capabilities files should have full admin rights as the documentation indicates.
The other answer by HAL9256 is great, but it describes the benefits of JEA, which is not the topic of this post.

I wouldn't say that it's a security hole, it's that you are clearly demonstrating what could happen on a system when you have not set up a fully secured configuration. Microsoft even states JEA doesn't protect against admins because "they could simply RDP in and change the configuration". We need the correct combination of SessionType and RoleDefinitions, and that they are meant for two different configurations.
Your example demonstrates a configuration setup where, even though we lock the front door of the house, we started off with a house that had all the windows and doors open. It is fully possible to get in through the back door, or reach through a window and unlock the front door, thus demonstrating the fruitlessness of locking the front door. For example, I don't need to run net stop I could just do a taskkill instead, or..., or..., etc.
Let's look at the overview of what JEA is designed for:
Reduce the number of administrators on your machines using virtual accounts or group-managed service accounts to perform privileged actions on behalf of regular users.
Limit what users can do by specifying which cmdlets, functions, and external commands they can run.
Better understand what your users are doing with transcripts and logs that show you exactly which commands a user executed during their session.
Reduce the number of administrators
We can use JEA to remove people from the local administrators group, or larger Domain Admin groups. They can then selectively get elevated Administrator rights when needed through Virtual Accounts.
If we set it up with the SessionType = 'Default' this enables all language features. We essentially can have a Jr. Technical Analyst without Domain Admin rights, without Local Admin rights, log on, and do Administrative duties. This is what the session type is meant for.
Limit what users can do
If we set it up with the SessionType = 'Default' this enables all language features. In mode it doesn't matter what commands we limit, all the doors and windows are open and we can pretty much do whatever we want. One rule in Windows is that there is always 3-4 different ways to do something. You just can't plug all the holes when everything is wide open.
#MathiasR.Jessen is right, the only way to Limit what users can do is to first lock down the system. Setting SessionType = 'RestrictedRemoteServer' locks down the session to:
Sessions of this type operate in NoLanguage mode and only have access
to the following default commands (and aliases):
Clear-Host (cls, clear)
Exit-PSSession (exsn, exit)
Get-Command (gcm)
Get-FormatData
Get-Help
Measure-Object (measure)
Out-Default
Select-Object (select)
No PowerShell providers are available, nor are any external programs
(executables or scripts).
This starts us out with a completely locked up house. We then selectively enable the needed commands. Ideally we should pre-create custom functions so that the custom function is the only thing they can run, they are technically not even allowed to execute the commands inside the function at all, it's all handled by the Virtual Account.
What you did was essentially exploiting this custom function capability, by "cheating" and creating our own "custom function" that will run in the Virtual Account scope, and not your own, which is why it was able to run "non-allowed" functions, and you were not. If the SessionType = 'RestrictedRemoteServer', you wouldn't be able to create scripts or custom functions like demonstrated, and hence, the "hole" would not be there.
Better understand what your users are doing
Finally the other benefit for JEA is that it can record a transcript of all the commands that are run. This might be needed for audit reasons or fed into a SIEM solution or to find out how your Jr. Technical Analyst messed up your system ;-).

Related

PowerShell - ActiveDirectory Module

I need the ability to have users run a script that requires the ActiveDirectory module. I copied over the following:
"C:\Windows\System32\WindowsPowerShell\v1.0\Modules\ActiveDirectory", "Microsoft.ActiveDirectory.Management.resources.dll", "Microsoft.ActiveDirectory.Management.dll".
The script runs two Get-ADUser commands, 1 without the -Server parameter and the other with. The issue is that the former is working but the latter is not.
Is there another module that I need to copy over?
I don't like the idea of installing administrative tools for non-admins. Even if you could get away with copying files and not doing the full-blown RSAT installation. Not the least of reasons is you are dramatically increasing the attack surface for malicious actors. The better solution is (Just Enough Administration) JEA, or a philosophically similar approach.
JEA / Contrained endpoints can get complicated, but a summary of what you can do looks something like this:
New-PSSessionConfigurationFile -Path 'C:\PSSessionConfigs\DemoPSEndpointConfig.pssc' -ModulesToImport ActiveDirectory -VisibleCmdlets "Get-ADUser"
Register-PSSessionConfiguration -Path 'C:\PSSessionConfigs\DemoPSEndpointConfig.pssc' -ShowSecurityDescriptorUI -Name DemoPSEndPoint
Run these commands on a system that has the ActiveDirectory module (likely the whole RSAT component) installed, it doesn't need to be a Domain Controller. It will create a new PowerShell remoting endpoint configuration that exposes only the commands you wish. The Register-PSSessionConfiguration command will display a security dialog where you can permission which users you want to allow to connect, you want to grant them read & execute permission. Once that's done, you can get the results with an Invoke-Command command like this:
Invoke-Command -ComputerName <ServerName> -ConfigurationName DemoPSEndPoint -ScriptBlock { Get-ADUser <UserName> }
You can add the -Server parameter in the command without issue. You can expand the cmdlets you are allowing in the New-PSSessionConfiguration command.
Again this is very much a summary of a more complex topic but should be enough to get what you want.
Personally, I don't use configuration files as much as I use startup scripts. I think the latter is more flexible. You can get some information about that here. If you really want to dig into this there are references at the end of the article including a link to the PowerShell JEA documentation. There's also a link to some of the MVP articles I used to develop my own endpoints.
The ActiveDirectory module is dependent on the RSAT (remote server administration tool). This is avalible to install/activate through powershell: https://mikefrobbins.com/2018/10/03/use-powershell-to-install-the-remote-server-administration-tools-rsat-on-windows-10-version-1809/
With this installed you automatically also get the Activedirectory module installed.

Double-Hop Errors when running Skype for Business Cmdlets

I am attempting to automate the Skype for Business Server installation process in Powershell, I have a script that remotes into specified machines and begins preparing them as Front-End servers. The problem lies when certain SfB cmdlets (SfB commands are all of the form "verb-Cs...", ex. Get-CsUser or Get-CsPool) are run in remote sessions, they throw the double-hop error:
Exception: Active Directory error "-2147016672" occurred while searching for domain controllers in domain...
This is after running Enable-CsComputer, which enables the computer's role-based off its definition in the topology (topology was published successfully). The user object is in all required groups (RTCUniversalServerAdmins, Schema Admins, CsAdministrators & Local Admin rights on all SfB Servers). Oddly enough, the command 'Import-CsConfiguration -localstore" does not throw errors, and it's in the same remote session. There may be other local or domain groups that I need to be in, but I cannot pinpoint exactly which and have not seen them documented in the Skype build guides. Skype commands that have parameters to specify targets or just pull data, such as Get-CsPool or Get-CsAdForest, do not have errors because they are run in the local scope. The Enable-CsComputer has no parameter for the computer name, it has to be executed from that machine itself.
Enabling CredSSP delegation on each server is not an option, and I'm not understanding why there is a "second hop" in this command! If the second hop was a resource on a file server or database, that would make sense, and be easy to solve, but in this case, I can't track it. Can anyone tell me what I may be missing?
Here's a code sample to try and illustrate. From the jumbox I get the pool data to create an array, and a session is opened to each machine:
$ServerArray =get-cspool -identity $poolName
$i=0
$SessionArray = #{}
foreach($server in $ServerArray.Computers){$SessionArray[$i] = new-PsSession -ComputerName $server}
foreach($session in $SessionArray.values){
invoke-Command -session $session -scriptBlock {
#remote commands:
import-csConfiguration -<config file path> -localstore; #no errors
enable-CsReplica; #no errors
enable-cscomputer; #double hop error here
}}
If I log into that machine and run the same command, it executes fine but the intention of the project is to automate it on an arbitrary number of machines.
It looks like it's just trying to authenticate to a domain controller, which is reasonable. You'll have to approach this like any other double-hop issue.
Microsoft has an article dedicated to the double hop issue, and has a few solutions other than CredSSP that you can look at: Making the second hop in PowerShell Remoting

What rights are necessary to enter a PSSession with a DC?

I want to switch 2 files in the SYSVOL Folder. For that I need to Invoke-Command a script on a Domain Controller from our schedule server.
$Server = Get-ADDomainController | select Name
Invoke-Command -ComputerName $Server.Name -ScriptBlock {
$Path = "c:\Windows\SYSVOL\sysvol\domain.com\scripts\Folder"
$Source = "$Path\backgrounddefault1.JPG"
$trg = "$Path\backgrounddefault.JPG"
Copy-Item -Path $Source -Destination $trg -Force
}
However if I do not promote the Service Account to Domain Admin, the script will get an access denied.
Are there any other ways I could do this, or other groups that give him that specific right?
The group is called "Remote Management Users". Obviously, however, you'll also need permissions on the folder. Create a new group for those rights specifically, and make it a member of RMU. Make the service account a member of the new group. (Note that none of this is specific to DCs.)
If you wanted to do this without changing any permissions on a Sysvol subdirectory, the cleanest thing I can think of would be to create a scheduled task on the DC running under local system that performs only this operation, and grant the service account permission to start this task. (There is no interface for this, but you can manipulate the SecurityDescriptor of what you get with Get-ScheduledTask; see this question, for example).
If this task needs parameters/input, it gets trickier since you'll need to supply these in a file somehow. Because that task effectively has domain admin permissions, you'd have to take very good care to check your inputs and make sure the task has no exploitable vulnerabilities. Just tweaking the permissions on that one specific folder seems a lot easier and also safer to me.
Last but not least, when performing operations like these it's always worth investigating if what you're trying to do can't be done with Group Policy somehow, because it leaves a clear statement of intent (and an audit trail).

Constrained endpoint on specific user

I'm trying to create a PowerShell endpoint constraint that restricts an user to only execute the functions in a custom module I made.
The first thing I did is import-module mymodule.psm1 which allows me to run my modules fine withing my host system.
Then the following PS command creates the configuration file for the endpoint which allows the functions inside the brackets to be the only functions the user gets to execute.
New-PSSessionConfigurationFile -VisibleFunctions('Get-Command','Get-Info', 'CreateAD-User','Generate-Html','Change-Logon') -LanguageMode ‘ConstrainedLanguage’ –SessionType ‘RestrictedRemoteServer’ –Path ‘c:\test\helpdesk.pssc’
Then I register the endpoint with
Register-PSSessionConfiguration –Name ‘HelpDesk’ -ShowSecurityDescriptorUI –Path ‘c:\test\helpdesk.pssc’
and selected which user I want allow to have these constrains once the SecurityDescriptorUI pops up. Once I log into the user that I set up the constrains for with
Enter-PSSession -computername SRV1-AD -Credential $credential -ConfigurationName HelpDesk
These are the allowed cmdlets / functions that the user is allowed to execute. These are the default required cmdlets to allow remote connections into a system.
How can I allow my custom module to be the only functions the endpoint allows users to execute? or How can I import my module into configuration file so it executes every time the HelpDesk end point configuration is used. I know that in the configuration file there's a line to import modules but Import-Module is not actually a module an example of a module would be ActiveDirectory, if I'm able to find what module import-module is a part of I think I should be able to do a quick and dirty work around for this.
UPDATE
A dirty solution I found for this was to enter into the user's session and disable all cmdlets / functions except the ones I want to allowed for example import-module & Get-Command with import-module I can manually import my custom module and my functions will be the only ones visible to user. But this is not a perfect solution because this means that I would need to download my module into every system I want this to take effect and it's no longer a one to many solution. The ideal solution is to have my module locally stored, enter into a session with the registered end point and have my module already imported into the users account.
Enter-PSSession -computername SRV1-AD -Credential $credential -ConfigurationName HelpDesk
Further Update
User #prasoon-karunan-v suggested I used -ScriptsToProcess & FunctionDefinitions to import the module so I used the following command
New-PSSessionConfigurationFile -VisibleFunctions('Get-Command','Get-Info', 'CreateAD-User','Generate-Html','Change-Logon') -LanguageMode ‘ConstrainedLanguage’ –SessionType ‘RestrictedRemoteServer’ –Path ‘.\EndPoint.pssc’ -ScriptsToProcess C:\Users\Administrator\Desktop\Modules\ImportM.psm1
In the configuration file I also set the functions I want to use like so
# Functions defined in this session configuration
FunctionDefinitions = 'Get-Command','Get-Info', 'CreateAD-User','Generate-Html','Change-Logon'
When I tried to establish a session it would throw the following error
Then I thought maybe it's not working because were not telling the command to import anything were just pointing to the module file, so maybe I need to create a small script that imports the module then add it the configuration file. So that's exactly what I did I created a small script with just,
import-module C:\Modules\ImportM.psm1 and then I went over to the .pssc
file and added this script to the ScriptsToProcess but I get the following error after I try to establish a session to the constrained endpoint.
Language Mode is set to
LanguageMode = 'RestrictedLanguage'
use -ScriptsToProcess parameter, which can be used to import your custom module.
See below as well.
Get-Help New-PSSessionConfigurationFile -Parameter ScriptsToProcess
Get-Help New-PSSessionConfigurationFile -Parameter FunctionDefinitions
Update:
Be sure about the language mode to use,
see here

Get-WinEvent via Powershell remoting

I have a non-admin access to a server. I'm allowed to connect via RDP, and to use PowerShell remoting. When I invoke the following PowerShell command from an RDP session:
Get-WinEvent -MaxEvents 100 -Provider Microsoft-Windows-TaskScheduler
I get 100 records, as expected.
When I do the same via PowerShell remoting, by invoking the following from my local machine:
invoke-command -ComputerName myserver {Get-WinEvent -MaxEvents 100 -Provider Microsoft-Windows-TaskScheduler }
I get an error:
No events were found that match the specified selection criteria.
CategoryInfo : ObjectNotFound: (:) [Get-WinEvent], Exception
FullyQualifiedErrorId : NoMatchingEventsFound,Microsoft.PowerShell.Commands.GetWinEventCommand
Any idea why? The remote PowerShell session should be running under identical credentials, right?
EDIT: whoami does show a difference in the security context between RDP logon and PowerShell remoting - the group set is different. In the RDP logon session, there are the following groups in the token:
BUILTIN\Remote Desktop Users
NT AUTHORITY\REMOTE INTERACTIVE LOGON
while in the remoted one, there's
CONSOLE LOGON
That could account for the discrepancy in rights...
EDIT: from the registry, it looks like the task scheduler log somehow is a part of the System log. According to MS KB article Q323076, the security descriptor for the System log can be found under HKLM\SYSTEM\CurrentControlSet\Services\EventLog\System, value CustomSD. I can't check the server in question, but on another server where I'm an admin, there's no CustomSD under that key. Under HKLM\SYSTEM\CurrentControlSet\Services\EventLog\System\Microsoft-Windows-TaskScheduler, neither. Only the Security log gets a CustomSD. The next question is, where's the default SD?
Permissions on the actual log file at C:\Windows\System32\winevt\LogsMicrosoft-Windows-TaskScheduler%4Operational.evtx are irrelevant, the access is being mediated by the EventLog service anyway.
If you are not an administrator on the remote computer, and invoke-command -ComputerName myserver {whoami /all} tells you are who you expected to be.
You will need to be part of Event Log Reader group on the remote computer.
As well as Remote Management Users group, which I believe you already are.
If you need to read security logs, you will also need Manage auditing and security log under Local Security Policy -> Security Settings -> Local Policies -> User Rights Assignment
According to Default ACLs on Windows Event Logs # MSDN blog, in Windows Server 2003+, the default ACL for the System log goes:
O:BAG:SYD:
*(D;;0xf0007;;;AN) // (Deny) Anonymous:All Access
*(D;;0xf0007;;;BG) // (Deny) Guests:All Access
(A;;0xf0007;;;SY) // LocalSystem:Full
(A;;0x7;;;BA) // Administrators:Read,Write,Clear
(A;;0x5;;;SO) // Server Operators:Read,Clear
(A;;0x1;;;IU) // INTERACTIVE LOGON:Read <===================
(A;;0x1;;;SU) // SERVICES LOGON:Read
(A;;0x1;;;S-1-5-3) // BATCH LOGON:Read
(A;;0x2;;;LS) // LocalService:Write
(A;;0x2;;;NS) // NetworkService:Write
Does NT AUTHORITY\INTERACTIVE LOGON include RDP logon? I've found a forum message that says so, but I'd better find a doc to that effect...
The article claims this ACE comes "straight from the source code". So it's hard-coded in the service, with a chance to change via the registry.
You need local admin rights to open a powershell session.
But there is a workaround/alterative here:
https://4sysops.com/archives/powershell-remoting-without-administrator-rights/
I had the weirdest variation of this problem, was driving me nuts !
Remoting from a server W2008r2 (logged on as domain admin, inside interactive powershell session) to workstation Win7 to get logon/logoff events :
invoke-command -computername $pc {Get-WinEvent -FilterHashtable #{logname='
Security';Id=#(4624,4634)}}
-> No events were found that match the specified selection criteria.
But it does work when outputting an empty string in the scriptblock before the Get-Winevent :
invoke-command -computername $pc {"";Get-WinEvent -FilterHashtable #{lognam
e='Security';Id=#(4624,4634)}}
TimeCreated ProviderName Id Message PSComputerName
----------- ------------ -- ------- --------------
19/03/2018 11:51:41 Microsoft-Windows-Se... 4624 An account was succe... b25_x64
19/03/2018 11:51:41 Microsoft-Windows-Se... 4624 An account was succe... b25_x64
Stumbled upon this fix after trying everything: Enter-Pssession, New-Pssession, using -credential parameter to pass a predefined credential to invoke-command, to get-winevent, to both. Nothing worked, gave "No events..." in every combination.
Then I inserted a $cred inside the scriptblock to show the passed on credential for debugging, and suddenly I got the events I was looking for...