Windows Script Host: The .vbs and .js Execution Surface Attackers Still Rely On
wscript.exe and cscript.exe run scripts with your full token and no compiler in the way. Here is how WSH gets abused and the three registry settings that shut the door.
Everyone hardens PowerShell now — constrained language mode, script block logging, AMSI. Meanwhile the other scripting engine that ships in every copy of Windows sits wide open by default: the Windows Script Host. A double-clickable invoice.vbs or update.js handed to wscript.exe runs with your full user token, no compiler, no build step, and none of the SmartScreen friction an .exe download gets. That is exactly why commodity malware and phishing kits have shipped script droppers for two decades and still do.
What WSH actually is
Windows Script Host is the runtime behind two binaries that are present on every Windows install:
wscript.exe— the windowed host (double-click a script and this runs it).cscript.exe— the console host (used from a command line or another script).
Between them they execute .vbs (VBScript), .js (JScript, not browser JavaScript), .wsf (Windows Script Files, which can mix languages), and .wsh settings files. Both are signed Microsoft binaries, so they sail past naive allow-listing and blend into normal process trees — a textbook "living off the land" pair.
How it gets abused
The pattern is boringly consistent across intrusions:
- Initial access. A ZIP or ISO email attachment contains a small
.jsor.vbs. The user double-clicks it.wscript.exeruns it — no Mark-of-the-Web prompt the way an executable would trigger, and no antivirus "you are about to run a program" gate on many configurations. - Download-and-execute. The script is tiny: a handful of lines using
MSXML2.XMLHTTPorWScript.Shellto pull a second-stage payload and launch it. The malicious logic lives on a server, so the file on disk looks almost empty to a scanner. - Persistence. A
.vbsplanted in the Startup folder or referenced from a Run key re-launches on every logon — quieter than a new service and trivial to write. - Remote scripts. If WSH is allowed to run scripts from UNC paths, a lure only has to point at a share; nothing needs to touch the local disk first.
None of this needs an exploit or admin rights. It is just a default-on feature doing exactly what it was designed to do.
The three registry settings that close the door
WSH behaviour is driven by values under HKLM\SOFTWARE\Microsoft\Windows Script Host\Settings (a per-user copy under HKCU exists too, but the machine-wide key is the one to control). Three of them matter for hardening.
1. Turn WSH off entirely: Enabled
If the machine never legitimately runs .vbs/.js scripts — and most user endpoints do not — the highest-value move is to disable the engine outright. Set Enabled = 0 and both wscript.exe and cscript.exe refuse to run any script, printing "Windows Script Host access is disabled on this machine." That removes the entire class of script-dropper delivery in one value.
New-Item -Path 'HKLM:\SOFTWARE\Microsoft\Windows Script Host\Settings' -Force | Out-Null
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows Script Host\Settings' `
-Name Enabled -Type DWord -Value 0
Absent or 1 means WSH is on — that is the Windows default, so an untouched machine is exposed. Test in your environment first: a few legacy installers and logon scripts still rely on WSH.
2. Block remote scripts: Remote
The Remote value controls whether WSH will execute scripts loaded from a remote (UNC / network) location. If you leave WSH enabled for local automation, at least keep it to local files: Remote = 0 (or absent) means local only; 1 lets a script run straight off a share.
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows Script Host\Settings' `
-Name Remote -Type DWord -Value 0
3. Require signed scripts: TrustPolicy
If WSH must stay on, the strongest remaining control is to demand an Authenticode signature. TrustPolicy = 2 runs only scripts signed by a trusted publisher; 1 prompts the user for unsigned scripts (better than nothing, but relies on a human decision); 0 or absent runs anything. Signed-only stops unsigned commodity droppers cold.
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows Script Host\Settings' `
-Name TrustPolicy -Type DWord -Value 2
Layer it with the controls you already have
Disabling or signing WSH pairs well with the rest of a script-execution defence:
- Attack Surface Reduction rules that block
wscript.exe/cscript.exefrom launching child processes or from running downloaded, obfuscated scripts. - Application allow-listing (WDAC / AppLocker) with script enforcement, which puts WSH scripts under the same policy as executables.
- Removing the file-type handler so
.vbs/.jsdouble-clicks open Notepad instead of executing.
WSH hardening is not a replacement for those — it is the cheap, single-value first layer that a lot of fleets simply never set.
Auditing it with WinSentinel
WinSentinel reads these three values locally and grades them so you do not have to eyeball registry keys machine by machine. It flags an enabled WSH on endpoints that do not need it, warns when remote scripts are allowed, and reports whether signature enforcement is in place — each finding with the exact Set-ItemProperty fix. It is part of the free, single-machine audit: run it and you will know your wscript.exe posture in seconds.
dotnet tool install --global WinSentinel.Cli
winsentinel --audit
The script host has been an attacker favourite since before "living off the land" had a name. Three registry values, checked and set once, take it off the board.