Skip to content

Commit bd5be06

Browse files
committed
added additional unsafe deserializers to powershell query
1 parent decbe38 commit bd5be06

3 files changed

Lines changed: 399 additions & 22 deletions

File tree

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

Lines changed: 147 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,23 @@ private import semmle.code.powershell.dataflow.DataFlow
88
import semmle.code.powershell.ApiGraphs
99
private import semmle.code.powershell.dataflow.flowsources.FlowSources
1010
private import semmle.code.powershell.Cfg
11+
private import powershell
1112

1213
module UnsafeDeserialization {
1314
/**
14-
* A data flow source for SQL-injection vulnerabilities.
15+
* A data flow source for unsafe deserialization vulnerabilities.
1516
*/
1617
abstract class Source extends DataFlow::Node {
1718
/** Gets a string that describes the type of this flow source. */
1819
abstract string getSourceType();
1920
}
2021

2122
/**
22-
* A data flow sink for SQL-injection vulnerabilities.
23+
* A data flow sink for unsafe deserialization vulnerabilities.
2324
*/
2425
abstract class Sink extends DataFlow::Node {
2526
/** Gets a description of this sink. */
2627
abstract string getSinkType();
27-
2828
}
2929

3030
/**
@@ -37,17 +37,156 @@ module UnsafeDeserialization {
3737
override string getSourceType() { result = SourceNode.super.getSourceType() }
3838
}
3939

40+
/**
41+
* Holds if the `ObjectCreationNode` `ocn` constructs a type whose fully qualified name
42+
* (lowercase) matches `fullTypeName`. Handles both `New-Object TypeName` and
43+
* `[TypeName]::new()` patterns.
44+
*/
45+
private predicate objectCreationMatchesType(
46+
DataFlow::ObjectCreationNode ocn, string fullTypeName
47+
) {
48+
// New-Object TypeName: getLowerCaseConstructedTypeName() returns the full qualified name
49+
ocn.getLowerCaseConstructedTypeName() = fullTypeName
50+
or
51+
// [TypeName]::new(): access the qualifier TypeNameExpr for the full qualified name
52+
ocn.getExprNode().getExpr().(ConstructorCall).getQualifier().(TypeNameExpr)
53+
.getPossiblyQualifiedName() = fullTypeName
54+
}
55+
56+
/**
57+
* Holds if `typeName` (lowercase, fully qualified) is a known unsafe deserializer type
58+
* and `methodName` (lowercase) is an unsafe deserialization instance method on that type.
59+
*/
60+
private predicate unsafeInstanceDeserializer(string typeName, string methodName) {
61+
typeName = "system.runtime.serialization.formatters.soap.soapformatter" and
62+
methodName = "deserialize"
63+
or
64+
typeName = "system.web.ui.objectstateformatter" and
65+
methodName = "deserialize"
66+
or
67+
typeName = "system.runtime.serialization.netdatacontractserializer" and
68+
methodName = ["deserialize", "readobject"]
69+
or
70+
typeName = "system.web.ui.losformatter" and
71+
methodName = "deserialize"
72+
or
73+
typeName = "system.data.dataset" and
74+
methodName = "readxmlschema"
75+
or
76+
typeName = "system.data.datatable" and
77+
methodName = ["readxmlschema", "readxml"]
78+
or
79+
typeName = "yamldotnet.serialization.deserializer" and
80+
methodName = "deserialize"
81+
}
82+
83+
/**
84+
* Holds if `typeName` (lowercase, fully qualified) has a static method
85+
* `methodName` (lowercase) that is an unsafe deserializer.
86+
*/
87+
private predicate unsafeStaticDeserializer(string typeName, string methodName) {
88+
typeName = "system.windows.markup.xamlreader" and
89+
methodName = ["parse", "load", "loadasync"]
90+
or
91+
typeName = "system.workflow.componentmodel.activity" and
92+
methodName = "load"
93+
or
94+
typeName = "memorypack.memorypackserializer" and
95+
methodName = "deserialize"
96+
}
97+
98+
/**
99+
* Holds if creating an instance of `typeName` (lowercase, fully qualified) with
100+
* untrusted arguments is an unsafe deserialization.
101+
*/
102+
private predicate unsafeDeserializerConstructor(string typeName) {
103+
typeName = "system.resources.resourcereader"
104+
or
105+
typeName = "system.resources.resxresourcereader"
106+
}
107+
108+
/**
109+
* An argument to a BinaryFormatter deserialization method call, including
110+
* Deserialize, UnsafeDeserialize, and UnsafeDeserializeMethodResponse.
111+
*/
40112
class BinaryFormatterDeserializeSink extends Sink {
41113
BinaryFormatterDeserializeSink() {
42-
exists(DataFlow::ObjectCreationNode ocn, DataFlow::CallNode cn |
43-
cn.getQualifier().getALocalSource() = ocn and
44-
ocn.getExprNode().getExpr().(CallExpr).getAnArgument().getValue().asString() = "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter" and
45-
cn.getLowerCaseName() = "deserialize" and
114+
exists(DataFlow::ObjectCreationNode ocn, DataFlow::CallNode cn |
115+
cn.getQualifier().getALocalSource() = ocn and
116+
objectCreationMatchesType(ocn,
117+
"system.runtime.serialization.formatters.binary.binaryformatter") and
118+
cn.getLowerCaseName() =
119+
["deserialize", "unsafedeserialize", "unsafedeserializemethodresponse"] and
46120
cn.getAnArgument() = this
47-
)
121+
)
48122
}
49123

50124
override string getSinkType() { result = "call to BinaryFormatter.Deserialize" }
125+
}
126+
127+
/**
128+
* An argument to an unsafe deserialization instance method call.
129+
* Covers SoapFormatter, ObjectStateFormatter, NetDataContractSerializer,
130+
* LosFormatter, DataSet, DataTable, and YamlDotNet deserializers.
131+
*/
132+
class InstanceDeserializerSink extends Sink {
133+
string typeName;
134+
string methodName;
135+
136+
InstanceDeserializerSink() {
137+
unsafeInstanceDeserializer(typeName, methodName) and
138+
exists(DataFlow::ObjectCreationNode ocn, DataFlow::CallNode cn |
139+
cn.getQualifier().getALocalSource() = ocn and
140+
objectCreationMatchesType(ocn, typeName) and
141+
cn.getLowerCaseName() = methodName and
142+
cn.getAnArgument() = this
143+
)
144+
}
145+
146+
override string getSinkType() { result = "call to " + typeName + "." + methodName }
147+
}
148+
149+
/**
150+
* An argument to an unsafe static deserialization method call.
151+
* Covers XamlReader, Activity.Load, and MemoryPackSerializer.
152+
*/
153+
class StaticDeserializerSink extends Sink {
154+
string typeName;
155+
string methodName;
156+
157+
StaticDeserializerSink() {
158+
unsafeStaticDeserializer(typeName, methodName) and
159+
exists(DataFlow::CallNode cn |
160+
cn.getAnArgument() = this and
161+
cn.getLowerCaseName() = methodName and
162+
exists(InvokeMemberExpr ime |
163+
ime = cn.getExprNode().getExpr() and
164+
ime.isStatic() and
165+
ime.getQualifier().(TypeNameExpr).getPossiblyQualifiedName() = typeName
166+
)
167+
)
168+
}
169+
170+
override string getSinkType() {
171+
result = "call to [" + typeName + "]::" + methodName
172+
}
173+
}
174+
175+
/**
176+
* An argument to a constructor of an unsafe deserializer type.
177+
* Covers ResourceReader and ResXResourceReader constructors.
178+
*/
179+
class UnsafeConstructorSink extends Sink {
180+
string typeName;
181+
182+
UnsafeConstructorSink() {
183+
unsafeDeserializerConstructor(typeName) and
184+
exists(DataFlow::ObjectCreationNode ocn |
185+
objectCreationMatchesType(ocn, typeName) and
186+
ocn.getAnArgument() = this
187+
)
188+
}
51189

190+
override string getSinkType() { result = "constructor of " + typeName }
52191
}
53192
}

0 commit comments

Comments
 (0)