@@ -8,23 +8,23 @@ private import semmle.code.powershell.dataflow.DataFlow
88import semmle.code.powershell.ApiGraphs
99private import semmle.code.powershell.dataflow.flowsources.FlowSources
1010private import semmle.code.powershell.Cfg
11+ private import powershell
1112
1213module 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