androidandroid-security

App rejected due to intent scheme vulnerability – which part of my code is problematic?


This is an app that uses WebView. Currently, my priority has been to get the upload done, so I haven’t organized the code yet. I’ve attempted to upload the app several times to Google Play Console for internal testing, but it keeps getting rejected due to an "intent scheme hijacking" issue.

Google only mentions that the problem lies in shouldOverrideUrlLoading, but they don’t specify exactly what the issue is. I can only find out whether the problem is resolved by submitting the app again for review.

The following code still results in the update being rejected. Which part is causing the issue?

androidManifest.xml

android:usesCleartextTraffic="false"

The code causing the issue

class CustomWebViewClientV2(val mContext:Context, val mProgress: ProgressBar, val Callback: WebCallback) : WebViewClient() {
    val TAG = "CustomWebViewClientV2"

    override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
        Log.e(TAG, "shouldOverrideUrlLoading11 $url")
        return handleLoading_new(url)
    }

    override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
        Log.e(TAG, "shouldOverrideUrlLoading22")
        val url = request?.url?.toString() ?: return false
        return handleLoading_new(url)
    }

    private fun handleLoading_new(url: String): Boolean {

        if (isIntent(url)) {

            Log.e(TAG, "intent $url")
            try {

                val intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME).apply {
                    addCategory(Intent.CATEGORY_BROWSABLE)
                    component = null
                    selector = null
                }

                if (url.contains("kakaolink")) {

                    if (intent.resolveActivity(mContext.packageManager) != null) {
                        mContext.startActivity(intent)
                    } else {
                        val marketIntent = Intent(
                            Intent.ACTION_VIEW,
                            Uri.parse("market://details?id=com.kakao.talk")
                        ).apply {
                            addCategory(Intent.CATEGORY_BROWSABLE)
                            component = null
                            selector = null
                        }
                        mContext.startActivity(marketIntent)
                    }
                    return true

                } else {

                    if (intent.resolveActivity(mContext.packageManager) != null) {
                        mContext.startActivity(intent)
                        return true
                    } else {
                        return false
                    }

                }
            } catch (e: Exception) {
                e.printStackTrace()
                return true
            }

        } else if (!url.startsWith("http://") && !url.startsWith("https://")) {
            Log.e(TAG, "https No : $url")

            try {
                Intent.parseUri(url, 0).apply {
                    addCategory(Intent.CATEGORY_BROWSABLE)
                    component = null
                    selector = null
                }.run {
                    mContext.startActivity(this)
                    return true
                }

            } catch (e: Exception) {
                return false
            }

        } else {
            Log.e(TAG, "NO : $url")

            when {
                url.contains("TESTURL") || url.contains("TESTURL3")
                        || url.contains("accounts.google") || url.contains("x.com") || url.contains("twitter.com") -> {

                    if ((url.contains("x.com") || url.contains("twitter.com")) && url.contains("TESTURL")) {
                        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
                            addCategory(Intent.CATEGORY_BROWSABLE)
                            `package` = "com.twitter.android"
                        }

                        if (intent.resolveActivity(mContext.packageManager) != null) {
                            mContext.startActivity(intent)
                        } else {
                            val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
                                addCategory(Intent.CATEGORY_BROWSABLE)
                                component = null
                                selector = null
                            }
                            mContext.startActivity(browserIntent)
                        }
                    }

                    if (url.contains("TESTURL")) {
                        try {
                            Intent.parseUri(url, 0).apply {
                                addCategory(Intent.CATEGORY_BROWSABLE)
                                component = null
                                selector = null
                            }.run {
                                mContext.startActivity(this)
                                return true
                            }

                        } catch (e: Exception) {
                            return false
                        }

                    } else {
                        return false
                    }
                }

                url.contains("play.google.com/store") -> {
                    try {
                        Intent.parseUri(url, 0).apply {
                            setPackage("com.android.vending")
                            component = null
                            selector = null
                        }.run {
                            mContext.startActivity(this)
                            return true
                        }

                    } catch (e: Exception) {
                        return false
                    }
                }

                url.contains("TESTURL") || url.contains("link.coupang.com") -> {
                    try {
                        Intent.parseUri(url, 0).apply {
                            addCategory(Intent.CATEGORY_BROWSABLE)
                            component = null
                            selector = null
                        }.run {
                            mContext.startActivity(this)
                            return true
                        }

                    } catch (e: Exception) {
                        return false
                    }
                }

                else -> {
                    return false
                }
            }
        }
    }

    private fun isIntent(url: String): Boolean{
        return url.startsWith("intent://")
    }

    override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
        super.onPageStarted(view, url, favicon)
        if(url == null) {
            Log.e(TAG, "onPageStarted url is null")
            return
        }

        Log.e(TAG, "onPageStarted :: $url")
        (mContext as Activity).runOnUiThread {
            Callback.Url(false, url)
            mProgress.visibility = View.VISIBLE
        }
    }

    override fun onPageFinished(view: WebView?, url: String?) {
        super.onPageFinished(view, url)

        if(url == null){
            Log.e(TAG,"onPageFinished :: url is null")
            return
        }

        Log.e(TAG,"onPageFinished :: $url")

        if(url.startsWith("market://") || url.startsWith("coupang://")){
            try{
               Intent.parseUri(url, 0).apply {
                    addCategory(Intent.CATEGORY_BROWSABLE)
                    component = null
                    selector = null
                }.run {
                    mContext.startActivity(this)
                }
            }catch (e: Exception){
                e.printStackTrace()
                Log.e(TAG, "onPageFinished Exception :: $e")
            }
        }else {
            Thread {
                try {
                    CookieManager.getInstance().flush()
                }catch (e: Exception){
                    Log.e(TAG,"CookieManager flush error :: ${e.message}")
                }
            }.start()

            (mContext as Activity).runOnUiThread{
                Callback.Url(true, url)
                mProgress.visibility = View.GONE
            }

        }
    }

}

Solution

  • I resolved the issue. In the end, I replaced all uses of Intent.parseUri with alternative code.
    Unfortunately, the following solutions suggested by Google were not helpful:
    addCategory(Intent.CATEGORY_BROWSABLE)
    component = null
    selector = null