mirror of
https://github.com/github/codeql.git
synced 2026-04-28 10:15:14 +02:00
Add Fragment injection query
This commit is contained in:
14
java/ql/lib/semmle/code/java/frameworks/android/Fragment.qll
Normal file
14
java/ql/lib/semmle/code/java/frameworks/android/Fragment.qll
Normal file
@@ -0,0 +1,14 @@
|
||||
import java
|
||||
|
||||
/** The class `android.app.Fragment` */
|
||||
class Fragment extends Class {
|
||||
Fragment() { this.hasQualifiedName("android.app", "Fragment") }
|
||||
}
|
||||
|
||||
/** The method `instantiate` of the class `android.app.Fragment`. */
|
||||
class FragmentInstantiateMethod extends Method {
|
||||
FragmentInstantiateMethod() {
|
||||
this.getDeclaringType() instanceof Fragment and
|
||||
this.hasName("instantiate")
|
||||
}
|
||||
}
|
||||
60
java/ql/lib/semmle/code/java/security/FragmentInjection.qll
Normal file
60
java/ql/lib/semmle/code/java/security/FragmentInjection.qll
Normal file
@@ -0,0 +1,60 @@
|
||||
import java
|
||||
private import semmle.code.java.dataflow.TaintTracking
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.frameworks.android.Android
|
||||
private import semmle.code.java.frameworks.android.Fragment
|
||||
private import semmle.code.java.Reflection
|
||||
|
||||
/**
|
||||
* A sink for Fragment injection vulnerabilities,
|
||||
* that is, method calls that dynamically add Fragments to Activities.
|
||||
*/
|
||||
abstract class FragmentInjectionSink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A unit class for adding additional taint steps.
|
||||
*
|
||||
* Extend this class to add additional taint steps that should apply to `FragmentInjectionTaintConf`.
|
||||
*/
|
||||
class FragmentInjectionAdditionalTaintStep extends Unit {
|
||||
abstract predicate step(DataFlow::Node n1, DataFlow::Node n2);
|
||||
}
|
||||
|
||||
private class FragmentInjectionSinkModels extends SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
["android.app", "android.support.v4.app", "androidx.fragment.app"] +
|
||||
";FragmentTransaction;true;" +
|
||||
[
|
||||
"add;(Class,Bundle,String);;Argument[0]", "add;(Fragment,String);;Argument[0]",
|
||||
"add;(int,Class,Bundle);;Argument[1]", "add;(int,Fragment);;Argument[1]",
|
||||
"add;(int,Class,Bundle,String);;Argument[1]", "add;(int,Fragment,String);;Argument[1]",
|
||||
"attach;(Fragment);;Argument[0]", "replace;(int,Class,Bundle);;Argument[1]",
|
||||
"replace;(int,Fragment);;Argument[1]", "replace;(int,Class,Bundle,String);;Argument[1]",
|
||||
"replace;(int,Fragment,String);;Argument[1]",
|
||||
] + ";fragment-injection"
|
||||
}
|
||||
}
|
||||
|
||||
private class DefaultFragmentInjectionSink extends FragmentInjectionSink {
|
||||
DefaultFragmentInjectionSink() { sinkNode(this, "fragment-injection") }
|
||||
}
|
||||
|
||||
private class DefaultFragmentInjectionAdditionalTaintStep extends FragmentInjectionAdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
exists(ReflectiveClassIdentifierMethodAccess ma |
|
||||
ma.getArgument(0) = n1.asExpr() and ma = n2.asExpr()
|
||||
)
|
||||
or
|
||||
exists(NewInstance ni |
|
||||
ni.getQualifier() = n1.asExpr() and
|
||||
ni = n2.asExpr()
|
||||
)
|
||||
or
|
||||
exists(MethodAccess ma |
|
||||
ma.getMethod() instanceof FragmentInstantiateMethod and
|
||||
ma.getArgument(1) = n1.asExpr() and
|
||||
ma = n2.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.security.FragmentInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for unsafe user input
|
||||
* that is used to create Android Fragments dinamically.
|
||||
*/
|
||||
class FragmentInjectionTaintConf extends TaintTracking::Configuration {
|
||||
FragmentInjectionTaintConf() { this = "FragmentInjectionTaintConf" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof FragmentInjectionSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
any(FragmentInjectionAdditionalTaintStep c).step(n1, n2)
|
||||
}
|
||||
}
|
||||
60
java/ql/src/Security/CWE/CWE-470/FragmentInjection.inc.qhelp
Normal file
60
java/ql/src/Security/CWE/CWE-470/FragmentInjection.inc.qhelp
Normal file
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Fragments are reusable parts of an Android application's user interface.
|
||||
Even though a Fragment controls its own lifecycle and layout and handles its input events,
|
||||
it cannot exist on its own: it must be hosted either by an Activity or another fragment.
|
||||
This means that a Fragment will be accessible by third-party applications (that is, exported)
|
||||
only if its hosting Activity is itself exported.
|
||||
</p>
|
||||
<p>
|
||||
If an exported Activity dinamically creates and hosts Fragments instantiated with externally
|
||||
provided names, a malicious application could provide the name of an arbitrary Fragment, even
|
||||
one not designed to be externally accessible, and inject it into the Activity, effectively
|
||||
bypassing access controls and exposing the application to unintended effects.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
In general, do not instantiate classes (including Fragments) with user-provided names
|
||||
without proper validation.
|
||||
|
||||
Also, if an exported Activity is extending the <code>PreferenceActivity</code> class, make sure that
|
||||
the <code>isValidFragment</code> method is overriden and only returns <code>true</code> when the provided
|
||||
<code>fragmentName</code> points to an intended Fragment.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example shows two cases: in the first one, untrusted data is used to instantiate and
|
||||
add a Fragment to an Activity, while in the second one, a Fragment is safely added with a static name.
|
||||
</p>
|
||||
<sample src="FragmentInjection.java" />
|
||||
|
||||
<p>
|
||||
The next example shows two Activities extending <code>PreferenceActivity</code>. The first one overrides
|
||||
<code>isValidFragment</code>, but it wrongly returns <code>true</code> inconditionally. The second Activity
|
||||
correctly overrides <code>isValidFragment</code> to only return <code>true</code> when <code>fragmentName</code>
|
||||
is a trusted Fragment name.
|
||||
</p>
|
||||
<sample src="FragmentInjectionInPreferenceActivity.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li> Google Help:
|
||||
<a href="https://support.google.com/faqs/answer/7188427?hl=en">How to fix Fragment Injection vulnerability</a>.
|
||||
</li>
|
||||
<li>
|
||||
IBM Security Systems:
|
||||
<a href="https://securityintelligence.com/wp-content/uploads/2013/12/android-collapses-into-fragments.pdf">Android collapses into Fragments</a>.
|
||||
</li>
|
||||
<li>
|
||||
Android Developers:
|
||||
<a href="https://developer.android.com/guide/fragments">Fragments</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
22
java/ql/src/Security/CWE/CWE-470/FragmentInjection.java
Normal file
22
java/ql/src/Security/CWE/CWE-470/FragmentInjection.java
Normal file
@@ -0,0 +1,22 @@
|
||||
public class MyActivity extends FragmentActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstance) {
|
||||
try {
|
||||
super.onCreate(savedInstance);
|
||||
// BAD: Fragment instantiated from user input
|
||||
{
|
||||
String fName = getIntent().getStringExtra("fragmentName");
|
||||
getFragmentManager().beginTransaction().replace(com.android.internal.R.id.prefs,
|
||||
Fragment.instantiate(this, fName, null)).commit();
|
||||
}
|
||||
// GOOD: Fragment instantiated statically
|
||||
{
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(com.android.internal.R.id.prefs, new MyFragment()).commit();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
4
java/ql/src/Security/CWE/CWE-470/FragmentInjection.qhelp
Normal file
4
java/ql/src/Security/CWE/CWE-470/FragmentInjection.qhelp
Normal file
@@ -0,0 +1,4 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
<include src="FragmentInjection.inc.qhelp"></include>
|
||||
</qhelp>
|
||||
21
java/ql/src/Security/CWE/CWE-470/FragmentInjection.ql
Normal file
21
java/ql/src/Security/CWE/CWE-470/FragmentInjection.ql
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @name Android Fragment injection
|
||||
* @description Instantiating an Android Fragment from a user-provided value
|
||||
* may lead to Fragment injection.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 9.8
|
||||
* @precision high
|
||||
* @id java/android/fragment-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-470
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.security.FragmentInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where any(FragmentInjectionTaintConf conf).hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Fragment injection from $@.", source.getNode(),
|
||||
"this user input"
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0"
|
||||
package="com.example.myapp">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,11 @@
|
||||
import java
|
||||
import semmle.code.java.security.FragmentInjectionQuery
|
||||
import TestUtilities.InlineFlowTest
|
||||
|
||||
class Test extends InlineFlowTest {
|
||||
override DataFlow::Configuration getValueFlowConfig() { none() }
|
||||
|
||||
override TaintTracking::Configuration getTaintFlowConfig() {
|
||||
result instanceof FragmentInjectionTaintConf
|
||||
}
|
||||
}
|
||||
39
java/ql/test/query-tests/security/CWE-470/MainActivity.java
Normal file
39
java/ql/test/query-tests/security/CWE-470/MainActivity.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package com.example.myapp;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
public class MainActivity extends FragmentActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstance) {
|
||||
try {
|
||||
super.onCreate(savedInstance);
|
||||
final String fname = getIntent().getStringExtra("fname");
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
Class<Fragment> fClass = (Class<Fragment>) Class.forName(fname);
|
||||
ft.add(fClass.newInstance(), ""); // $ hasTaintFlow
|
||||
ft.add(0, Fragment.instantiate(this, fname), null); // $ hasTaintFlow
|
||||
ft.add(0, Fragment.instantiate(this, fname, null)); // $ hasTaintFlow
|
||||
ft.add(0, fClass, null, ""); // $ hasTaintFlow
|
||||
ft.add(0, fClass.newInstance(), ""); // $ hasTaintFlow
|
||||
ft.attach(fClass.newInstance()); // $ hasTaintFlow
|
||||
ft.replace(0, fClass, null); // $ hasTaintFlow
|
||||
ft.replace(0, fClass.newInstance()); // $ hasTaintFlow
|
||||
ft.replace(0, fClass, null, ""); // $ hasTaintFlow
|
||||
ft.replace(0, fClass.newInstance(), ""); // $ hasTaintFlow
|
||||
|
||||
ft.add(Fragment.class.newInstance(), ""); // Safe
|
||||
ft.attach(Fragment.class.newInstance()); // Safe
|
||||
ft.replace(0, Fragment.class.newInstance(), ""); // Safe
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
java/ql/test/query-tests/security/CWE-470/options
Normal file
1
java/ql/test/query-tests/security/CWE-470/options
Normal file
@@ -0,0 +1 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/google-android-9.0.0
|
||||
Reference in New Issue
Block a user