Skip to content

Commit aeec7fd

Browse files
authored
Merge pull request #368 from microsoft/copilot/ps-deserialization-precision-20260615
Improve PowerShell deserialization precision
2 parents 7dbad99 + 2e9bc99 commit aeec7fd

13 files changed

Lines changed: 158 additions & 155 deletions

File tree

powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,8 @@ class FlowSummaryNode extends NodeImpl, TFlowSummaryNode {
743743

744744
override EmptyLocation getLocationImpl() { any() }
745745

746+
override predicate nodeIsHidden() { any() }
747+
746748
override string toStringImpl() { result = this.getSummaryNode().toString() }
747749
}
748750

powershell/ql/lib/semmle/code/powershell/frameworks/System.IO.model.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ extensions:
3737
extensible: summaryModel
3838
data:
3939
- ["system.io.path!", "Method[getfullpath]", "Argument[0]", "ReturnValue", "taint"]
40+
- ["system.io.memorystream!", "Method[new]", "Argument[0]", "ReturnValue", "taint"]
41+
- ["system.io.stringreader!", "Method[new]", "Argument[0]", "ReturnValue", "taint"]
4042
- ["system.io.file!", "Method[readallbytes]", "Argument[0]", "ReturnValue", "taint"]
4143
- ["system.io.file!", "Method[readallbytesasync]", "Argument[0]", "ReturnValue", "taint"]
4244
- ["system.io.file!", "Method[appendtext]", "Argument[0]", "ReturnValue", "taint"]
@@ -45,4 +47,5 @@ extensions:
4547
- ["system.io.file!", "Method[readalllinesasync]", "Argument[0]", "ReturnValue", "taint"]
4648
- ["system.io.file!", "Method[readalltext]", "Argument[0]", "ReturnValue", "taint"]
4749
- ["system.io.file!", "Method[readalltextasync]", "Argument[0]", "ReturnValue", "taint"]
48-
- ["system.io.fileinfo", "Method[createtext]", "Argument[0]", "ReturnValue", "taint"]
50+
- ["system.io.fileinfo", "Method[createtext]", "Argument[0]", "ReturnValue", "taint"]
51+

powershell/ql/lib/semmle/code/powershell/frameworks/System.model.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ extensions:
1010
- ["system.environment!", "Method[getcommandlineargs].ReturnValue", "command-line"]
1111
- ["system.environment!", "Method[getenvironmentvariable].ReturnValue", "environment"]
1212
- ["system.environment!", "Method[getenvironmentvariables].ReturnValue", "environment"]
13+
14+
- addsTo:
15+
pack: microsoft/powershell-all
16+
extensible: summaryModel
17+
data:
18+
- ["system.convert!", "Method[frombase64string]", "Argument[0]", "ReturnValue", "taint"]

powershell/ql/lib/semmle/code/powershell/security/UnsafeDeserializationCustomizations.qll

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ module UnsafeDeserialization {
2727
abstract string getSinkType();
2828
}
2929

