Skip to content

Commit 378e74a

Browse files
committed
making TypeNameHandlingQuery.qll file readable
1 parent a2d5377 commit 378e74a

1 file changed

Lines changed: 168 additions & 67 deletions

File tree

Lines changed: 168 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,55 @@
1+
/**
2+
* Provides classes and predicates for detecting unsafe usage of
3+
* Newtonsoft.Json `TypeNameHandling` settings.
4+
*
5+
* Setting `TypeNameHandling` to any value other than `None` during
6+
* deserialization can enable remote code execution if untrusted data
7+
* is deserialized without a custom `SerializationBinder`.
8+
*/
9+
110
import csharp
211
import semmle.code.csharp.security.dataflow.flowsources.Remote
312
import semmle.code.csharp.serialization.Deserializers
413

14+
// ---------------------------------------------------------------------------
15+
// Source expressions: unsafe TypeNameHandling values
16+
// ---------------------------------------------------------------------------
17+
/**
18+
* An expression that represents an unsafe `TypeNameHandling` value —
19+
* any member of the `TypeNameHandling` enum other than `None`, or an
20+
* integer literal greater than zero (which maps to a non-`None` value).
21+
*/
522
class BadTypeHandling extends Expr {
623
BadTypeHandling() {
724
exists(Enum e, EnumConstant c |
825
e.hasFullyQualifiedName("Newtonsoft.Json", "TypeNameHandling") and
926
c = e.getAnEnumConstant() and
1027
this = c.getAnAccess() and
1128
not c.hasName("None")
12-
) or
29+
)
30+
or
1331
this.(IntegerLiteral).getValue().toInt() > 0
1432
}
1533
}
1634

35+
// ---------------------------------------------------------------------------
36+
// TypeNameHandling property modelling
37+
// ---------------------------------------------------------------------------
38+
/**
39+
* The `TypeNameHandling` property on `JsonSerializerSettings` or `JsonSerializer`.
40+
*/
1741
class TypeNameHandlingProperty extends Property {
18-
TypeNameHandlingProperty() {
19-
this.hasFullyQualifiedName("Newtonsoft.Json", ["JsonSerializerSettings", "JsonSerializer"], "TypeNameHandling")
20-
}
42+
TypeNameHandlingProperty() {
43+
this.hasFullyQualifiedName("Newtonsoft.Json",
44+
["JsonSerializerSettings", "JsonSerializer"], "TypeNameHandling")
45+
}
2146
}
2247

