'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 |
|---|
