'Android Your app(s) are vulnerable to Intent Redirection

I got an email from google play support saying "Intent Redirection Your app(s) are vulnerable to Intent Redirection. To address this issue, follow the steps in this Google Help Center article."

After reading through the article, I'm guessing the key is my app should not call startActivity, startService, sendBroadcast, or setResult on untrusted Intents (intents used by external apps to invoke my app for example) without validating or sanitizing these Intents.

However, solution 1 in the article doesn't work in my case because my component needs to receive Intents from other apps.

Solution 2 is not applicable to my case because I don't know in advance which app would invoke my app, so I don't know what would getCallingActivity returns.

Solution 3 seems to be the most promising one, I tried to removeFlags of intents, however, when I resubmit my app, Google Play again alerts this vulnerability. I am about to try checking whether an Intent grants a URI permission using methods like getFlags and submit my app again to see the result. Does anyone know how do Google check this vulnerability anyway, and could someone spot the vulnerability in my source code and suggest a way to resolve it?

The exact message from Google Play is

Intent Redirection

Your app(s) are vulnerable to Intent Redirection.

To address this issue, follow the steps in this Google Help Center article.

com.mydomain.somepackage.a->a

And the following is the simplified source code.

// MainActivity.java

public class MainActivity extends CordovaActivity
{

    SpecialUtil specialUtil;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        specialUtil = new specialUtil(MainActivity.this);
    }

    @Override
    public void onResume() {
        super.onResume();

        specialUtil.verifyServerIfNeeded(MainActivity.this);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == this.specialUtil.CERT_INVALID_POPUP_REQUEST_CODE) {
            // the user clicked the return button in the alert dialog within WhiteScreen activity
            this.specialUtil.declareAsFailure();
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
    }
}

// com/mydomain/somepackage/SpecialUtil.java

public class SpecialUtil {
    private SharedPreferences mSharedPreferences;
    private SharedPreferences.Editor mSharedPreferencesEditor;
    private SharedPreferences.OnSharedPreferenceChangeListener listener;
    private Activity activity;
    private boolean shownCertInvalidPopup = false;
    public final int CERT_INVALID_POPUP_REQUEST_CODE = 1000;

    public SpecialUtil(Activity activity) {
        this.activity = activity;
        this.mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
        this.mSharedPreferencesEditor = mSharedPreferences.edit();
        this.listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                if (key.equals("SOME_RESULT")) {
                    String result = mSharedPreferences.getString("SOME_RESULT", "");

                    if (result.equals("RESULT_OK")) {
                        SpecialUtil.this.declareAsSuccess();

                    } else if (result.equals("RESULT_CANCELED")) {
                        SpecialUtil.this.declareAsFailure();
                    }
                }
            }
        };
        this.mSharedPreferences.registerOnSharedPreferenceChangeListener(listener);
    }

    public void verifyServerIfNeeded(Activity activity) {
        Intent intent = activity.getIntent();
        
        if (this.isFlowA(intent)) {
            this.removePermission(intent);

            String url = intent.getStringExtra("url");
            this.verifyServer(url);

        } else if (this.isFlowB(intent)) {
            this.removePermission(intent);

            String payment_request_object_url = intent.getData().getQueryParameter("pay_req_obj");
            String callback_url = intent.getData().getQueryParameter("callback");

            this.verifyServer(payment_request_object_url);
        }
    }

    public boolean isFlowA(Intent intent) {
        if (intent.getAction().equals("someAction")) {
            return true;
        }
        return false;
    }

    public boolean isFlowB(Intent intent) {
        if (intent.getData() != null) {
            String path = intent.getData().getPath();
            if (path.equals("something")) {
                return true;
            }
        }
        return false;
    }

    public void verifyServer(final String httpsURL) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    boolean isCertValid = SpecialUtil.this.verify(httpsURL);

                    if (isCertValid) {
                        // do somthing
                    } else {
                        // show a white screen with an alert msg
                        SpecialUtil.this.activity.runOnUiThread(new Runnable() {
                            public void run() {
                                if (!shownCertInvalidPopup) {
                                    shownCertInvalidPopup = true;
                                    Intent intent = new Intent(SpecialUtil.this.activity, WhiteScreen.class);
                                    SpecialUtil.this.activity.startActivityForResult(intent, CERT_INVALID_POPUP_REQUEST_CODE);
                                }
                            }
                        });
                    }
                } catch (IOException e) {
                    SpecialUtil.this.declareAsFailure();
                }
            }
        }).start();
    }

    private void declareAsSuccess() {
        this.activity.setResult(Activity.RESULT_OK, SpecialUtil.this.activity.getIntent());
        this.activity.finishAndRemoveTask();
    }

    public void declareAsFailure() {
        this.activity.setResult(Activity.RESULT_CANCELED, this.activity.getIntent());
        this.activity.finishAndRemoveTask();
    }

    private void removePermission(Intent intent) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            intent.removeFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.removeFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        }
    }
}

// com/mydomain/somepackage/WhiteScreen.java

public class WhiteScreen extends Activity {

    SpecialUtil specialUtil;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        specialUtil = new SpecialUtil(WhiteScreen.this);

        String title = "someTitle";

        final AlertDialog.Builder builder = new AlertDialog.Builder(WhiteScreen.this)
                .setTitle(title)
                .setPositiveButton(btn_text, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        // Don't start the process, quit App immediately
                        WhiteScreen.this.setResult(Activity.RESULT_CANCELED, WhiteScreen.this.getIntent());
                        WhiteScreen.this.finishAndRemoveTask();
                    }
                });
        AlertDialog alertDialog = builder.create();
        alertDialog.show();
    }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source