'Your app(s) are using a WebView that is vulnerable to cross-app scripting

My android app keeps getting rejected because of this reason: Your app(s) are using a WebView that is vulnerable to cross-app scripting.

I already did an extensive search and found some thing I could do:

  1. Follow the steps on https://support.google.com/faqs/answer/9084685. Since I'm using a launcher, I have to follow option 2.

  2. I added this code in my manifest.xml: android.webkit.WebView.EnableSafeBrowsing

  3. I'm sure the problem lies in the code posted below, where I don't use a fixed url for my loadURL, but I let it change via intent, being a notificiation url or a mail link. I'm aware this gives serious security issues, but I don't know how to fix it. In the link I provided above I want to follow option 2 but:

  • I can't disable javascript (my webpage won't load without it)
  • I don't know how to validate/secure the url in loadURL in such a way that Google allows me to upload my app to the Play Store.

Can someone please help me?

@Override
protected void onCreate(Bundle savedInstanceState) {

    LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
            new IntentFilter("send-url"));

    super.onCreate(savedInstanceState);
    setRequestedOrientation (ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    setContentView(R.layout.activity_main);

    String default_url = "https://www.playday.be/app/";

    FirebaseMessaging.getInstance().getToken()
            .addOnCompleteListener(task -> {
                if (task.isSuccessful() && task.getResult() != null) {
                    String refreshedToken = task.getResult();
                    Log.v("newToken",refreshedToken);
                    sendRegistrationToServer(refreshedToken);
                }
            });

    if(getIntent().getExtras() != null){
        if (getIntent().getExtras().getString("pushUrl") != null) {
            String url_from_notif = getIntent().getExtras().getString("pushUrl");
            if(Patterns.WEB_URL.matcher(url_from_notif).matches()) {
                default_url = url_from_notif;
                Log.v("url_from_notif = ", default_url);
            }
        }
        if (getIntent().getData() != null) {
            String url_from_mail = getIntent().getData().toString();
            url_from_mail = url_from_mail.replace("playday://", "");
            if(Patterns.WEB_URL.matcher(url_from_mail).matches()) {
                default_url = url_from_mail;
                Log.v("url_from_mail = ", default_url);
            }
        }
    }

    Log.v("default_url_new = ", "" +default_url);

    webView = findViewById(R.id.ifView);
    assert webView != null;
    WebSettings webSettings = webView.getSettings();
    webSettings.setJavaScriptEnabled(true);
    webSettings.setAllowFileAccess(true);
    webSettings.setGeolocationEnabled(false);

    webView.setWebViewClient(new Callback());

    webView.setWebViewClient(new MyAppWebViewClient(){

        public void onReceivedError(WebView webView, int errorCode, String description, String failingUrl) {
            try {
                webView.stopLoading();
            } catch (Exception e) {
                Log.v("method:", "onReceivedError");
            }

            if (webView.canGoBack()) {
                webView.goBack();
            }

            webView.loadUrl("about:blank");
            AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this).create();
            alertDialog.setTitle("Je bent niet online...");
            alertDialog.setIcon(R.mipmap.ic_launcher);
            alertDialog.setMessage("Gelieve je internetconnectie te herstellen en probeer dan opnieuw.");
            alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Probeer opnieuw", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    finish();
                    startActivity(getIntent());
                }
            });

            try {
                alertDialog.show();
            } catch(Exception e){
                Log.e(TAG,"show dialog error no internet connection");
            }

            super.onReceivedError(webView, errorCode, description, failingUrl);
        }


    });

    webView.loadUrl(default_url);

    webView.setWebChromeClient(new WebChromeClient() {


        public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> filePath, WebChromeClient.FileChooserParams fileChooserParams) {
            Log.d(TAG,"111 ShowFileChooser For Android 5.0 ");
            if (Build.VERSION.SDK_INT >= 23) {
                if (mUMA != null) {
                    mUMA.onReceiveValue(null);
                }
                mUMA = filePath;
                Log.d(TAG,"ShowFileChooser For Android 5.0 SDK_INT>=23 chk permission");
                String[] PERMISSIONS = {android.Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.CAMERA};
                if (!hasPermissions(mContext, PERMISSIONS)) {
                    ActivityCompat.requestPermissions((Activity) mContext, PERMISSIONS, REQUEST_CAMERA);
                } else {
                    Log.d(TAG,"112 ShowFileChooser For Android 5.0 in IF  SDK_INT>=23 permission grant");
                    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                        // Create the File where the photo should go
                        File photoFile = null;
                        try {
                            photoFile = createImageFile();
                            takePictureIntent.putExtra("PhotoPath", mCM);
                        } catch (Exception ex) {
                            // Error occurred while creating the File
                            Log.e(TAG, "Unable to create Image File 1", ex);
                        }
                        // Continue only if the File was successfully created
                        if (photoFile != null) {
                            mCM = "file:" + photoFile.getAbsolutePath();
                            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                                    Uri.fromFile(photoFile));
                        } else {
                            takePictureIntent = null;
                        }
                    }
                    Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
                    contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
                    contentSelectionIntent.setType("image/*");
                    Intent[] intentArray;
                    if (takePictureIntent != null) {
                        intentArray = new Intent[]{takePictureIntent};
                    } else {
                        intentArray = new Intent[0];
                    }
                    Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
                    chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
                    chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
                    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
                    startActivityForResult(chooserIntent, FCR);
                }
            } else {
                Log.d(TAG,"113 ShowFileChooser For Android 5.0 in else SDK_INT>=23");
                if (mUMA != null) {
                    mUMA.onReceiveValue(null);
                }
                mUMA = filePath;
                Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                    // Create the File where the photo should go
                    File photoFile = null;
                    try {
                        photoFile = createImageFile();
                        takePictureIntent.putExtra("PhotoPath", mCM);
                    } catch (Exception ex) {
                        // Error occurred while creating the File
                        Log.e(TAG, "Unable to create Image File 2", ex);
                    }
                    // Continue only if the File was successfully created
                    if (photoFile != null) {
                        mCM = "file:" + photoFile.getAbsolutePath();
                        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                                Uri.fromFile(photoFile));
                    } else {
                        takePictureIntent = null;
                    }
                }
                Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
                contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
                contentSelectionIntent.setType("image/*");
                Intent[] intentArray;
                if (takePictureIntent != null) {
                    intentArray = new Intent[]{takePictureIntent};
                } else {
                    intentArray = new Intent[0];
                }
                Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
                chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
                chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
                chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
                startActivityForResult(chooserIntent, FCR);
            }
            // Double check that we don't have any existing callbacks
            return true;
        }

    });

    NotificationManager notificationManager =
            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    String channelId = "1";
    String channel2 = "2";

    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
        NotificationChannel notificationChannel = new NotificationChannel(channelId,
                "Channel 1", NotificationManager.IMPORTANCE_HIGH);

        notificationChannel.setDescription("This is BNT");
        notificationChannel.setLightColor(Color.RED);
        notificationChannel.enableVibration(true);
        notificationChannel.setShowBadge(true);
        assert notificationManager != null; //edit 1402
        notificationManager.createNotificationChannel(notificationChannel);

        NotificationChannel notificationChannel2 = new NotificationChannel(channel2,
                "Channel 2", NotificationManager.IMPORTANCE_MIN);

        notificationChannel.setDescription("This is bTV");
        notificationChannel.setLightColor(Color.RED);
        notificationChannel.enableVibration(true);
        notificationChannel.setShowBadge(true);
        notificationManager.createNotificationChannel(notificationChannel2);
    }



}


