Enfore execution policy on scripts downloaded using Invoke-WebRequest

Hi,

When I download a script using Invoke-WebRequest or WebClient.DownloadFile, it seems like the script is not marked as “downloaded” (not blocked), and it’s signature is not verified (in case the execution policy is RemoteSigned). Is there a way to download the file so it will be marked as downloaded? I’ve searched for solutions but everything I found was how to unblock the script or bypass the execution policy, I want the opposite :slight_smile:
And an extra question - if I don’t want to save the downloaded script to disk, just run it immediately from the web response, I can pipe the response to Invoke-Expression. I tried that and it worked, but following the previous question, is there a way to enforce execution policy in this case? It’s not really treated as a script in this case so I don’t think so but worth asking…

Thanks!
Gabriel

Are you looking at this from a security standpoint? The execution policy is not really a security control and can easily be bypassed:

PowerShell Execution Policy Explained (thinkpowershell.com)

Typically, the methods you are mentioning are how malicious actors execute code on machines. Go to your favorite video platform and do some searching for powershell cyber security and watch the webinars. There are guides on best practices on locking down Powershell, although to have more control and tracking with active blocking, you may need to have 3rd party software to mitigate risk.

Intel Insights: How to Secure PowerShell (cisecurity.org)

2 Likes

The mechanism used to identify a file as downloaded from the internet is “Alternate Data Stream”

More specifically the Zone.Identifier stream. Find a file that shows the option to unblock it, and inspect with Get-Item/Get-Content or other stream supporting cmdlets

Get-Item C:\users\Doug\Downloads\PSFolderSize-master.zip -Stream *


PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\users\Doug\Downloads\PSFolderSize-master.zip::$DATA
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\users\Doug\Downloads
PSChildName   : PSFolderSize-master.zip::$DATA
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\users\Doug\Downloads\PSFolderSize-master.zip
Stream        : :$DATA
Length        : 33667

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\users\Doug\Downloads\PSFolderSize-master.zip:Zone.Identifier
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\users\Doug\Downloads
PSChildName   : PSFolderSize-master.zip:Zone.Identifier
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\users\Doug\Downloads\PSFolderSize-master.zip
Stream        : Zone.Identifier
Length        : 163
Get-Content C:\users\Doug\Downloads\PSFolderSize-master.zip -Stream Zone.Identifier

[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://github.com/gngrninja/PSFolderSize
HostUrl=https://codeload.github.com/gngrninja/PSFolderSize/zip/refs/heads/master

All Unblock-File does is clear/remove this stream. You can achieve the same result by running

Set-Content C:\users\Doug\Downloads\PSFolderSize-master.zip -Stream Zone.Identifier -Value ""

or

Clear-Content C:\Users\Doug\Downloads\PSFolderSize-master.zip -Stream Zone.Identifier

Conversely, you can set the value of this data stream (as well as create any number of other alternate data streams) - it accepts an array of strings. The minimum requirement from my testing is to add these two lines.

'[ZoneTransfer]'
'ZoneId=3'

Here is an example

Set-Content C:\users\Doug\Downloads\PSFolderSize-master.zip -Stream Zone.Identifier -Value @(
'[ZoneTransfer]'
'ZoneId=3'
)

Now when you view the properties it should show blocked, and I believe this is what you are trying to do.

1 Like

Hi, thanks for you response!

I am looking at this from security perspective, I understand the execution policies can be easily bypassed if you are already on the machine, but I’m referring to something else - I have a trusted script that downloads another script from the internet and executes it, and I want to verify the second script is legit/was not tampered/the web server was not spoofed. I understand that if an attacker can change the first script they can change the way it executes the second script so the policy will be ignored, but that’s a different attack vector.

Thanks,
Gabriel

Thanks!
Do you know why downloading with Powershell does not set this stream while downloading from the browser does? I understand that Invoke-WebRequest uses IE components under the hood so I would assume that it should behave the same.