{"id":2123,"date":"2015-12-01T00:00:00","date_gmt":"2015-12-01T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/powershell-day-to-day-sysadmin-tasks-securing-scripts\/"},"modified":"2018-05-01T07:57:14","modified_gmt":"2018-05-01T07:57:14","slug":"powershell-day-to-day-sysadmin-tasks-securing-scripts","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/powershell-day-to-day-sysadmin-tasks-securing-scripts\/","title":{"rendered":"PowerShell Day-to-Day SysAdmin Tasks: Securing Scripts"},"content":{"rendered":"<h4>The series so far:<\/h4>\n<ol>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/automating-day-to-day-powershell-admin-tasks---jobs-and-workflow\/\">Automating Day-to-Day PowerShell Admin Tasks \u2013 Part 1: Jobs and Workflow<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/powershell-day-to-day-admin-tasks-wmi,-cim-and-pswa\/\">PowerShell Day-to-Day Admin Tasks \u2013 Part 2: WMI, CIM and PSWA<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/powershell-day-to-day-admin-tasks-monitoring-performance\/\">PowerShell Day-to-Day Admin Tasks \u2013 Part 3: Monitoring Performance<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/powershell-day-to-day-sysadmin-tasks-securing-scripts\/\">PowerShell Day-to-Day Admin Tasks \u2013 Part 4: Securing Scripts<\/a><\/li>\n<li>PowerShell Day-to-Day Admin Tasks \u2013 Part 5: Events and Monitoring<\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/powershell-day-to-day-admin-tasks-part-6-real-time-it-dashboard\/\">PowerShell Day-to-Day Admin Tasks - Part 6: Real Time IT Dashboard<\/a><\/li>\n<\/ol>\n\n<h1>Overview<\/h1>\n<p class=\"start\">Scripting shells can be one of the most effective attack points of an operating system. They offer an advantage to an attacker by providing a layer of abstraction that anti-virus applications have no idea how to interpret. PowerShell is a scripting shell, but it uses &#8216;defense in depth&#8217; meaning that it takes a number of approaches to preventing malicious use of PowerShell or your scripts. One of the most effective measures is to use Digital Signatures to determine whether a script is to be &#8216;trusted&#8217;.<\/p>\n<p>PowerShell, by default, has an execution policy that prevents scripts being run at all and only allows interactive use of PowerShell. Many admins switch to an unrestricted policy, which is leaves the possibility of malicious attack wide-open, thereby risking harm for them and the firm for which they work. In this article, I will argue that using signed scripts a via digital Signatures (<strong>allsigned<\/strong>) is the best execution policy.<\/p>\n<p>PowerShell is unique amongst shells in that it executes scripts on start-up. There are user or system scripts that are run unnoticed when you start up PowerShell in order to provide the PowerShell &#8216;environment&#8217;, and to maintain profiles for users. An attacker needs only add code to a profile is then executed using the ID of the user, along with all its permissions. It could then execute an external binary, or more covertly, load shellcode or a malicious DLL. When the user executes the script, he inadvertently executes the added code. The attacker has avoided having to load an external malicious script and the modification could easily remain unnoticed. Obviously, the PowerShell profile scripts are stored within a windows users&#8217; directory so they may not seem to be easy for an intruder to alter, but any malware is likely to run under the users ID anyway so this, by itself, offers little protection. Digital signatures provide a measure of security wherever the PowerShell scripts are stored, and allow admins to safely share common libraries of scripts.<\/p>\n<p>In case you are unfamiliar with profiles, I&#8217;ll show how you can obtain PowerShell&#8217;s environment when personalising your profile. Then I shall build on this to show how PowerShell handles this security aspect. Most important, I&#8217;d like to demonstrate that ease-of-use does not mean lack of security.<\/p>\n<h3>The customisation of your work environment<\/h3>\n<p>The PowerShell is capable of saving time with your tasks and assisting with your work. A profile can, among other things, help with the administrator&#8217;s task. Anyone, for example, who does administrative tasks will find that many actions are performed several times per day, or each time they connect to their machine. Let&#8217;s take a simple example: you launch a remote connection on a server to update a component. How many times will you ask yourself the following question: &#8216;Am I really connected to server &#8220;SRV01&#8221;?&#8217; To reassure ourselves, we open a PowerShell console and type &#8216;$env:COMPUTERNAME&#8217;.It is certainly wise to double-check but, in doing so, you have just lost a few seconds. You&#8217;d probably have other information you&#8217;d want to check, such as the User_AD you are logged-in as, and the up-time of the machine you&#8217;re logged into. There are, of course, tools available to provide this sort of information such as BGInfo (download: <a href=\"https:\/\/technet.microsoft.com\/en-us\/sysinternals\/bginfo.aspx\">https:\/\/technet.microsoft.com\/en-us\/sysinternals\/bginfo.aspx<\/a>). Sadly it is not always possible to execute this type of tool on critical servers. It is here that PowerShell can help you if we create a profile.<\/p>\n<p>To determine if you already possess a PowerShell profile, type in the following on your machine:<\/p>\n<pre class=\"lang:ps theme:powershell-output\">  PS&gt; Test-path $profile <\/pre>\n<p>If the result is TRUE, then a profile has already been created. Otherwise we would need to type in the following to create the path.<\/p>\n<pre class=\"lang:ps theme:powershell-output\"> PS&gt; New-item -type file -force $profile  <\/pre>\n<p>A file <strong>&#8216;Microsoft.PowerShell_profile.ps1&#8217;<\/strong> will have been created in the following list:<\/p>\n<p><strong>C:\\Users\\&lt;username&gt;\\Documents\\WindowsPowerShell\\<\/strong><\/p>\n<p>This file is called a &#8216;PowerShell profile&#8217;, and will automatically execute its contents each time a console is opened. This file may contain personalised aliases, PSDrives, snap-ins or any other command that you judge to be useful. Database Administrators, for example, use SQLPS so much they&#8217;d want to include this in their profile, and Exchange\/Office Administrators might want the Exchange Management Shell snap-in or Azure Active Directory Module.<\/p>\n<p>There are four different profiles:<\/p>\n<pre class=\"lang:ps theme:powershell-output\">PS&gt; $PROFILE | Get-Member -MemberType noteproperty | format-list <\/pre>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 1 &#8211; List $Profile properties<\/p>\n<p>Note: The term &#8216;User&#8217; refers to the actual user connected to the machine.<\/p>\n<p>The term &#8216;Host&#8217; refers to the host application that interfaces with the PowerShell engine. (A few examples of &#8216;Host&#8217;: the PowerShell console, PowerShell ISE, PowerGUI).<\/p>\n<p>This means that it is possible to have four different profiles for a single &#8216;Host&#8217;:<\/p>\n<ul>\n<li><strong>CurrentUserCurrentHost<\/strong> This applies only to the connected user and for a supported &#8216;host&#8217;.<\/li>\n<li><strong>CurrentUserAllHosts<\/strong> This applies only to the connected user and all the &#8216;hosts&#8217; of the machine.<\/li>\n<li><strong>AllUsersCurrentHosts<\/strong> This applies to all users and for a supported &#8216;host&#8217;.<\/li>\n<li><strong>AllUsersAllHosts<\/strong> This applies to all users and for all supported &#8216;hosts&#8217; of the machine.<\/li>\n<\/ul>\n<p>For each profile, there is an associated file in a particular location. For ease of maintenance, I prefer to use just one file to manage several profiles: I just add conditions to a single file so as to prevent a &#8216;host&#8217; from executing a non-supported command (We&#8217;ll see this in the next section). Certain people will prefer to separate the profiles into separate files so as to avoid having a PS1 file that becomes far too complex to maintain.<\/p>\n<p>Let&#8217;s see in more detail what this file looks like. I take as an example the file that I use every day while I work so as to obtain useful overall information rapidly:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">    &lt;#\r\n    .SYNOPSIS\r\n    Microsoft.PowerShell_profile.ps1 - My Custom PowerShell profile\r\n    \r\n    .DESCRIPTION\r\n    Customizes the PowerShell console\r\n    \r\n    .NOTES\r\n    Author : Nicolas PRIGENT\r\n    #&gt;\r\n    \r\n    #PART 1\r\n    \r\n    if ($host.name -eq 'ConsoleHost')\r\n    {\r\n    $Shell=$Host.UI.RawUI\r\n    $size=$Shell.BufferSize\r\n    $size.width=120\r\n    $size.height=3000\r\n    $Shell.BufferSize=$size\r\n    $size=$Shell.WindowSize\r\n    $size.width=120\r\n    $size.height=30\r\n    $Shell.WindowSize=$size\r\n    $Shell.BackgroundColor=\"Black\"\r\n    $Shell.ForegroundColor=\"White\"\r\n    $Shell.CursorSize=10\r\n    $Shell.WindowTitle=\"Console PowerShell - By Nicolas\"\r\n    }\r\n    \r\n    #PART 2\r\n    \r\n    function Get-Uptime {\r\n    \r\n    \u00a0$os = Get-WmiObject win32_operatingsystem\r\n    \u00a0$uptime = (Get-Date) - ($os.ConvertToDateTime($os.lastbootuptime))\r\n    \u00a0$Display = \"\" + $Uptime.Days + \"days \/ \" + $Uptime.Hours + \"hours \/ \" + $Uptime.Minutes + \"minutes\"\r\n    \u00a0Write-Output $Display\r\n    }\r\n    \r\n    function Get-Time {return $(Get-Date | ForEach {$_.ToLongTimeString()})}\r\n    \r\n    function prompt\r\n    {\r\n    \u00a0Write-Host \"[\" -noNewLine\r\n    \u00a0Write-Host $(Get-Time) -ForegroundColor DarkYellow -noNewLine\r\n    \u00a0Write-Host \"] \" -noNewLine\r\n    \u00a0Write-Host $($(Get-Location).Path.replace($home,\"~\")) -ForegroundColor DarkGreen -noNewLine\r\n    \u00a0Write-Host $(if ($nestedpromptlevel -ge 1) { '&gt;&gt;' }) -noNewLine\r\n    \u00a0return \"&gt; \"\r\n    }\r\n    \r\n    function rdp ($IPAddr) {\r\n    \u00a0Start-Process -FilePath mstsc -ArgumentList \"\/admin \/w:1024 \/h:768 \/v:$IPAddr\"\r\n    }\r\n    \r\n    #PART 3\r\n    \r\n    Set-Location C:\\\r\n    \r\n    $MaximumHistoryCount=1024\r\n    $IPAddress=@(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.DefaultIpGateway})[0].IPAddress[0]\r\n    $IPGateway=@(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.DefaultIpGateway})[0].DefaultIPGateway[0]\r\n    $PSExecPolicy=Get-ExecutionPolicy\r\n    $PSVersion=$PSVersionTable.PSVersion.Major\r\n    \r\n    #PART 4\r\n    \r\n    Clear-Host\r\n    Write-Host \" -------------------------------------------------------------------------------------------------------\" -ForegroundColor Green\r\n    Write-Host \" \u00a6`tComputerName:`t`t\" -nonewline -ForegroundColor Green;Write-Host $($env:COMPUTERNAME)\"`t`t`t`t\" -nonewline -ForegroundColor Cyan;Write-Host \"UserName:`t\" -nonewline -ForegroundColor Green;Write-Host $env:UserDomain\\$env:UserName\"`t`t\" -nonewline -ForegroundColor Cyan;Write-Host \" \u00a6\" -ForegroundColor Green\r\n    Write-Host \" \u00a6`tLogon Server:`t`t\" -nonewline -ForegroundColor Green;Write-Host $($env:LOGONSERVER)\"`t`t`t`t\" -nonewline -ForegroundColor Cyan;Write-Host \"IP Address:`t\" -nonewline -ForegroundColor Green;Write-Host $IPAddress\"`t`t\" -nonewline -ForegroundColor Cyan;Write-Host \" \u00a6\" -ForegroundColor Green\r\n    Write-Host \" \u00a6`tPS Execution Policy:`t\" -nonewline -ForegroundColor Green;Write-Host $($PSExecPolicy)\"`t`t`t\" -nonewline -ForegroundColor Cyan;Write-Host \"PS Version:`t\" -nonewline -ForegroundColor Green;Write-Host $PSVersion\"`t`t`t\" -nonewline -ForegroundColor Cyan;Write-Host \" \u00a6\" -ForegroundColor Green\r\n    Write-Host \" \u00a6`tUptime:`t`t`t\" -nonewline -ForegroundColor Green;Write-Host $(Get-Uptime)\"`t`t`t`t`t`t\" -nonewline -ForegroundColor Cyan;Write-Host \" \u00a6\" -ForegroundColor Green\r\n    Write-Host \" -------------------------------------------------------------------------------------------------------`n\" -ForegroundColor Green\r\n    Write-Host \"Customs functions : Get-Uptime \/ Get-Time \/ RDP &lt;IPAddr&gt;`n\" -ForegroundColor Yellow; \r\n<\/pre>\n<p>The file is segmented into four parts:<\/p>\n<ol>\n<li><strong>#PART1<\/strong> I add a condition on the &#8216;Host&#8217; that can execute those commands because they can only be interpreted by the PowerShell console.<\/li>\n<li><strong>#PART2<\/strong> This section contains the overall personalised PowerShell functions so as to facilitate administration tasks.<\/li>\n<li><strong>#PART3<\/strong> This bloc defines the PowerShell variables that will be used in the following section of this file.<\/li>\n<li><strong>#PART4<\/strong> Lastly, this section allows to visually see the profile. Let your imagination run wild.<\/li>\n<\/ol>\n<p>Figure 2 &#8211; Custom profile<\/p>\n<p>It is also possible to load modules or snap-ins:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">  add-PSSnapIn Microsoft.Exchange, Microsoft.Windows.AD <\/pre>\n<p>Note: A profile that loads several snap-ins may result in a slower start-up of your PowerShell console.<\/p>\n<p>At this point, things get interesting from a security angle. Although PowerShell profiles can help with administration, it is easy for someone to alter your profile to execute malicious code under your administrative user account. After all, it is only a PS1 file. Even if it is, by default, in a user&#8217;s directory and therefore protected from access by other users, it could be altered to execute malicious commands silently with your windows identity, unbeknown to you. In order to avoid this, there are two things you can do. Firstly use the NTFS permissions to limit modify permission to these files to the administrator account. Secondly, sign it digitally and configure PowerShell so that it uses a more restrictive execution strategy. I shall explain the second option in detail.<\/p>\n<h3>The securing of your PowerShell scripts at the heart of your systems<\/h3>\n<p>This section focuses mainly around a command <strong>Set-ExecutionPolicy<\/strong>. The execution policy indicates to PowerShell what can and what cannot be executed on your system. Thus it is possible to block the execution of a script. I see too often Execution Policy deactivated entirely to make it simpler to maintain and less restrictive. In a test or development environment, it may indeed be important not to be too restrictive. However, in a production environment, you have to secure the execution of PowerShell scripts as necessary. The command <strong>Set-ExecutionPolicy<\/strong> accepts six values:<\/p>\n<ul>\n<li><strong>Restricted\/Default:<\/strong> This is the default policy. You may not launch any script. The only possibility is to use PowerShell in &#8216;interactive mode&#8217;.<\/li>\n<li><strong>Allsigned:<\/strong> All the scripts that should be executed on the machine must be signed by a &#8216;Trusted Publisher&#8217;.<\/li>\n<li><strong>RemoteSigned:<\/strong> This concerns only the scripts that have been downloaded from the internet. These scripts must be signed by a &#8216;Trusted Publisher&#8217;.<\/li>\n<li><strong>Unrestricted:<\/strong> No constraints on the execution of scripts. All the scripts will be executed if you accept a warning message. I do not recommend it in a production environment.<\/li>\n<li><strong>Bypass:<\/strong> No blockage, no warning message. Everything is executed without control.<\/li>\n<li><strong>Undefined:<\/strong> Removes the currently assigned execution policy from the current scope. This parameter will not remove an execution policy that is set in a Group Policy scope.<\/li>\n<\/ul>\n<p>The latest versions of PowerShell bring an extra granularity to the security level so as to assist the users to put a strategic security in place. For this, it is possible to apply these values to a &#8216;scope&#8217;:<\/p>\n<ul>\n<li><strong>Process<\/strong> Affect only PowerShell process.<\/li>\n<li><strong>CurrentUser<\/strong> Affect only the current user.<\/li>\n<li><strong>LocalMachine<\/strong> Affect all users of the computer.<\/li>\n<li><strong>MachinePolicy<\/strong> Must be set through Group Policy.<\/li>\n<li><strong>UserPolicy<\/strong> Must be set through Group Policy.<\/li>\n<\/ul>\n<pre class=\"lang:ps theme:powershell-output\">    PS &gt; Get-ExecutionPolicy -List <\/pre>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 3 &#8211; Get-ExecutionPolicy -List<\/p>\n<p>Note: The value is stocked in the registry base:<\/p>\n<p><strong>HKLM\\SOFTWARE\\Microsoft\\PowerShell\\1\\ShellIds\\Microsoft.PowerShell<\/strong> except for &#8216;Process&#8217; that is temporarily stocked in (<strong>$env:PSExecutionPolicyPreference<\/strong>) then it is destroyed at the end of the session.<\/p>\n<p>A few examples of use:<\/p>\n<p>This affects the policy &#8216;<strong>unrestricted<\/strong>&#8216; only for the current user.<\/p>\n<pre class=\"lang:ps theme:powershell-output\">  PS&gt; Set-ExecutionPolicy -Scope:CurrentUser -ExecutionPolicy:Unrestricted<\/pre>\n<p>This affects the policy &#8216;<strong>RemoteSigned<\/strong>&#8216; of the machine (and therefore to all users).<\/p>\n<pre class=\"lang:ps theme:powershell-output\">  PS&gt; Set-ExecutionPolicy -Scope:LocalMachine -ExecutionPolicy:RemoteSigned<\/pre>\n<p>Let&#8217;s take a simple concrete example. A PowerShell profile is nothing more than a PowerShell script, so you must authorise the script&#8217;s execution in your environment. For this, I recommend the policy &#8216;<strong>AllSigned<\/strong>&#8216; that allows you to &#8216;freeze&#8217; the changes of your profile and so avoid all malicious modifications. This will allow you to control the level of trust with the author\/editor of the script and to warn the user about code injections.<\/p>\n<h3>Self-signed certificate<\/h3>\n<p>As Code-signing certificated are expensive, a self-signed certificate is worth using for testing although it will be trusted only on your own PC and any PCs to which you have exported the certificate. Instructions on doing this are <a href=\"http:\/\/www.hanselman.com\/blog\/SigningPowerShellScripts.aspx\">here<\/a>. You can get help on the signing process by typing:<\/p>\n<pre class=\"lang:ps theme:powershell-output\">  PS&gt; Get-help about_Signing <\/pre>\n<h3>Active Directory-based public key infrastructure<\/h3>\n<p>It is relatively simple to put an <strong>AllSigned<\/strong> policy in place in an Active Directory environment. For this, you will need a certification authority to generate a certificate so as to sign your scripts.<\/p>\n<p>Begin by creating a template of type &#8216;Code Signing&#8217;. To sign the code digitally, we use a normalised certificate X509.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2319-image4.png\" alt=\"2319-image4.png\" \/><\/p>\n<p class=\"caption\">Figure 4 &#8211; Enable certificate template<\/p>\n<p>Then generate a certificate request from this template and think about selecting option &#8216;Mark key as exportable&#8217;. You must export the private key along with your certificate for it to be valid on your target machines. When you export the private key you will need to use a strong password. This password protects access to your private key.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2319-image5.png\" alt=\"2319-image5.png\" \/><\/p>\n<p class=\"caption\">Figure 5 &#8211; Request a certificate<\/p>\n<p>This certificate must then be imported into the folder &#8216;Trusted Publishers&#8217; from the certificate store of each machine using your scripts. You can deploy it easily via Group Policy Object (GPO) or with a logon script or you can just copy manually. The goal is to import the certificate on every machine in the Active Directory domain that will run your signed scripts.<\/p>\n<p>To do this, you will need a Group Policy Object (GPO) to define the overall parameters of your units<\/p>\n<p>The following parameter must be configured: Computer Configuration | Administrative Templates | Windows Components | Windows PowerShell<\/p>\n<p class=\"illustration\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2319-image6.png\" alt=\"2319-image6.png\" width=\"638\" height=\"404\" \/><\/p>\n<p class=\"caption\">Figure 6 &#8211; Configure Group Policy Object<\/p>\n<p>With all this done, all you then need to do is to sign your profile with this certificate. The command used is <strong>Set-AuthenticodeSignature<\/strong>.<\/p>\n<p>Note: Authenticode is a digital signature format used to embed a signature into a file. This means that the signature bloc is directly visible in your script.<\/p>\n<p>The certificate store is directly accessible as PSDrive. We therefore verify if a certificate is available then we add it to the variable <strong>$MyCert<\/strong>.Then, this certificate must be passed as an object to the command <strong>Set-AuthenticodeSignature<\/strong>.<\/p>\n<pre class=\"lang:ps theme:powershell-output\">  PS&gt; $MyCert =(dir Cert:\\CurrentUser\\My -CodeSigningCert)[0]\r\n    PS&gt; Set-AuthenticodeSignature .\\Microsoft.PowerShell_profile.ps1 -Certificate $MyCert \r\n\r\n<\/pre>\n<p class=\"illustration\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2319-image7.png\" alt=\"2319-image7.png\" width=\"639\" height=\"175\" \/><\/p>\n<p class=\"caption\">Figure 7 &#8211; Sign powershell script<\/p>\n<p>Note: If the variable <strong>$MyCert<\/strong> is empty, then it means that your certificate is not a &#8216;Code Signing&#8217; type. You have to generate a new certificate.<\/p>\n<p>If you open your script, you will note that it now has a signature at the end of the script:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2319-image8.png\" alt=\"2319-image8.png\" \/><\/p>\n<p class=\"caption\">Figure 8 &#8211; Signature block<\/p>\n<p>Let&#8217;s imagine that an ill-intentioned person modifies your profile. You launch your PowerShell console and you obtain this error:<\/p>\n<pre class=\"lang:ps theme:powershell-output\">  The file .\\profile.ps1 cannot be loaded. The file .\\profile.ps1 is not digitally signed. The script will not execute on the system. Please see \"get-help about_signing\" for more details..<\/pre>\n<p>This means that after modification, the file has not been signed again. The policy &#8216;AllSigned&#8217; has just blocked the execution of your profile.<\/p>\n<p>But you may also get this error after updating your profile. Therefore, in order to simplify the signature of your scripts, you may add the following function to your profile:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">    function Sign ($filename) {\r\n    \u00a0\u00a0\u00a0 $MyCert =(dir Cert:\\CurrentUser\\My -CodeSigningCert)[0]\r\n    \u00a0\u00a0\u00a0 Set-AuthenticodeSignature $filename -Certificate $MyCert \r\n    }\r\n\r\n<\/pre>\n<p>Then it will be very simple to sign subsequent scripts with the following command:<\/p>\n<pre class=\"lang:ps theme:powershell-output\">  PS&gt; Sign .\\profile.ps1<\/pre>\n<p>Note: Some PowerShell IDEs will sign your scripts automatically for you when you save the script.<\/p>\n<p>Once your script is signed, you will then notice a new tab in the properties of the file named &#8216;Digital Signatures&#8217; that will allow you to view the certificate.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2319-image9.png\" alt=\"2319-image9.png\" \/><\/p>\n<p class=\"caption\">Figure 9 &#8211; Digital Signatures Tab<\/p>\n<p>Here I do not use <strong>TimeStamp<\/strong>. You may, if you wish, use the parameter <strong>-TimeStampServer<\/strong> that lets you verify that the script was signed at some given time. Indeed, most of the certificates are valid for one year. When the date of expiration of the certificate arrives at its due date, the usage of this parameter does not let the code itself expire. It will be possible to carry on using it as long as it has not been modified. In case of modification, then it will have to be signed again with a valid certificate.<\/p>\n<pre class=\"lang:ps theme:powershell-output\">  PS&gt; Set-AuthenticodeSignature $filename -Certificate $MyCert -IncludeChain All -TimeStampServer \"http:\/\/timestamp.verisign.com\/scripts\/timstamp.dll\" <\/pre>\n<h3>To go further<\/h3>\n<p>It is possible to use the previously created certificate to sign all your Windows files, or even executables files. Indeed, how to make sure that the PowerShell executable has not been modified? By also signing powershell.exe, you will be assured that it is not an infected copy.<\/p>\n<p>A folder may be signed in two manners:<\/p>\n<p><strong>With an embedded signature<\/strong><\/p>\n<p>This method protects a file by integrating the signature bloc directly into the script. It offers an ease of use of the files because the signature is situated in the file itself. The drawback of this method is that it does not support many file formats and does not detect if a file has been signed from a signed catalog file (the following method). The result of the command PowerShell <strong>Get-AuthenticodeSignature<\/strong> returns &#8216;Valid&#8217; if the file is signed with an embedded signature.<\/p>\n<p>Let&#8217;s take the example of the executable PowerShell.exe:<\/p>\n<pre class=\"lang:ps theme:powershell-output\">    PS&gt; Get-AuthenticodeSignature C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\r\n    \r\n    \u00a0\u00a0\u00a0  Directory: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\r\n    \r\n    SignerCertificate\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  Status\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  Path\r\n    -----------------\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ------\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ----\r\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  NotSigned\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  \r\n<\/pre>\n<p class=\"MsoNormal\">It is indicated as &#8216;not signed&#8217;. To correct this:<\/p>\n<pre class=\"lang:ps theme:powershell-output\">  PS&gt; $MyCert =(dir Cert:\\CurrentUser\\My -CodeSigningCert)[0]\r\n    \r\n    PS&gt; Set-AuthenticodeSignature -FilePath C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -Certificate $MyCert \r\n\r\n<\/pre>\n<p>The result of the command now returns &#8216;Valid&#8217;:<\/p>\n<pre class=\"lang:ps theme:powershell-output\"> PS&gt; Get-AuthenticodeSignature C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\r\n    \r\n    \r\n    \u00a0\u00a0\u00a0  Directory: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\r\n    \r\n    \r\n    SignerCertificate\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  Status\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  Path\r\n    -----------------\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0------\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ----\r\n    DF3B9B7E5AEA1AA0B82EA25F542A6A00963AB890\u00a0 Valid\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0powershell.exe\r\n\r\n<\/pre>\n<p><strong>With a signed catalog file<\/strong><\/p>\n<p>It is a list containing several &#8216;Hash&#8217; and acting as a signature for several files. This is different from the previous method in that the signature is &#8216;detached&#8217; from the signed file. The main advantage of this method is that it supports all types of folders. The tool SigCheck allows us to verify that a file is signed via a signed catalog file. SigCheck allows us to verify that a file is signed via a signed catalog file.<\/p>\n<h2>Conclusion<\/h2>\n<p>By using a digital certificate to sign your PowerShell scripts, you can be alerted to malicious actions. A digital signature in a script is an insurance against a very effective way of attacking your network. Unfortunately, a signature is valid only until the expiration date of the certificate. However, as long as the script was signed while the signing certificate was valid, and there is a time stamp server, users can carry on using script indefinitely.<\/p>\n<p>The securing is very important in a production environment, but it must not become a restriction. So, before deploying your policy, you should go through a test plan to make sure that the use of signed scripts is not going to cause problems to any of the units affected.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Although PowerShell is popular, for malicious intruders it represents a very attractive attack vector into your system. The obvious way of preventing this type of penetration is to detect when a script is altered. Not only must any script that is used for system or data administration be properly secured, but also any script that is used to maintain a PowerShell profile.&hellip;<\/p>\n","protected":false},"author":158223,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[35],"tags":[4635,4871],"coauthors":[6804],"class_list":["post-2123","post","type-post","status-publish","format-standard","hentry","category-powershell","tag-powershell","tag-sysadmin"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2123","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/158223"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=2123"}],"version-history":[{"count":13,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2123\/revisions"}],"predecessor-version":[{"id":78640,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2123\/revisions\/78640"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=2123"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=2123"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=2123"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=2123"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}