Solution 1:[1]

I finally figured it out myself and got my app approved in the app store. I just needed to validate/sanitize the url that was loaded in the webview.

I changed the code:

if(getIntent().getExtras() != null){
    if (getIntent().getExtras().getString("pushUrl") != null) {
        String url_from_notif = getIntent().getExtras().getString("pushUrl");
        if(Patterns.WEB_URL.matcher(url_from_notif).matches()) {
            default_url = url_from_notif;
            Log.v("url_from_notif = ", default_url);
        }
    }
    if (getIntent().getData() != null) {
        String url_from_mail = getIntent().getData().toString();
        url_from_mail = url_from_mail.replace("playday://", "");
        if(Patterns.WEB_URL.matcher(url_from_mail).matches()) {
            default_url = url_from_mail;
            Log.v("url_from_mail = ", default_url);
        }
    }
}

To:

if(getIntent().getExtras() != null){
        String url_from_notif = getIntent().getExtras().getString("pushUrl");
        if (url_from_notif != null) {
            if(URLUtil.isValidUrl(url_from_notif) && Patterns.WEB_URL.matcher(url_from_notif).matches()) {
                default_url = url_from_notif;
                Log.v("url_from_notif = ", default_url);
            }
        }
        if (getIntent().getData() != null) {
            String url_from_mail = getIntent().getData().toString();
            url_from_mail = url_from_mail.replace("playday://", "");
            if(URLUtil.isValidUrl(url_from_mail) && Patterns.WEB_URL.matcher(url_from_mail).matches()) {
                default_url = url_from_mail;
                Log.v("url_from_mail = ", default_url);
            }
        }
    }

And:

webView.loadUrl(default_url);

To:

URI uri;
    try {
        uri = new URI(default_url);
        String domain = uri.getHost();
        if (!default_url.startsWith("https://www.playday.be") || !domain.equals("www.playday.be") || !URLUtil.isValidUrl(default_url) || !Patterns.WEB_URL.matcher(default_url).matches()) {
            webView.loadUrl("about:blank");
        } else {
            webView.loadUrl(default_url);
        }
    } catch (URISyntaxException e) {
        e.printStackTrace();
    }

Sources

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

Source: Stack Overflow

Solution Source
Solution 1 Simon Lenaerts