30-
/**
31-
* A sanitizer for Unsafe Deserialization vulnerabilities.
32-
*/
33-
abstract class Sanitizer extends DataFlow::Node { }
34-
3530
/** A source of user input, considered as a flow source for unsafe deserialization. */
3631
class FlowSourceAsSource extends Source instanceof SourceNode {
3732
override string getSourceType() { result = SourceNode.super.getSourceType() }
@@ -42,14 +37,16 @@ module UnsafeDeserialization {
4237
* (lowercase) matches `fullTypeName`. Handles both `New-Object TypeName` and
4338
* `[TypeName]::new()` patterns.
4439
*/
45-
private predicate objectCreationMatchesType(
46-
DataFlow::ObjectCreationNode ocn, string fullTypeName
47-
) {
40+
private predicate objectCreationMatchesType(DataFlow::ObjectCreationNode ocn, string fullTypeName) {
4841
// New-Object TypeName: getLowerCaseConstructedTypeName() returns the full qualified name
4942
ocn.getLowerCaseConstructedTypeName() = fullTypeName
5043
or
5144
// [TypeName]::new(): access the qualifier TypeNameExpr for the full qualified name
52-
ocn.getExprNode().getExpr().(ConstructorCall).getQualifier().(TypeNameExpr)
45+
ocn.getExprNode()
46+
.getExpr()
47+
.(ConstructorCall)
48+
.getQualifier()
49+
.(TypeNameExpr)
5350
.getPossiblyQualifiedName() = fullTypeName
5451
}
5552

@@ -106,20 +103,28 @@ module UnsafeDeserialization {
106103
}
107104

108105
/**
109-
* An argument to a BinaryFormatter deserialization method call, including
110-
* Deserialize, UnsafeDeserialize, and UnsafeDeserializeMethodResponse.
106+
* A BinaryFormatter deserialization method call, including Deserialize, UnsafeDeserialize,
107+
* and UnsafeDeserializeMethodResponse.
111108
*/
112-
class BinaryFormatterDeserializeSink extends Sink {
113-
BinaryFormatterDeserializeSink() {
114-
exists(DataFlow::ObjectCreationNode ocn, DataFlow::CallNode cn |
115-
cn.getQualifier().getALocalSource() = ocn and
109+
class BinaryFormatterDeserializeCall extends DataFlow::CallNode {
110+
BinaryFormatterDeserializeCall() {
111+
exists(DataFlow::ObjectCreationNode ocn |
112+
this.getQualifier().getALocalSource() = ocn and
116113
objectCreationMatchesType(ocn,
117114
"system.runtime.serialization.formatters.binary.binaryformatter") and
118-
cn.getLowerCaseName() =
119-
["deserialize", "unsafedeserialize", "unsafedeserializemethodresponse"] and
120-
cn.getAnArgument() = this
115+
this.getLowerCaseName() =
116+
["deserialize", "unsafedeserialize", "unsafedeserializemethodresponse"]
121117
)
122118
}
119+
}
120+
121+
/**
122+
* An input argument to a BinaryFormatter deserialization method call.
123+
*/
124+
class BinaryFormatterDeserializeSink extends Sink {
125+
BinaryFormatterDeserializeSink() {
126+
exists(BinaryFormatterDeserializeCall cn | cn.getAnArgument() = this)
127+
}
123128

124129
override string getSinkType() { result = "call to BinaryFormatter.Deserialize" }
125130
}
@@ -167,9 +172,7 @@ module UnsafeDeserialization {
167172
)
168173
}
169174

170-
override string getSinkType() {
171-
result = "call to [" + typeName + "]::" + methodName
172-
}
175+
override string getSinkType() { result = "call to [" + typeName + "]::" + methodName }
173176
}
174177

175178
/**

powershell/ql/lib/semmle/code/powershell/security/UnsafeDeserializationQuery.qll

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ module Config implements DataFlow::ConfigSig {
1717
predicate isSource(DataFlow::Node source) { source instanceof Source }
1818

1919
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
20-
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo){
21-
exists(InvokeMemberExpr ime |
22-
nodeTo.asExpr().getExpr() = ime and
23-
nodeFrom.asExpr().getExpr() = ime.getAnArgument()
20+
21+
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
22+
exists(InvokeMemberExpr ime |
23+
ime.getLowerCaseName() = "getbytes" and
24+
nodeTo.asExpr().getExpr() = ime and
25+
nodeFrom.asExpr().getExpr() = ime.getAnArgument()
2426
)
2527
}
2628
}
2729

28-
module UnsafeDeserializationFlow = TaintTracking::Global<Config>;
30+
module UnsafeDeserializationFlow = TaintTracking::Global<Config>;

powershell/ql/src/queries/security/cwe-502/BinaryFormatterDeserialization.qhelp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,28 @@
44
<qhelp>
55
<overview>
66

7-
<p>Using <code>BinaryFormatter</code> to deserialize an object from untrusted input may result in security problems, such
8-
as denial of service or remote code execution.</p>
7+
<p><code>BinaryFormatter</code> is unsafe for deserialization and should not be used, even when
8+
the data is produced locally. This query is an API-use warning: it reports calls to
9+
<code>BinaryFormatter</code> deserialization methods without proving that the serialized data is
10+
attacker-controlled.</p>
11+
12+
<p>When attacker-controlled data is known to flow into a deserializer, the
13+
<code>powershell/microsoft/public/unsafe-deserialization</code> query reports that higher-confidence
14+
data-flow result.</p>
915

1016
</overview>
1117
<recommendation>
1218

13-
<p>Avoid using <code>BinaryFormatter</code>.</p>
19+
<p>Avoid using <code>BinaryFormatter</code>. Prefer serializers that do not permit arbitrary type
20+
instantiation, and validate or authenticate any serialized data before deserializing it.</p>
1421

1522
</recommendation>
1623
<example>
1724

1825
<p>In this example, a string is deserialized using a
19-
<code>BinaryFormatter</code>. <code>BinaryFormatter</code> is an easily exploited deserializer.</p>
26+
<code>BinaryFormatter</code>. <code>BinaryFormatter</code> is an easily exploited deserializer. If the
27+
string is attacker-controlled, this is also reported by the taint-tracking unsafe
28+
deserialization query.</p>
2029

2130
<sample src="examples/BinaryFormatterDeserialization.ps1" />
2231

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/**
2-
* @name Use of Binary Formatter deserialization
3-
* @description Use of Binary Formatter is unsafe
2+
* @name Use of BinaryFormatter deserialization API
3+
* @description BinaryFormatter deserialization is unsafe even when the input is trusted.
44
* @kind problem
5-
* @problem.severity error
6-
* @security-severity 8.8
5+
* @problem.severity warning
6+
* @security-severity 7.5
77
* @precision high
88
* @id powershell/microsoft/public/binary-formatter-deserialization
99
* @tags correctness
@@ -14,5 +14,6 @@
1414
import powershell
1515
import semmle.code.powershell.security.UnsafeDeserializationCustomizations::UnsafeDeserialization
1616

17-
from BinaryFormatterDeserializeSink sink
18-
select sink, "Call to BinaryFormatter.Deserialize"
17+
from BinaryFormatterDeserializeCall call
18+
select call,
19+
"This call uses BinaryFormatter deserialization. BinaryFormatter is unsafe; if this data can be attacker-controlled, the unsafe-deserialization query reports the data flow."

powershell/ql/src/queries/security/cwe-502/UnsafeDeserialization.qhelp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@
44
<qhelp>
55
<overview>
66

7-
<p>Deserializing an object from untrusted input may result in security problems, such
8-
as denial of service or remote code execution.</p>
7+
<p>Deserializing attacker-controlled data with an unsafe deserializer may result in
8+
security problems, such as denial of service or remote code execution. This query tracks
9+
untrusted data flow into unsafe deserialization APIs.</p>
10+
11+
<p>Local, self-produced serialized data without any untrusted source is not reported by
12+
this query. The separate <code>powershell/microsoft/public/binary-formatter-deserialization</code>
13+
query reports <code>BinaryFormatter</code> API use even when no attacker-controlled data flow is
14+
known.</p>
915

1016
</overview>
1117
<recommendation>
1218

13-
<p>Avoid using an unsafe deserialization framework.</p>
19+
<p>Avoid using unsafe deserialization frameworks for attacker-controlled data. Prefer safe
20+
serializers, and validate, authenticate, or otherwise constrain serialized data before
21+
deserializing it.</p>
1422

1523
</recommendation>
1624
<example>

0 commit comments

Comments
 (0)