Skip to content

Commit cd2c9e9

Browse files
committed
Add Gson support to unsafe deserialization query
1 parent 6b4ca31 commit cd2c9e9

File tree

21 files changed

+929
-3
lines changed

21 files changed

+929
-3
lines changed

java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ private import FlowSummary
7777
*/
7878
private module Frameworks {
7979
private import internal.ContainerFlow
80+
private import semmle.code.java.frameworks.android.Android
81+
private import semmle.code.java.frameworks.android.Intent
8082
private import semmle.code.java.frameworks.android.XssSinks
8183
private import semmle.code.java.frameworks.android.Intent
8284
private import semmle.code.java.frameworks.ApacheHttp

java/ql/lib/semmle/code/java/frameworks/android/Android.qll

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java
66
import semmle.code.java.dataflow.ExternalFlow
77
import semmle.code.xml.AndroidManifest
8+
private import semmle.code.java.dataflow.ExternalFlow
89

910
/**
1011
* Gets a transitive superType avoiding magic optimisation
@@ -202,3 +203,59 @@ private class ContentProviderSourceModels extends SourceModelCsv {
202203
]
203204
}
204205
}
206+
207+
/** Interface for classes whose instances can be written to and restored from a Parcel. */
208+
class TypeParcelable extends Interface {
209+
TypeParcelable() { this.hasQualifiedName("android.os", "Parcelable") }
210+
}
211+
212+
/**
213+
* A method that overrides `android.os.Parcelable.Creator.createFromParcel`.
214+
*/
215+
class CreateFromParcelMethod extends Method {
216+
CreateFromParcelMethod() {
217+
this.hasName("createFromParcel") and
218+
this.getEnclosingCallable().getDeclaringType().getASupertype*() instanceof TypeParcelable
219+
}
220+
}
221+
222+
private class TaintPropagationModels extends SummaryModelCsv {
223+
override predicate row(string s) {
224+
// BaseBundle getters
225+
s =
226+
"android.os;BaseBundle;true;get" + ["Boolean", "Double", "Int", "Long", "String"] +
227+
["", "Array"] + ";;;Argument[-1];ReturnValue;taint"
228+
or
229+
// Bundle getters
230+
s =
231+
"android.os;Bundle;true;get" +
232+
[
233+
"Binder", "Bundle", "Byte", "ByteArray", "Char", "CharArray", "CharSequence",
234+
"CharSequenceArray", "CharSequenceArrayList", "Float", "FloatArray", "IntegerArrayList",
235+
"Parcelable", "ParcelableArray", "ParcelableArrayList", "Serializable", "Short",
236+
"ShortArray", "Size", "SizeF", "SparseParcelableArray", "StringArrayList"
237+
] + ";;;Argument[-1];ReturnValue;taint"
238+
or
239+
// Intent readers that return their value
240+
s =
241+
"android.os;Parcel;false;read" +
242+
[
243+
"Array", "ArrayList", "Boolean", "Bundle", "Byte", "Double", "FileDescriptor", "Float",
244+
"HashMap", "Int", "Long", "Parcelable", "ParcelableArray", "PersistableBundle",
245+
"Serializable", "Size", "SizeF", "SparseArray", "SparseBolleanArray", "String",
246+
"StrongBinder", "TypedObject", "Value"
247+
] + ";;;Argument[-1];ReturnValue;taint"
248+
or
249+
// Intent readers that write to an existing object
250+
s =
251+
"android.os;Parcel;false;read" +
252+
[
253+
"BinderArray", "BinderList", "BooleanArray", "ByteArray", "CharArray", "DoubleArray",
254+
"FloatArray", "IntArray", "List", "LongArray", "Map", "ParcelableList", "StringArray",
255+
"StringList", "TypedArray", "TypedList"
256+
] + ";;;Argument[-1];Argument[0];taint"
257+
or
258+
// One Intent method that aliases an argument to a return value
259+
s = "android.os;Parcel;false;readParcelableList;;;Argument[0];ReturnValue;value"
260+
}
261+
}

java/ql/lib/semmle/code/java/frameworks/android/Intent.qll

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,53 @@ private import semmle.code.java.dataflow.DataFlow
33
import semmle.code.java.dataflow.FlowSteps
44
import semmle.code.java.dataflow.ExternalFlow
55

6+
/**
7+
* The class `android.content.Intent`.
8+
*/
69
class TypeIntent extends Class {
710
TypeIntent() { hasQualifiedName("android.content", "Intent") }
811
}
912

13+
/**
14+
* The class `android.app.Activity`.
15+
*/
1016
class TypeActivity extends Class {
1117
TypeActivity() { hasQualifiedName("android.app", "Activity") }
1218
}
1319

20+
/**
21+
* The class `android.content.Context`.
22+
*/
1423
class TypeContext extends RefType {
1524
TypeContext() { hasQualifiedName("android.content", "Context") }
1625
}
1726

27+
/**
28+
* The class `android.content.BroadcastReceiver`.
29+
*/
1830
class TypeBroadcastReceiver extends Class {
1931
TypeBroadcastReceiver() { hasQualifiedName("android.content", "BroadcastReceiver") }
2032
}
2133

34+
/**
35+
* The method `Activity.getIntent`
36+
*/
2237
class AndroidGetIntentMethod extends Method {
2338
AndroidGetIntentMethod() { hasName("getIntent") and getDeclaringType() instanceof TypeActivity }
2439
}
2540

41+
/**
42+
* The method `BroadcastReceiver.onReceive`.
43+
*/
2644
class AndroidReceiveIntentMethod extends Method {
2745
AndroidReceiveIntentMethod() {
2846
hasName("onReceive") and getDeclaringType() instanceof TypeBroadcastReceiver
2947
}
3048
}
3149

50+
/**
51+
* The method `Context.startActivity` or `startActivities`.
52+
*/
3253
class ContextStartActivityMethod extends Method {
3354
ContextStartActivityMethod() {
3455
(hasName("startActivity") or hasName("startActivities")) and
@@ -44,6 +65,16 @@ private class IntentFieldsInheritTaint extends DataFlow::SyntheticFieldContent,
4465
IntentFieldsInheritTaint() { this.getField().matches("android.content.Intent.%") }
4566
}
4667

68+
/**
69+
* The method `Intent.getParcelableExtra`.
70+
*/
71+
class IntentGetParcelableExtraMethod extends Method {
72+
IntentGetParcelableExtraMethod() {
73+
hasName("getParcelableExtra") and
74+
getDeclaringType() instanceof TypeIntent
75+
}
76+
}
77+
4778
private class IntentBundleFlowSteps extends SummaryModelCsv {
4879
override predicate row(string row) {
4980
row =
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Provides classes for working with the Gson framework.
3+
*/
4+
5+
import java
6+
import semmle.code.java.dataflow.DataFlow
7+
import semmle.code.java.frameworks.android.Android
8+
import semmle.code.java.frameworks.android.Intent
9+
10+
/** The class `com.google.gson.Gson`. */
11+
class Gson extends RefType {
12+
Gson() { this.hasQualifiedName("com.google.gson", "Gson") }
13+
}
14+
15+
/** The `fromJson` deserialization method. */
16+
class GsonDeserializeMethod extends Method {
17+
GsonDeserializeMethod() {
18+
this.getDeclaringType() instanceof Gson and
19+
this.hasName("fromJson")
20+
}
21+
}
22+
23+
/**
24+
* Holds if `intentNode` is an `Intent` used in the context `(T)intentNode.getParcelableExtra(...)` and
25+
* `parcelNode` is the corresponding parameter of `Parcelable.Creator<T> { public T createFromParcel(Parcel parcelNode) { }`.
26+
*/
27+
predicate intentFlowsToParcel(DataFlow::Node intentNode, DataFlow::Node parcelNode) {
28+
exists(MethodAccess getParcelableExtraCall, CreateFromParcelMethod cfpm, Type createdType |
29+
intentNode.asExpr() = getParcelableExtraCall.getQualifier() and
30+
getParcelableExtraCall.getMethod() instanceof IntentGetParcelableExtraMethod and
31+
DataFlow::localExprFlow(getParcelableExtraCall, any(Expr e | e.getType() = createdType)) and
32+
parcelNode.asParameter() = cfpm.getParameter(0) and
33+
cfpm.getReturnType() = createdType
34+
)
35+
}

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ private import semmle.code.java.frameworks.Jackson
1717
private import semmle.code.java.frameworks.Jabsorb
1818
private import semmle.code.java.frameworks.JoddJson
1919
private import semmle.code.java.frameworks.Flexjson
20+
private import semmle.code.java.frameworks.google.Gson
2021
private import semmle.code.java.frameworks.apache.Lang
2122
private import semmle.code.java.Reflection
2223

@@ -207,6 +208,10 @@ predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
207208
or
208209
m instanceof FlexjsonDeserializeMethod and
209210
sink = ma.getArgument(0)
211+
or
212+
m instanceof GsonDeserializeMethod and
213+
sink = ma.getArgument(0) and
214+
any(UnsafeTypeConfig config).hasFlowToExpr(ma.getArgument(1))
210215
)
211216
}
212217

@@ -249,6 +254,8 @@ class UnsafeDeserializationConfig extends TaintTracking::Configuration {
249254
createJacksonJsonParserStep(pred, succ)
250255
or
251256
createJacksonTreeNodeStep(pred, succ)
257+
or
258+
intentFlowsToParcel(pred, succ)
252259
}
253260

254261
override predicate isSanitizer(DataFlow::Node node) {
@@ -362,9 +369,15 @@ class UnsafeTypeConfig extends TaintTracking2::Configuration {
362369
ma.getMethod() instanceof JabsorbUnmarshallMethod
363370
or
364371
ma.getMethod() instanceof JoddJsonParseMethod
372+
or
373+
ma.getMethod() instanceof GsonDeserializeMethod
365374
) and
366375
// Note `JacksonTypeDescriptorType` includes plain old `java.lang.Class`
367-
arg.getType() instanceof JacksonTypeDescriptorType and
376+
(
377+
arg.getType() instanceof JacksonTypeDescriptorType
378+
or
379+
arg.getType().(RefType).hasQualifiedName("java.lang.reflect", "Type")
380+
) and
368381
arg = sink.asExpr()
369382
)
370383
}
@@ -375,7 +388,8 @@ class UnsafeTypeConfig extends TaintTracking2::Configuration {
375388
*/
376389
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
377390
resolveClassStep(fromNode, toNode) or
378-
looksLikeResolveClassStep(fromNode, toNode)
391+
looksLikeResolveClassStep(fromNode, toNode) or
392+
intentFlowsToParcel(fromNode, toNode)
379393
}
380394
}
381395

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
package="com.example.app"
3+
android:installLocation="auto"
4+
android:versionCode="1"
5+
android:versionName="0.1" >
6+
7+
<uses-permission android:name="android.permission.INTERNET" />
8+
9+
<application
10+
android:icon="@drawable/ic_launcher"
11+
android:label="@string/app_name"
12+
android:theme="@style/AppTheme" >
13+
<activity
14+
android:name=".GsonActivity"
15+
android:icon="@drawable/ic_launcher"
16+
android:label="@string/app_name">
17+
<intent-filter>
18+
<action android:name="android.intent.action.MAIN" />
19+
<category android:name="android.intent.category.LAUNCHER" />
20+
</intent-filter>
21+
</activity>
22+
</application>
23+
24+
</manifest>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.example.app;
2+
3+
import android.app.Activity;
4+
import android.os.Bundle;
5+
import android.os.Parcel;
6+
import android.os.Parcelable;
7+
8+
import com.google.gson.Gson;
9+
10+
public class GsonActivity extends Activity {
11+
public void onCreate(Bundle savedInstanceState) {
12+
super.onCreate(savedInstanceState);
13+
setContentView(-1);
14+
15+
ParcelableEntity entity = (ParcelableEntity) getIntent().getParcelableExtra("jsonEntity");
16+
}
17+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import java.io.IOException;
2+
3+
import javax.servlet.http.HttpServlet;
4+
import javax.servlet.http.HttpServletRequest;
5+
import javax.servlet.http.HttpServletResponse;
6+
7+
import com.google.gson.Gson;
8+
import com.google.gson.GsonBuilder;
9+
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
10+
11+
import com.example.User;
12+
import com.thirdparty.Person;
13+
14+
public class GsonServlet extends HttpServlet {
15+
16+
private static final long serialVersionUID = 1L;
17+
18+
@Override
19+
// GOOD: concrete class type specified
20+
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
21+
String json = req.getParameter("json");
22+
23+
Gson gson = new Gson();
24+
Object obj = gson.fromJson(json, User.class);
25+
}
26+
27+
@Override
28+
// GOOD: concrete class type specified
29+
public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
30+
String json = req.getParameter("json");
31+
32+
Gson gson = new Gson();
33+
Object obj = gson.fromJson(json, Person.class);
34+
}
35+
36+
@Override
37+
// BAD: allow class name to be controlled by remote source
38+
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
39+
String json = req.getParameter("json");
40+
String clazz = req.getParameter("class");
41+
42+
try {
43+
Gson gson = new Gson();
44+
Object obj = gson.fromJson(json, Class.forName(clazz)); // $unsafeDeserialization
45+
} catch (ClassNotFoundException cne) {
46+
throw new IOException(cne.getMessage());
47+
}
48+
}
49+
50+
@Override
51+
// BAD: allow class name to be controlled by remote source even with a type adapter factory
52+
public void doHead(HttpServletRequest req, HttpServletResponse resp) throws IOException {
53+
String json = req.getParameter("json");
54+
String clazz = req.getParameter("class");
55+
56+
try {
57+
RuntimeTypeAdapterFactory<User> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
58+
.of(User.class, "type");
59+
Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create();
60+
Object obj = gson.fromJson(json, Class.forName(clazz)); // $unsafeDeserialization
61+
} catch (ClassNotFoundException cne) {
62+
throw new IOException(cne.getMessage());
63+
}
64+
}
65+
66+
@Override
67+
// GOOD: specify allowed class types without explicitly configured vulnerable subclass types
68+
public void doTrace(HttpServletRequest req, HttpServletResponse resp) throws IOException {
69+
String json = req.getParameter("json");
70+
String clazz = req.getParameter("class");
71+
72+
RuntimeTypeAdapterFactory<Person> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
73+
.of(Person.class, "type");
74+
Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create();
75+
Person obj = gson.fromJson(json, Person.class);
76+
}
77+
}

0 commit comments

Comments
 (0)