One small step to enhance PowerShell security using Constrained Endpoints.


There are lots of debates on the innerwebs today about PowerShell being a malware tool or it bringing vulnerabilities to the environment and they are not wrong. “Wait what? Not wrong?!!” you say? Well… yes it can be and it does but, no more than any other like Python, C/C++, Java, JavaScript, C# and etc. etc.. The fact is that PowerShell is a very “powerful” scripting language that can leverage objects of all types and many frameworks running on a system. Having this power and it being implemented in such a easy to use syntax does make it useful to not only good gals/guys, but the bad alike.

Just like anything else we need to control what, where and how we allow things to happen. How our information is to be queried, and changes to be made. Just because we implement doors, gates and fences does not mean we can let our guard down. For every locked door, shut gate, and tall fence, there is someone out there is looking to pick it, smash it down and go right through it, or simply go around, under, or over it. We have to proactively monitor and investigate as well as adapt to the changing methods and attacks. So, this is the first in a series of articles that I will discuss and show you how to implement some items to help you in this journey.

In this installment we are going to up our security by configuring what personnel can do on a system. There are quite a few ways to do this, we will start with “Constrained Endpoints” (Other methods coming soon). Endpoints are connection points for PowerShell that allow users and processes to remote to a system and perform tasks. There are several default Endpoints on a given system and we can run the following command to see them Get-PSSessionConfiguration (must being running elevated).

You will see output like the following:



Endpoint capability can be limited to Commands, Permissions, Language, and RunAs which allows us a lot of flexibility in implementation. So let’s get into the commands we are going to use here, PSSessionConfiguration is going to be our main noun. We will use that noun with the following verbs: Get/Set, Register/Unregister, and Enable/Disable. The other noun we will use here is PSSessionConfigurationFile, this noun will allow us to create and test our configuration with the New/Test verbs added.

Let’s start with New-PSSessionConfigurationFile as it is needed first. The simple command I am going to use to demonstrate is as follows:




The command above created my file which I then opened with Visual Studio Code for you to see. Breaking this down, here is what we created: the New-PSSessionConfigurationFile command itself defines and creates the .pssc file we need to create and register the new Endpoint.  The first parameter I passed is -SessionType and there are 3 values for it: Default, Empty, and RestrictedRemoteServer with the last being the recommended value as it only includes a limited set of proxy functions. The next parameter is the -VisibleCmdlets, these are the cmdlets that you are allowing the session to have, they can be edited in the file as well so as not to have to pass them through the command. I gave access to only the Get-Date cmdlet. Then I set the -LanguageMode with my value choice being ConstrainedLanguage, this does not allow commands that contain scripts to be evaluated and restricts access to .Net types, objects and methods. Lastly I gave the -Path parameter that points to where and filename .pssc to be created. There are lots of options here that you can explore and you will need to determine what access your Endpoint needs for whom it is going to be used by.

Next step, now that we have our configuration file of settings, we need to associate it to the Endpoint that we want to create. This is done with the Register-PSSessionConfiguration command, we simply pass it the path to the file we created in the last step through the -Path parameter and give it a -Name <Cosmetic Name>.




Almost there, the endpoint is active and available but… We need to setup who can use it and what permissions they have. Lets take a look at the session configuration before we do so we can ensure the endpoint was created:




We now have an endpoint named T1HelpDesk. Ok, lets get those permissions set and test. To do this we will use the Set-PSSessionConfiguration cmdlet and adding the -ShowSecurityDescriptorUI switch parameter will give us the UI to add users/groups and set permissions for them. I am adding both Read(Get,Enumerate,Subscribe) and Execute(Invoke) so that my user can run the commands he has access to. You will need to understand what permissions do what to ensure you are giving just the ones to do the job at hand. You may also be prompted after clicking on to restart WinRM which is required for the changes to take affect.



Time of truth, let’s test and see what we get!



I tried to remote in from my SumPiGi account <alter ego math wiz..> and as you see above, I cannot enter a interactive session directly. I must first pass the -ConfigurationName that I have been granted access to, “T1HelpDesk” in this case. Then once I am in, I run a Get-Command to see what I can do. The only cmdlet I get access to at this point is Get-Date which is the only one I defined in the -VisibleCmdlets for the New-PSSessionConfigurationFile. I also get a few others by default due to the LanguageMode I choose to allow Exit-PSSession, Get-Help, etc..

There will be more to come on other methods and practices to help secure your environment. Everyone’s approach and configuration will always be different, you have to sit down and make a plan, understand what privileges your admins/users need to do their jobs. Environments change as well, what works today may not be what you need tomorrow so adaptation is very important. Monitoring and understanding your environment is an continually ongoing evolution.

Happy Scripting!



Comments (0)

Skip to main content