48+
/** A write to a `TypeNameHandling` property. */
2349
class TypeNameHandlingPropertyWrite extends PropertyWrite {
2450
TypeNameHandlingPropertyWrite() { this.getProperty() instanceof TypeNameHandlingProperty }
2551

52+
/** Gets the right-hand side value assigned to this property. */
2653
Expr getAssignedValue() {
2754
exists(AssignExpr a |
2855
a.getLValue() = this and
@@ -31,33 +58,80 @@ class TypeNameHandlingPropertyWrite extends PropertyWrite {
3158
}
3259
}
3360

61+
/**
62+
* A write to a `TypeNameHandling` property where the assigned value is
63+
* known to be unsafe (i.e. not `None`).
64+
*/
3465
class BadTypeHandlingPropertyWrite extends TypeNameHandlingPropertyWrite {
3566
BadTypeHandlingPropertyWrite() {
36-
exists(BadTypeHandling b |
37-
DataFlow::localExprFlow(b, this.getAssignedValue())
67+
exists(BadTypeHandling b |
68+
DataFlow::localExprFlow(b, this.getAssignedValue())
3869
)
3970
}
4071
}
4172

73+
// ---------------------------------------------------------------------------
74+
// Binder property modelling
75+
// ---------------------------------------------------------------------------
76+
/**
77+
* A write to the `SerializationBinder` or `Binder` property on
78+
* `JsonSerializerSettings` or `JsonSerializer`. Setting a custom binder
79+
* is a mitigation against unsafe `TypeNameHandling`.
80+
*/
4281
class BinderPropertyWrite extends PropertyWrite {
4382
BinderPropertyWrite() {
44-
this.getProperty().hasFullyQualifiedName("Newtonsoft.Json", ["JsonSerializerSettings", "JsonSerializer"], ["SerializationBinder", "Binder"])
83+
this.getProperty()
84+
.hasFullyQualifiedName("Newtonsoft.Json",
85+
["JsonSerializerSettings", "JsonSerializer"],
86+
["SerializationBinder", "Binder"])
4587
}
4688
}
4789

48-
module UserInputToDeserializeObjectCallConfig implements DataFlow::ConfigSig {
49-
predicate isSource(DataFlow::Node source) {
50-
source instanceof RemoteFlowSource
90+
// ---------------------------------------------------------------------------
91+
// Deserialize call argument modelling
92+
// ---------------------------------------------------------------------------
93+
/**
94+
* An argument passed to a `Newtonsoft.Json.JsonConvert.DeserializeObject` call.
95+
*/
96+
class DeserializeArg extends Expr {
97+
MethodCall deserializeCall;
98+
99+
DeserializeArg() {
100+
deserializeCall.getTarget() instanceof NewtonsoftJsonConvertClassDeserializeObjectMethod and
101+
deserializeCall.getAnArgument() = this
51102
}
52103

104+
/** Gets the enclosing `DeserializeObject` method call. */
105+
MethodCall getDeserializeCall() { result = deserializeCall }
106+
}
107+
108+
/**
109+
* A `JsonSerializerSettings`-typed argument passed to a `DeserializeObject`
110+
* call. This is the primary sink for unsafe `TypeNameHandling` flow.
111+
*/
112+
class JsonSerializerSettingsArg extends DeserializeArg {
113+
JsonSerializerSettingsArg() { this.getType() instanceof JsonSerializerSettingsClass }
114+
}
115+
116+
// ---------------------------------------------------------------------------
117+
// Taint tracking: remote input → DeserializeObject
118+
// ---------------------------------------------------------------------------
119+
/**
120+
* Tracks tainted data flowing from remote sources into arguments of
121+
* `JsonConvert.DeserializeObject`, including flow through `ToString()` calls.
122+
*/
123+
module UserInputToDeserializeObjectCallConfig implements DataFlow::ConfigSig {
124+
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
125+
53126
predicate isSink(DataFlow::Node sink) {
54-
exists(MethodCall mc |
55-
mc.getTarget().hasFullyQualifiedName("Newtonsoft.Json.JsonConvert", _) and
56-
mc.getTarget().hasUndecoratedName("DeserializeObject") and
127+
exists(MethodCall mc |
128+
mc.getTarget().hasFullyQualifiedName("Newtonsoft.Json.JsonConvert", _) and
129+
mc.getTarget().hasUndecoratedName("DeserializeObject") and
57130
sink.asExpr() = mc.getAnArgument()
58131
)
59132
}
60-
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ){
133+
134+
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
61135
exists(MethodCall ma |
62136
ma.getTarget().hasName("ToString") and
63137
ma.getQualifier() = pred.asExpr() and
@@ -66,94 +140,108 @@ module UserInputToDeserializeObjectCallConfig implements DataFlow::ConfigSig {
66140
}
67141
}
68142

69-
module UserInputToDeserializeObjectCallFlow = TaintTracking::Global<UserInputToDeserializeObjectCallConfig>;
143+
module UserInputToDeserializeObjectCallFlow =
144+
TaintTracking::Global<UserInputToDeserializeObjectCallConfig>;
70145

146+
// ---------------------------------------------------------------------------
147+
// Data flow: binder set on settings object
148+
// ---------------------------------------------------------------------------
149+
/**
150+
* Tracks whether a `SerializationBinder` is assigned via an object
151+
* initializer and the resulting settings object flows to a
152+
* `DeserializeObject` call argument.
153+
*/
71154
module BinderConfig implements DataFlow::ConfigSig {
72-
predicate isSource(DataFlow::Node source){
155+
predicate isSource(DataFlow::Node source) {
73156
exists(ObjectCreation oc, MemberInitializer mi |
74157
oc.getInitializer().(ObjectInitializer).getAMemberInitializer() = mi and
75158
mi.getInitializedMember().hasName(["Binder", "SerializationBinder"]) and
76159
source.asExpr() = oc
77160
)
78161
}
79-
predicate isSink(DataFlow::Node sink){
80-
sink.asExpr() instanceof JsonSerializerSettingsArg
81-
}
82-
}
83-
module BinderFlow = DataFlow::Global<BinderConfig>;
84162

85-
class DeserializeArg extends Expr {
86-
MethodCall deserializeCall;
87-
DeserializeArg() {
88-
deserializeCall.getTarget() instanceof NewtonsoftJsonConvertClassDeserializeObjectMethod and
89-
deserializeCall.getAnArgument() = this
90-
}
91-
MethodCall getDeserializeCall() {
92-
result = deserializeCall
93-
}
163+
predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof JsonSerializerSettingsArg }
94164
}
95165

96-
class JsonSerializerSettingsArg extends DeserializeArg {
97-
JsonSerializerSettingsArg() {
98-
this.getType() instanceof JsonSerializerSettingsClass
99-
}
100-
}
166+
module BinderFlow = DataFlow::Global<BinderConfig>;
101167

168+
// ---------------------------------------------------------------------------
169+
// Binder-set check (mitigation detection)
170+
// ---------------------------------------------------------------------------
171+
/**
172+
* Holds if a custom `SerializationBinder` or `Binder` has been set on the
173+
* settings object referenced by `arg`, either through an object initializer
174+
* (tracked via `BinderFlow`) or through a later property write on the same
175+
* variable.
176+
*/
102177
predicate hasBinderSet(JsonSerializerSettingsArg arg) {
103-
// //passed as argument to initializer
104-
exists(BinderFlow::PathNode sink |
178+
// Binder was set in an object initializer and flowed to `arg`
179+
exists(BinderFlow::PathNode sink |
105180
sink.isSink() and
106181
sink.getNode().asExpr() = arg
107-
) or
108-
//set in later Propertywrite
182+
)
183+
or
184+
// Binder was set via a property write on the same variable
109185
exists(PropertyWrite pw |
110186
pw.getProperty().hasName(["Binder", "SerializationBinder"]) and
111187
pw.getQualifier().(Access).getTarget() = arg.(Access).getTarget()
112188
)
113189
}
114190

191+
// ---------------------------------------------------------------------------
192+
// Sink node: TypeNameHandling property write value
193+
// ---------------------------------------------------------------------------
194+
/**
195+
* A data-flow node representing the value assigned to a `TypeNameHandling`
196+
* property. Provides a predicate to check whether a mitigating binder is set.
197+
*/
115198
class TypeNameHandlingPropertySink extends DataFlow::Node {
116199
TypeNameHandlingPropertySink() {
117200
exists(TypeNameHandlingPropertyWrite pw |
118201
this.asExpr() = pw.getAssignedValue()
119202
)
120203
}
121204

205+
/** Holds if a custom binder is set on the same settings object. */
122206
predicate hasBinderSet() {
123207
exists(JsonSerializerSettingsArg arg |
124208
this.asExpr() = arg and
125209
hasBinderSet(arg)
126210
)
127211
}
128-
129212
}
130213

214+
// ---------------------------------------------------------------------------
215+
// Main flow: unsafe TypeNameHandling value → DeserializeObject settings arg
216+
// ---------------------------------------------------------------------------
217+
/**
218+
* Tracks unsafe `TypeNameHandling` values flowing into `JsonSerializerSettings`
219+
* arguments of `DeserializeObject` calls. Includes additional flow steps for
220+
* integer-to-enum casts and property writes on settings objects.
221+
*/
131222
module UnsafeTypeNameHandlingFlowConfig implements DataFlow::ConfigSig {
132-
predicate isSource(DataFlow::Node source) {
133-
source.asExpr() instanceof BadTypeHandling
134-
}
223+
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof BadTypeHandling }
135224

136-
predicate isSink(DataFlow::Node sink) {
137-
sink.asExpr() instanceof JsonSerializerSettingsArg
138-
}
225+
predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof JsonSerializerSettingsArg }
139226

140227
predicate isBarrierIn(DataFlow::Node node) { isSource(node) }
141228

142229
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
230+
// Cast from integer literal to TypeNameHandling enum
143231
node1.asExpr() instanceof IntegerLiteral and
144232
node2.asExpr().(CastExpr).getExpr() = node1.asExpr()
145233
or
146234
node1.getType() instanceof TypeNameHandlingEnum and
147235
exists(TypeNameHandlingPropertyWrite pw, Assignment a |
148236
a.getLValue() = pw and
149237
(
150-
// Explicit property write: flow from the assigned value to the JsonSerializerSettingsArg
151-
// that accesses the same settings variable
238+
// Explicit property write: flow from the assigned value to the
239+
// JsonSerializerSettingsArg that accesses the same settings variable
152240
node1.asExpr() = a.getRValue() and
153241
node2.asExpr().(JsonSerializerSettingsArg).(VariableAccess).getTarget() =
154242
pw.getQualifier().(VariableAccess).getTarget()
155243
or
156-
// ObjectInitializer case: flow from the member initializer value to the
244+
// Object initializer: flow from the member initializer value to the
157245
// ObjectCreation, which then flows locally to the JsonSerializerSettingsArg
158246
exists(ObjectInitializer oi, ObjectCreation oc |
159247
node1.asExpr() = oi.getAMemberInitializer().getRValue() and
@@ -167,52 +255,65 @@ module UnsafeTypeNameHandlingFlowConfig implements DataFlow::ConfigSig {
167255

168256
module UnsafeTypeNameHandlingFlow = DataFlow::Global<UnsafeTypeNameHandlingFlowConfig>;
169257

258+
// ---------------------------------------------------------------------------
259+
// Settings / serializer object creation modelling
260+
// ---------------------------------------------------------------------------
170261
/**
171-
* An ObjectCreation of type `Newtonson.Json.JsonSerializerSettings.JsonSerializerSettings` or `Newtonson.Json.JsonSerializerSettings.JsonSerializer`.
262+
* An `ObjectCreation` of type `Newtonsoft.Json.JsonSerializerSettings` or
263+
* `Newtonsoft.Json.JsonSerializer`.
172264
*/
173265
class JsonSerializerSettingsCreation extends ObjectCreation {
174266
JsonSerializerSettingsCreation() {
175267
this.getTarget()
176-
.hasFullyQualifiedName("Newtonsoft.Json.JsonSerializerSettings", "JsonSerializerSettings") or
268+
.hasFullyQualifiedName("Newtonsoft.Json.JsonSerializerSettings",
269+
"JsonSerializerSettings")
270+
or
177271
this.getTarget()
178-
.hasFullyQualifiedName("Newtonsoft.Json.JsonSerializer", "JsonSerializer")
272+
.hasFullyQualifiedName("Newtonsoft.Json.JsonSerializer", "JsonSerializer")
179273
}
180-
Class getAssignedBinderType(){
181-
exists(AssignExpr ae |
274+
275+
/** Gets the type of the binder assigned to this settings object. */
276+
Class getAssignedBinderType() {
277+
exists(AssignExpr ae |
182278
ae.getLValue() = this.getBinderPropertyWrite() and
183-
ae.getRValue().getType() = result
279+
ae.getRValue().getType() = result
184280
)
185281
}
186282

187-
BinderPropertyWrite getBinderPropertyWrite() {
188-
result = this.getPropertyWrite()
189-
}
283+
/** Gets a `BinderPropertyWrite` associated with this settings object. */
284+
BinderPropertyWrite getBinderPropertyWrite() { result = this.getPropertyWrite() }
190285

286+
/** Gets a `TypeNameHandlingPropertyWrite` associated with this settings object. */
191287
TypeNameHandlingPropertyWrite getTypeNameHandlingPropertyWrite() {
192288
result = this.getPropertyWrite()
193289
}
194290

195-
PropertyWrite getPropertyWrite(){
291+
/**
292+
* Gets a `PropertyWrite` associated with this settings object, via an
293+
* initializer, direct local flow, or an assignment to the same property.
294+
*/
295+
PropertyWrite getPropertyWrite() {
196296
result = this.getInitializer().getAChild*()
197297
or
198298
// Direct local flow via some intermediary
199299
DataFlow::localExprFlow(this, result.getQualifier())
200300
or
201301
// Local flow via property writes
202-
hasPropertyWrite(result)
302+
this.hasPropertyWrite(result)
203303
}
204304

205305
/**
206-
* The qualifier to `pw` is a property, which this value flows into somewhere locally.
207-
* Janky local DF.
208-
* */
306+
* Holds if `pw` is a property write on the same target that this object
307+
* creation is assigned to, within the same callable.
308+
*/
209309
bindingset[pw]
210310
pragma[inline_late]
211-
predicate hasPropertyWrite(PropertyWrite pw){
311+
predicate hasPropertyWrite(PropertyWrite pw) {
212312
exists(Assignment a |
213-
a.getRValue() = this
214-
and a.getLValue().(PropertyAccess).getTarget() = pw.getQualifier().(PropertyAccess).getTarget()
215-
and a.getEnclosingCallable() = pw.getEnclosingCallable()
313+
a.getRValue() = this and
314+
a.getLValue().(PropertyAccess).getTarget() =
315+
pw.getQualifier().(PropertyAccess).getTarget() and
316+
a.getEnclosingCallable() = pw.getEnclosingCallable()
216317
)
217318
}
218319
}

0 commit comments

Comments
 (0)