본문 바로가기

Android

[Google play] Android, Cross app scripting 으로 인해 Reject 된 경우

한줄요약: 웹뷰에 UrlQuerySanitizer.getUrlAndSpaceLegal().sanitize(url) 를 쓰세요

fun WebView.loadFinalUrl(url: String) {
    val finalUrl: String = UrlQuerySanitizer.getUrlAndSpaceLegal().sanitize(url)
    loadUrl(finalUrl)
}

 

 

구글덕분에 Android 로 밥먹고 살고 있지만, 구글플레이의 모호한 정책은 가끔 참 싫어집니다.

명확하게 원인을 알려주지 않고, 거대한 문서링크만 하나 던져주면서 정책위반을 했으니 수정하라는 식으로 Reject 되는 경우가 있기 때문입니다.

 

요즘은 그래도 코드 상에서 어떤 라인이 정책을 위반했는지 알려주는 경우가 있기 때문에 전보다는 좀 나아지고 있지만,

아예 정책을 위반하면 컴파일 되지 않도록 Lint 등을 추가해 주든지, 수정 방법 (Best practice)을 가이드 해주면 훨씬 편할거라는 상상을 해봅니다.

 

제 경우에는 5번 이상 이 이유로 Reject 을 당해봤는데요, 

 

1. 2021년 9월 1일 경: Cross app scripting 으로 Reject

대응결과: 🟢 성공

해결방법: 문제가 되는 특정 Activity 이름을 구글에서 알려주었고, 해당 Activity 에 android:exported="false" 를 추가.

참고로 TargetSDK를 31 이상으로 설정하는 경우에는 이 값의 default 가 false 이기 때문에 ​신경쓸 필요가 없을 것으로 보입니다. 제 앱은 30을 target으로 설정하고 있습니다.

 

 

2. 2021년 9월 25일 경: Cross app scripting 으로 Reject

대응결과: 🟢 성공

해결방법: 모든 액티비티에 "android:exported-false"를 추가. 이게 맞나 싶어서 일단 모든 액티비티에 추가해 보았고, 통과 되었습니다.

 

3. 2021년 10월 8일 경: Cross app scripting 으로 Reject

대응결과: 🟢 성공

해결방법: 도무지 이해할 수 없는 현상이라 열심히 구글링 했고, Cross-App Scripting Reject 해결방법 이라는 글을 찾아서 "webview 에 입력되는 스트링을 final으로 만들어" 해결했습니다. 즉 이전에는 이렇게 되어 있었다면

loadUrl("www.google.com")

아래와 같이 수정했습니다.

val finalUrl: String = "www.google.com"
loadUrl(finalUrl)

정말 구글 검수로직이 체크하는 부분은 loadUrl으로 입력되는 스트링이 final인지 여부만이었던걸까요? 아무튼 통과되었습니다.

 

4. 2022년 2월 16일 경: Cross app scripting 으로 Reject

대응결과: 🔴 실패

해결방법: 직전에 Reject 되었던 사유가 WebView였기 때문에, 이번에도 아마 웹뷰 문제가 아닐까 추측했습니다. 그리고 WebView에 Extension function을 하나 만들어, 이 함수를 통해서만 url을 loading 하도록 수정했습니다. 

fun WebView.loadFinalUrl(url: String) {
    val finalUrl: String = url
    loadUrl(finalUrl)
}

물론 말도 안되는 로직입니다. 이제 무슨 의미가 있을까요? 하지만 제 가설은 "검수 로직이 final 스트링이 입력되는지만 체크한다" 였기 때문에, 검수로직을 통과한다는 목표를 충족시키기 위한 tricky 코드였습니다. 

 

이 시점에서는 솔직히 구글이 그냥 검사를 하는게 아니고, "개발자가 아무거나 수정해서 다시 배포하기만 해도 통과시켜주는게 아닌가" 라는 의심까지 들었습니다. 이전의 대응도 전혀 논리적인 코드가 아니었지만 잘 통과되었기 때문입니다. 하지만 결과는? 실패였습니다.

 

5. 2022년 2월 17일 경: Cross app scripting 으로 Reject

대응결과: 🟢 성공

문서를 좀 더 차분하고 천천히 읽어보기로 했습니다. 그리고 문서에서 unsanitised 라는 키워드를 유난히 강조하는 것을 발견했습니다.

 

https://support.google.com/faqs/answer/9084685?hl=en-GB

Protect calls to evaluateJavascript and loadUrl_Ensure that parameters to evaluateJavascript are always trusted. Calling evaluateJavascript using unsanitised input from untrusted intents lets attackers execute harmful scripts in the affected WebView. Similarly, calling loadUrl with unsanitised input containing javascript: scheme URLs lets attackers execute harmful scripts.

 

그래서 다시 열심히 검색한 결과, UrlQuerySanitizer라는 유틸리티가 제공되는 걸 발견했습니다. 그 중에서 url 을 위한 메소드가 있었고, 그것을 이용하니 통과되었습니다.

fun WebView.loadFinalUrl(url: String) {
    val finalUrl: String = UrlQuerySanitizer.getUrlAndSpaceLegal().sanitize(url)
    loadUrl(finalUrl)
}

 

결론: 정황상, 맞게 수정한 것 같습니다. unsanitised 가 문제된다는 경고에 대해 sanitize 라는 메소드를 써서 대응했으니까요. 하지만 이게 맞다고 100% 말할 수는 없습니다.

왜냐하면

- 구글 내부 검수 로직을 제가 아는 것도 아니고, 

- 구글 내부 검수 로직이 업데이트 될 수도 있고,

- 이 코드가 원인을 수정한 것이 아닌, 검수 로직을 우연히 피해나가도록 만들었을수도 있기 때문입니다.

 

하지만 언제나 그랬듯, 문제가 생기면 우리는 답을 찾을 겁니다.

 

 

 

 

참고:

Cross-App Scripting Reject 해결방법

앱 배포 시 업데이트가 거부(rejected) 당한 경우(feat.Cross-app Scripting)