Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7f7a92d

Browse files
committed
C#: Model Json.NET dataflow.
1 parent 937049e commit 7f7a92d

6 files changed

Lines changed: 417 additions & 0 deletions

File tree

csharp/ql/src/semmle/code/csharp/dataflow/TaintTracking.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module TaintTracking {
99
private import semmle.code.csharp.dataflow.LibraryTypeDataFlow
1010
private import semmle.code.csharp.dispatch.Dispatch
1111
private import semmle.code.csharp.commons.ComparisonTest
12+
private import semmle.code.csharp.frameworks.JsonNET
1213
private import cil
1314
private import dotnet
1415

@@ -18,6 +19,9 @@ module TaintTracking {
1819
*/
1920
predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) }
2021

22+
/** A member (property or field) that is tainted if its containing object is tainted. */
23+
abstract class TaintedMember extends AssignableMember { }
24+
2125
/**
2226
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
2327
* (intra-procedural) step.
@@ -243,6 +247,16 @@ module TaintTracking {
243247
DataFlow::Internal::flowOutOfDelegateLibraryCall(nodeFrom, nodeTo, false)
244248
or
245249
localTaintStepCil(nodeFrom, nodeTo)
250+
or
251+
// Taint members
252+
exists(Access access |
253+
access = nodeTo.asExpr() and
254+
access.getTarget() instanceof TaintedMember
255+
|
256+
access.(FieldRead).getQualifier() = nodeFrom.asExpr()
257+
or
258+
access.(PropertyRead).getQualifier() = nodeFrom.asExpr()
259+
)
246260
}
247261
}
248262
import Cached
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/**
2+
* Classes for modelling Json.NET.
3+
*/
4+
5+
import csharp
6+
import semmle.code.csharp.dataflow.LibraryTypeDataFlow
7+
8+
/** Definitions relating to the `Json.NET` package. */
9+
module JsonNET {
10+
/** The namespace `Newtonsoft.Json`. */
11+
class JsonNETNamespace extends Namespace {
12+
JsonNETNamespace() { this.hasQualifiedName("Newtonsoft.Json") }
13+
}
14+
15+
/** A class in `Newtonsoft.Json`. */
16+
class JsonClass extends Class { JsonClass() { this.getParent() instanceof JsonNETNamespace } }
17+
18+
/** The class `Newtonsoft.Json.JsonConvert`. */
19+
class JsonConvertClass extends JsonClass, LibraryTypeDataFlow {
20+
JsonConvertClass() { this.hasName("JsonConvert") }
21+
22+
/** Gets a `ToString` method. */
23+
private Method getAToStringMethod() {
24+
result = this.getAMethod("ToString") and
25+
result.isStatic()
26+
}
27+
28+
/** Gets a `Deserialize` method. */
29+
Method getADeserializeMethod() {
30+
result = this.getAMethod() and
31+
result.getName().matches("Deserialize%")
32+
}
33+
34+
/** Gets a `Serialize` method. */
35+
Method getASerializeMethod() {
36+
result = this.getAMethod() and
37+
result.getName().matches("Serialize%")
38+
}
39+
40+
private Method getAPopulateMethod() {
41+
result = this.getAMethod() and
42+
result.getName().matches("Populate%")
43+
}
44+
45+
override predicate callableFlow(
46+
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
47+
boolean preservesValue
48+
) {
49+
// ToString methods
50+
c = getAToStringMethod() and
51+
preservesValue = true and
52+
source = any(CallableFlowSourceArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 0) and
53+
sink instanceof CallableFlowSinkReturn
54+
or
55+
// Deserialize methods
56+
c = getADeserializeMethod() and
57+
preservesValue = false and
58+
source = any(CallableFlowSourceArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 0) and
59+
sink instanceof CallableFlowSinkReturn
60+
or
61+
// Serialize methods
62+
c = getASerializeMethod() and
63+
preservesValue = false and
64+
source = any(CallableFlowSourceArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 0) and
65+
sink instanceof CallableFlowSinkReturn
66+
or
67+
// Populate methods
68+
c = getAPopulateMethod() and
69+
preservesValue = false and
70+
source = any(CallableFlowSourceArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 0) and
71+
sink = any(CallableFlowSinkArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 1)
72+
}
73+
}
74+
75+
/** A type that is serialized. */
76+
private class SerializedType extends ValueOrRefType {
77+
SerializedType() {
78+
// Supplied as a parameter to a serialize or deserialize method
79+
exists(TypeParameter tp, JsonConvertClass jc, UnboundGenericMethod serializeMethod |
80+
this = tp.getAnUltimatelySuppliedType() and
81+
tp = serializeMethod.getATypeParameter()
82+
|
83+
serializeMethod = jc.getASerializeMethod()
84+
or
85+
serializeMethod = jc.getADeserializeMethod()
86+
)
87+
or
88+
exists(Class attribute | attribute = this.getAnAttribute().getType() |
89+
attribute instanceof JsonObjectAttributeClass
90+
or
91+
attribute.hasName("JsonConverterAttribute")
92+
or
93+
attribute.hasQualifiedName("System.DataContractAttribute")
94+
)
95+
or
96+
this.getAConstructor().getAnAttribute().getType().hasName("JsonConstructorAttribute")
97+
}
98+
99+
predicate isOptIn() { this.getAnAttribute().(JsonObjectAttribute).isOptIn() }
100+
}
101+
102+
/**
103+
* A field/property that can be serialized, either explicitly
104+
* or as a member of a serialized type.
105+
*/
106+
private class SerializedMember extends AssignableMember, TaintTracking::TaintedMember {
107+
SerializedMember() {
108+
// This member has a Json attribute
109+
exists(Class attribute | attribute = this.(Attributable).getAnAttribute().getType() |
110+
attribute.hasName("JsonPropertyAttribute")
111+
or
112+
attribute.hasName("JsonDictionaryAttribute")
113+
or
114+
attribute.hasName("JsonRequiredAttribute")
115+
or
116+
attribute.hasName("JsonArrayAttribute")
117+
or
118+
attribute.hasName("JsonConverterAttribute")
119+
or
120+
attribute.hasName("JsonExtensionDataAttribute")
121+
or
122+
attribute.hasName("SerializableAttribute") // System.SerializableAttribute
123+
or
124+
attribute.hasName("DataMemberAttribute") // System.DataMemberAttribute
125+
)
126+
or
127+
// This field is a member of an explicitly serialized type
128+
this.getDeclaringType() instanceof SerializedType and
129+
not this.getDeclaringType().(SerializedType).isOptIn() and
130+
not this.(Attributable).getAnAttribute().getType() instanceof NotSerializedAttributeClass
131+
}
132+
}
133+
134+
/** The class `NewtonSoft.Json.JsonSerializer`. */
135+
class JsonSerializerClass extends JsonClass, LibraryTypeDataFlow {
136+
JsonSerializerClass() { this.hasName("JsonSerializer") }
137+
138+
Method getSerializeMethod() { result = this.getAMethod("Serialize") }
139+
140+
Method getDeserializeMethod() { result = this.getAMethod("Deserialize") }
141+
142+
override predicate callableFlow(
143+
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
144+
boolean preservesValue
145+
) {
146+
// Serialize
147+
c = this.getSerializeMethod() and
148+
preservesValue = false and
149+
source = any(CallableFlowSourceArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 0) and
150+
sink = any(CallableFlowSinkArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 1)
151+
or
152+
// Deserialize
153+
c = this.getDeserializeMethod() and
154+
preservesValue = false and
155+
source = any(CallableFlowSourceArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 0) and
156+
sink = any(CallableFlowSinkArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 1)
157+
}
158+
}
159+
160+
/** Any attribute class that marks a member to not be serialized. */
161+
private class NotSerializedAttributeClass extends JsonClass {
162+
NotSerializedAttributeClass() {
163+
this.hasName("JsonIgnoreAttribute") or this.hasName("NonSerializedAttribute")
164+
}
165+
}
166+
167+
/** The class `Newtonsoft.Json.ObjectAttribute`. */
168+
class JsonObjectAttributeClass extends JsonClass {
169+
JsonObjectAttributeClass() { this.hasName("JsonObjectAttribute") }
170+
}
171+
172+
/** An attribute of type `Newtonsoft.Json.ObjectAttribute`. */
173+
class JsonObjectAttribute extends Attribute {
174+
JsonObjectAttribute() { this.getType() instanceof JsonObjectAttributeClass }
175+
176+
/** Holds if the `OptIn` argument has been supplied to this attribute. */
177+
predicate isOptIn() { this.getArgument(_).(FieldAccess).getTarget().hasName("OptIn") }
178+
}
179+
180+
/** The namespace `Newtonsoft.Json.Linq`. */
181+
class LinqNamespace extends Namespace {
182+
LinqNamespace() {
183+
this.getParentNamespace() instanceof JsonNETNamespace and this.hasName("Linq")
184+
}
185+
}
186+
187+
/** A class in `Newtonsoft.Json.Linq`. */
188+
class LinqClass extends Class {
189+
LinqClass() { this.getDeclaringNamespace() instanceof LinqNamespace }
190+
}
191+
192+
/** The `NewtonSoft.Json.Linq.JObject` class. */
193+
class JObjectClass extends LinqClass, LibraryTypeDataFlow {
194+
JObjectClass() { this.hasName("JObject") }
195+
196+
override predicate callableFlow(
197+
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
198+
boolean preservesValue
199+
) {
200+
// ToString method
201+
c = this.getAMethod("ToString") and
202+
source instanceof CallableFlowSourceQualifier and
203+
sink instanceof CallableFlowSinkReturn and
204+
preservesValue = false
205+
or
206+
// Parse method
207+
c = this.getParseMethod() and
208+
source = any(CallableFlowSourceArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 0) and
209+
sink instanceof CallableFlowSinkReturn and
210+
preservesValue = false
211+
or
212+
// operator string
213+
c = any(Operator op |
214+
op.getDeclaringType() = this.getABaseType*() and op.getReturnType() instanceof StringType
215+
) and
216+
source = any(CallableFlowSourceArg arg | arg.getCallable() = c and arg.getArgumentIndex() = 0) and
217+
sink instanceof CallableFlowSinkReturn and
218+
preservesValue = false
219+
or
220+
// SelectToken method
221+
c = this.getSelectTokenMethod() and
222+
source instanceof CallableFlowSourceQualifier and
223+
sink instanceof CallableFlowSinkReturn and
224+
preservesValue = false
225+
}
226+
227+
/** Gets the `Parse` method. */
228+
Method getParseMethod() { result = this.getAMethod("Parse") }
229+
230+
/** Gets the `SelectToken` method. */
231+
Method getSelectTokenMethod() { result = this.getABaseType*().getAMethod("SelectToken") }
232+
}
233+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// semmle-extractor-options: ${testdir}/../../../resources/stubs/JsonNET.cs /r:System.Linq.dll
2+
3+
using Newtonsoft.Json;
4+
using Newtonsoft.Json.Linq;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
8+
namespace JsonTest
9+
{
10+
class Dataflow
11+
{
12+
void Sink(object o)
13+
{
14+
}
15+
16+
void F()
17+
{
18+
string t = "tainted";
19+
string u = "untainted";
20+
21+
Sink(JsonConvert.ToString(int.Parse(t)));
22+
23+
var taintedObject = JsonConvert.DeserializeObject<Object>(t);
24+
Sink(taintedObject);
25+
Sink(taintedObject.tainted);
26+
Sink(taintedObject.untainted);
27+
Sink(JsonConvert.SerializeObject(taintedObject));
28+
Sink(taintedObject.taintedValues["1"]);
29+
Sink(taintedObject.taintedArray[0]);
30+
31+
var taintedObject2 = JsonConvert.DeserializeObject<Object2>(t);
32+
Sink(taintedObject2.tainted);
33+
Sink(taintedObject2.untainted);
34+
35+
Object taintedPopulatedObject = new Object();
36+
JsonConvert.PopulateObject(t, taintedPopulatedObject);
37+
Sink(taintedPopulatedObject.tainted); // False negative
38+
39+
Object untaintedObject = JsonConvert.DeserializeObject<Object>(u);
40+
Sink(untaintedObject);
41+
42+
// JObject tests
43+
var jobject = JObject.Parse(t);
44+
Sink(jobject);
45+
Sink(jobject["1"]);
46+
Sink(jobject["1"]["2"]);
47+
Sink((string)jobject["1"]["2"]);
48+
49+
// Linq JToken tests
50+
Sink(jobject.First(i => true));
51+
Sink(jobject["2"].First(i => true));
52+
Sink(jobject["2"]["3"].First(i => true));
53+
Sink(jobject.SelectToken("Manufacturers[0].Name"));
54+
55+
JObject untaintedJObject = JObject.Parse(u);
56+
Sink(untaintedJObject);
57+
Sink(untaintedJObject.First(i => true));
58+
}
59+
60+
public class Object
61+
{
62+
public int tainted;
63+
64+
[JsonIgnore]
65+
public int untainted;
66+
67+
public Dictionary<string,string> taintedValues;
68+
69+
public string[] taintedArray;
70+
}
71+
72+
[JsonObject(MemberSerialization.OptIn)]
73+
public class Object2
74+
{
75+
public int untainted;
76+
77+
[JsonRequired]
78+
public int tainted;
79+
}
80+
}
81+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:21:18:21:51 | call to method ToString |
2+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:24:18:24:30 | access to local variable taintedObject |
3+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:25:18:25:38 | (...) ... |
4+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:27:18:27:59 | call to method SerializeObject |
5+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:28:18:28:49 | access to indexer |
6+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:29:18:29:46 | access to array element |
7+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:32:18:32:39 | (...) ... |
8+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:44:18:44:24 | access to local variable jobject |
9+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:45:18:45:29 | access to indexer |
10+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:46:18:46:34 | access to indexer |
11+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:47:18:47:42 | call to operator explicit conversion |
12+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:50:18:50:41 | call to method First |
13+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:51:18:51:46 | call to method First |
14+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:52:18:52:51 | call to method First |
15+
| Json.cs:18:24:18:32 | "tainted" | Json.cs:53:18:53:61 | call to method SelectToken |
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import csharp
2+
import semmle.code.csharp.dataflow.TaintTracking
3+
4+
class Configuration extends TaintTracking::Configuration {
5+
Configuration() { this = "Json.NET test" }
6+
7+
override predicate isSource(DataFlow::Node src) {
8+
src.asExpr().(StringLiteral).getValue() = "tainted"
9+
}
10+
11+
override predicate isSink(DataFlow::Node sink) {
12+
exists(MethodCall c | c.getArgument(0) = sink.asExpr() and c.getTarget().getName() = "Sink")
13+
}
14+
}
15+
16+
from Configuration c, DataFlow::Node source, DataFlow::Node sink
17+
where c.hasFlow(source, sink)
18+
select source, sink

0 commit comments

Comments
 (0)