Skip to content

Commit 3101cc8

Browse files
authored
Merge pull request #253 from microsoft/add-set-execution-policy-bypass-query
PS: Add query for insecure uses of `Set-ExecutionPolicy`
2 parents 1d64a79 + 398d27b commit 3101cc8

File tree

8 files changed

+132
-2
lines changed

8 files changed

+132
-2
lines changed

powershell/ql/lib/semmle/code/powershell/ast/internal/Raw/ChildIndex.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ newtype ChildIndex =
1414
CatchClauseBody() or
1515
CatchClauseType(int i) { exists(any(CatchClause c).getCatchType(i)) } or
1616
CmdElement_(int i) { exists(any(Cmd cmd).getElement(i)) } or // TODO: Get rid of this?
17+
CmdParameterExpr() or
1718
CmdCallee() or
1819
CmdRedirection(int i) { exists(any(Cmd cmd).getRedirection(i)) } or
1920
CmdExprExpr() or
@@ -127,6 +128,8 @@ string stringOfChildIndex(ChildIndex i) {
127128
or
128129
i = CmdElement_(_) and result = "CmdElement"
129130
or
131+
i = CmdParameterExpr() and result = "CmdParameterExpr"
132+
or
130133
i = CmdCallee() and result = "CmdCallee"
131134
or
132135
i = CmdRedirection(_) and result = "CmdRedirection"

powershell/ql/lib/semmle/code/powershell/ast/internal/Raw/CommandParameter.qll

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ class CmdParameter extends @command_parameter, CmdElement {
55

66
string getName() { command_parameter(this, result) }
77

8-
Ast getExpr() {
9-
command_parameter_argument(this, result)
8+
Ast getExpr() { command_parameter_argument(this, result) }
9+
10+
final override Ast getChild(ChildIndex i) {
11+
i instanceof CmdParameterExpr and
12+
result = this.getExpr()
1013
}
1114

1215
Cmd getCmd() { result.getElement(_) = this }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>The command <code>Set-ExecutionPolicy</code> is used to set the execution policies for Windows computers.
7+
The execution policy is used to determine which configuration files can be loaded and which scripts can be run.
8+
Setting the execution policy to <code>Bypass</code> disables all warnings and signature checks for script execution,
9+
allowing any script—including malicious or unsigned code—to run without restriction.</p>
10+
</overview>
11+
12+
<recommendation>
13+
<p>Always prefer <code>AllSigned</code> to enforce full signature verification.</p>
14+
<p>If this is not possible, set the execution policy to <code>RemoteSigned</code> to allow local scripts while requiring downloaded scripts to be signed.</p>
15+
<p>Always limit the scope of the execution policy by supplying the most restrictive <code>Scope</code> as possible. Use <code>Process</code> to limit the execution policy to the current PowerShell session. When no <code>Scope</code> is supplied the execution policy change is applied system-wide.
16+
</recommendation>
17+
18+
<example>
19+
<p>In the following example, <code>Set-ExecutionPolicy</code> is called twice</p>
20+
21+
<p>The first call sets the execution policy to <code>Bypass</code> which allows any script to be run.</p>
22+
23+
<p>The second call sets the execution policy to <code>RemoteSigned</code> which allows local scripts to be run,
24+
but requires scripts and configurations downloaded from the Internet to be signed.</p>
25+
26+
<sample src="examples/InsecureExecutionPolicy.ps1" />
27+
</example>
28+
29+
<references>
30+
<li>MSDN: <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy">Set-ExecutionPolicy</a>.</li>
31+
</references>
32+
</qhelp>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @name Insecure execution policy
3+
* @description Calling `Set-ExecutionPolicy` with an insecure execution policy argument may allow
4+
* attackers to execute malicious scripts or load malicious configurations.
5+
* @kind problem
6+
* @problem.severity error
7+
* @security-severity 8.8
8+
* @precision high
9+
* @id powershell/microsoft/public/insecure-execution-policy
10+
* @tags correctness
11+
* security
12+
* external/cwe/cwe-250
13+
*/
14+
15+
import powershell
16+
17+
/** A call to `Set-ExecutionPolicy`. */
18+
class SetExecutionPolicy extends CmdCall {
19+
SetExecutionPolicy() { this.getAName() = "Set-ExecutionPolicy" }
20+
21+
/** Gets the execution policy of this call to `Set-ExecutionPolicy`. */
22+
Expr getExecutionPolicy() {
23+
result = this.getNamedArgument("executionpolicy")
24+
or
25+
not this.hasNamedArgument("executionpolicy") and
26+
result = this.getPositionalArgument(0)
27+
}
28+
29+
/** Gets the scope of this call to `Set-ExecutionPolicy`, if any. */
30+
Expr getScope() {
31+
result = this.getNamedArgument("scope")
32+
or
33+
not this.hasNamedArgument("scope") and
34+
(
35+
// The ExecutionPolicy argument has position 0 so if is present as a
36+
// named argument then the position of the Scope argument is 0. However,
37+
// if the ExecutionPolicy is present as a positional argument then the
38+
// Scope argument is at position 1.
39+
if this.hasNamedArgument("executionpolicy")
40+
then result = this.getPositionalArgument(0)
41+
else result = this.getPositionalArgument(1)
42+
)
43+
}
44+
45+
/** Holds if the argument `flag` is supplied with a `$true` value. */
46+
predicate isForced() { this.getNamedArgument("force").getValue().asBoolean() = true }
47+
}
48+
49+
class Process extends Expr {
50+
Process() { this.getValue().stringMatches("process") }
51+
}
52+
53+
class Bypass extends Expr {
54+
Bypass() { this.getValue().stringMatches("Bypass") }
55+
}
56+
57+
class BypassSetExecutionPolicy extends SetExecutionPolicy {
58+
BypassSetExecutionPolicy() { this.getExecutionPolicy() instanceof Bypass }
59+
}
60+
61+
from BypassSetExecutionPolicy setExecutionPolicy
62+
where
63+
not setExecutionPolicy.getScope() instanceof Process and
64+
setExecutionPolicy.isForced()
65+
select setExecutionPolicy, "Insecure use of 'Set-ExecutionPolicy'."
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Invoke-WebRequest -Uri "https://example.com/script.ps1" -OutFile "C:\Path\To\script.ps1"
2+
3+
# BAD: No warnings or prompts when running potentially unsafe scripts
4+
Set-ExecutionPolicy Bypass
5+
& "C:\Path\To\script.ps1" # Will never be blocked
6+
7+
# GOOD: Requires that scripts and configuration files downloaded from the Internet are signed
8+
Set-ExecutionPolicy RemoteSigned
9+
& "C:\Path\To\script.ps1" # Will not run unless script.ps1 is signed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| test.ps1:1:1:1:33 | Call to set-executionpolicy | Insecure use of 'Set-ExecutionPolicy'. |
2+
| test.ps1:5:1:5:54 | Call to set-executionpolicy | Insecure use of 'Set-ExecutionPolicy'. |
3+
| test.ps1:7:1:7:39 | Call to set-executionpolicy | Insecure use of 'Set-ExecutionPolicy'. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/security/cwe-250/InsecureExecutionPolicy.ql
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Set-ExecutionPolicy Bypass -Force # BAD
2+
Set-ExecutionPolicy RemoteSigned -Force # GOOD
3+
Set-ExecutionPolicy Bypass -Scope Process -Force # GOOD
4+
Set-ExecutionPolicy RemoteSigned -Scope Process -Force # GOOD
5+
Set-ExecutionPolicy Bypass -Scope MachinePolicy -Force # BAD
6+
7+
Set-ExecutionPolicy Bypass -Force:$true # BAD
8+
Set-ExecutionPolicy Bypass -Force:$false # GOOD
9+
10+
Set-ExecutionPolicy Bypass # GOOD
11+
Set-ExecutionPolicy RemoteSigned # GOOD
12+
Set-ExecutionPolicy Bypass -Scope Process # GOOD
13+
Set-ExecutionPolicy RemoteSigned -Scope Process # GOOD
14+
Set-ExecutionPolicy Bypass -Scope MachinePolicy # GOOD

0 commit comments

Comments
 (0)