Add Fragment injection in PreferenceActivity query

This commit is contained in:
Tony Torralba
2021-10-20 12:05:10 +02:00
parent 701d12fb5b
commit 85526d71da
10 changed files with 126 additions and 0 deletions

View File

@@ -5,6 +5,32 @@ private import semmle.code.java.frameworks.android.Android
private import semmle.code.java.frameworks.android.Fragment
private import semmle.code.java.Reflection
/** The method `isValidFragment` of the class `android.preference.PreferenceActivity`. */
class IsValidFragmentMethod extends Method {
IsValidFragmentMethod() {
this.getDeclaringType()
.getASupertype*()
.hasQualifiedName("android.preference", "PreferenceActivity") and
this.hasName("isValidFragment")
}
/**
* Holds if this method makes the Activity it is declared in vulnerable to Fragment injection,
* that is, all code paths in this method return `true` and the Activity is exported.
*/
predicate isUnsafe() {
this.getDeclaringType().(AndroidActivity).isExported() and
forex(ReturnStmt retStmt, BooleanLiteral bool |
retStmt.getEnclosingCallable() = this and
// Using taint tracking to handle logical expressions, like
// fragmentName.equals("safe") || true
TaintTracking::localExprTaint(bool, retStmt.getResult())
|
bool.getBooleanValue() = true
)
}
}
/**
* A sink for Fragment injection vulnerabilities,
* that is, method calls that dynamically add Fragments to Activities.

View File

@@ -0,0 +1,21 @@
class UnsafeActivity extends PreferenceActivity {
@Override
protected boolean isValidFragment(String fragmentName) {
// BAD: any Fragment name can be provided.
return true;
}
}
class SafeActivity extends PreferenceActivity {
@Override
protected boolean isValidFragment(String fragmentName) {
// Good: only trusted Fragment names are allowed.
return SafeFragment1.class.getName().equals(fragmentName)
|| SafeFragment2.class.getName().equals(fragmentName)
|| SafeFragment3.class.getName().equals(fragmentName);
}
}

View File

@@ -0,0 +1,4 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<include src="FragmentInjection.inc.qhelp"></include>
</qhelp>

View File

@@ -0,0 +1,21 @@
/**
* @name Android Fragment injection in PreferenceActivity
* @description An insecure implementation of the isValidFragment method
* of the PreferenceActivity class may lead to Fragment injection.
* @kind problem
* @problem.severity error
* @security-severity 9.8
* @precision high
* @id java/android/fragment-injection-preference-activity
* @tags security
* external/cwe/cwe-470
*/
import java
import semmle.code.java.security.FragmentInjection
from IsValidFragmentMethod m
where m.isUnsafe()
select m,
"The 'isValidFragment' method always returns true. This makes the exported Activity $@ vulnerable to Fragment Injection.",
m.getDeclaringType(), m.getDeclaringType().getName()

View File

@@ -19,5 +19,8 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SafePreferenceActivity" android:exported="true" />
<activity android:name=".UnsafePreferenceActivity" android:exported="true" />
<activity android:name=".UnexportedPreferenceActivity" android:exported="false" />
</application>
</manifest>

View File

@@ -0,0 +1,18 @@
import java
import semmle.code.java.security.FragmentInjection
import TestUtilities.InlineExpectationsTest
class FragmentInjectionInPreferenceActivityTest extends InlineExpectationsTest {
FragmentInjectionInPreferenceActivityTest() { this = "FragmentInjectionInPreferenceActivityTest" }
override string getARelevantTag() { result = "hasPreferenceFragmentInjection" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasPreferenceFragmentInjection" and
exists(IsValidFragmentMethod isValidFragment | isValidFragment.isUnsafe() |
isValidFragment.getLocation() = location and
element = isValidFragment.toString() and
value = ""
)
}
}

View File

@@ -0,0 +1,11 @@
package com.example.myapp;
import android.preference.PreferenceActivity;
public class SafePreferenceActivity extends PreferenceActivity {
@Override
protected boolean isValidFragment(String fragmentName) { // Safe: not all paths return true
return fragmentName.equals("MySafeFragment") || fragmentName.equals("MySafeFragment2");
}
}

View File

@@ -0,0 +1,11 @@
package com.example.myapp;
import android.preference.PreferenceActivity;
public class UnexportedPreferenceActivity extends PreferenceActivity {
@Override
protected boolean isValidFragment(String fragmentName) { // Safe: not exported
return true;
}
}

View File

@@ -0,0 +1,11 @@
package com.example.myapp;
import android.preference.PreferenceActivity;
public class UnsafePreferenceActivity extends PreferenceActivity {
@Override
protected boolean isValidFragment(String fragmentName) { // $ hasPreferenceFragmentInjection
return fragmentName.equals("MySafeClass") || true;
}
}