Мобильные
Безопасность WebView: JavaScript-мосты, доступ к файлам и очистка при выходе
WebView — браузер внутри приложения, но без защит браузера. Как правильно настроить JavaScript-мосты, доступ к файлам, смешанный контент и allowlist URL.
· 10 мин. чтения

Во многих мобильных приложениях есть хотя бы один WebView: страница оплаты, раздел помощи, условия использования, часть личного кабинета или целое приложение-«обёртка» над сайтом. WebView удобен, потому что позволяет переиспользовать веб-код. Но это не обычный браузер: нет адресной строки, пользователь не видит, на каком сайте находится, и главное — веб-страница может быть связана с нативным кодом приложения через мост. Поэтому на экспертизе настройки WebView проверяются отдельно.
В этой статье разберём основные риски WebView, как проверить их самостоятельно и как закрыть на Android, iOS и во Flutter.
В чём проблема
Риск WebView складывается из трёх вещей: какой контент загружается, что этому контенту разрешено и где остаются данные сессии.
1. JavaScript-мосты
addJavascriptInterface() на Android, WKScriptMessageHandler на iOS, JavaScript channel во Flutter — всё это позволяет JavaScript на веб-странице вызывать нативные функции приложения. Например, Android.getToken() или window.webkit.messageHandlers.pay.postMessage(...).
Если WebView открывает только ваши страницы, это управляемый риск. Но если в WebView окажется чужая страница (через редирект, параметр deep link, рекламу, XSS на странице), она тоже получит доступ к мосту и прочитает токен или выполнит нативное действие. Поэтому в WebView с мостом нужно строго контролировать, какие страницы открываются.
2. Доступ к файлам
В Android WebView есть настройки, разрешающие схему file://: setAllowFileAccess, setAllowFileAccessFromFileURLs, setAllowUniversalAccessFromFileURLs. Если включены последние две, страница, загруженная через file://, может читать другие локальные файлы или любой домен. Это может привести к утечке внутренних файлов приложения (сессия, кеш, база данных). В современных версиях Android значения по умолчанию безопасны, но в коде встречается их ручное включение.
3. Смешанный контент и ошибки сертификата
Если на странице https загружаются ресурсы по http (скрипт, картинка, iframe), это смешанный контент (mixed content). Скрипт из открытого канала можно подменить в сети. Ещё опаснее ошибка — вызов handler.proceed() внутри onReceivedSslError: так принимается любой неверный сертификат, и TLS фактически отключается. Часто это пишут для работы с самоподписанным сертификатом тестового сервера, и код остаётся в release.
4. Неконтролируемая навигация
Если приложение не ограничивает навигацию, WebView открывает у себя любую ссылку со страницы. В итоге пользователь оказывается внутри вашего приложения, но на чужом сайте (например, на поддельной странице входа) и не замечает этого.
5. Данные, оставшиеся после выхода
WebView хранит свои cookie, localStorage, IndexedDB и кеш. При выходе из приложения нативная сессия удаляется, но cookie веб-сессии в WebView остаётся. Следующий пользователь (или человек, взявший устройство) открывает WebView и попадает в предыдущий аккаунт. Готовя к экспертизе наше собственное приложение, мы закрыли именно этот момент: при выходе данные WebView полностью очищаются.
6. Режим отладки
Если WebView.setWebContentsDebuggingEnabled(true) (Android) или isInspectable = true (iOS) остаётся в release-сборке, к WebView можно подключиться с компьютера через Chrome DevTools или Safari Web Inspector и увидеть DOM, cookie и запросы.
Почему это важно для экспертизы
Группа PLATFORM в OWASP MASVS требует, чтобы WebView были безопасно настроены, а мосты открывались только при необходимости; в MASTG есть отдельные тест-кейсы для WebView (JavaScript, доступ к файлам, мосты, навигация). По OWASP Mobile Top 10 (2024) эти находки попадают в M4 (Insufficient Input/Output Validation), M5 (Insecure Communication — смешанный контент и игнорирование ошибок SSL), M8 (Security Misconfiguration) и M9 (Insecure Data Storage — данные после выхода).
Эксперт декомпилирует APK, ищет настройки WebView в коде, а при динамическом анализе пытается загрузить в WebView чужой URL. Поэтому эту часть полезно просмотреть заранее.
Как проверить самостоятельно
Статическая проверка
Откройте APK в jadx и поищите:
jadx -d out app-release.apk
grep -rn "addJavascriptInterface\|setAllowFileAccess\|setAllowUniversalAccessFromFileURLs\|setAllowFileAccessFromFileURLs" out/sources
grep -rn "onReceivedSslError\|setMixedContentMode\|setWebContentsDebuggingEnabled" out/sources
grep -rn "@JavascriptInterface" out/sources
Просмотрите каждый метод с @JavascriptInterface: что он возвращает и что выполняет? Токен, персональные данные или платёжное действие — тревожный признак.
Во Flutter-приложениях основной код находится в libapp.so, поэтому ищите в исходниках: addJavaScriptChannel, setJavaScriptMode, loadRequest, NavigationDelegate.
На iOS ищите в исходном коде WKWebView, addScriptMessageHandler, allowFileAccessFromFileURLs, isInspectable. Устаревшего UIWebView быть не должно вовсе.
Динамическая проверка
- Проверьте каждый путь, по которому открывается WebView: параметр deep link, push-уведомление, ссылка на странице. Чужой домен открываться не должен.
- Откройте
chrome://inspectпри подключённом тестовом устройстве. WebView release-приложения там не должен появляться. - Смотрите трафик через прокси (например, Burp Suite): запросов по
httpбыть не должно. Попробуйте без установленного сертификата прокси — WebView не должен открывать страницу (если открывает, ошибка SSL игнорируется). - Выйдите из аккаунта и снова откройте страницу WebView. Должен потребоваться вход.
Как закрыть
Android (Kotlin)
val allowedHosts = setOf("example.uz", "pay.example.uz")
webView.settings.apply {
javaScriptEnabled = true // только если нужно
allowFileAccess = false
allowContentAccess = false
@Suppress("DEPRECATION")
allowFileAccessFromFileURLs = false
@Suppress("DEPRECATION")
allowUniversalAccessFromFileURLs = false
mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
domStorageEnabled = true
}
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView, request: WebResourceRequest
): Boolean {
val uri = request.url
val ok = uri.scheme == "https" && uri.host in allowedHosts
if (!ok) {
// чужая ссылка открывается во внешнем браузере или блокируется
return true
}
return false
}
override fun onReceivedSslError(
view: WebView, handler: SslErrorHandler, error: SslError
) {
handler.cancel() // никогда не proceed()
}
}
Если нужно показывать локальные HTML-файлы, вместо file:// используйте WebViewAssetLoader из библиотеки androidx.webkit: он отдаёт файлы по адресу вида https://appassets.androidplatform.net/..., и включать доступ к файлам не требуется.
Если JavaScript-мост нужен:
- добавляйте его только при загрузке доверенного домена;
- методы должны быть минимальными и не возвращать секреты;
- при каждом вызове проверяйте host у
webView.url; - по возможности используйте
addWebMessageListenerизandroidx.webkit— он принимает список разрешённых origin.
Очистка при выходе:
fun clearWebData(context: Context, webView: WebView?) {
CookieManager.getInstance().removeAllCookies(null)
CookieManager.getInstance().flush()
WebStorage.getInstance().deleteAllData()
webView?.clearCache(true)
webView?.clearHistory()
webView?.clearFormData()
}
iOS (Swift)
let config = WKWebViewConfiguration()
config.preferences.javaScriptCanOpenWindowsAutomatically = false
let webView = WKWebView(frame: .zero, configuration: config)
#if DEBUG
if #available(iOS 16.4, *) { webView.isInspectable = true }
#endif
func webView(_ webView: WKWebView,
decidePolicyFor action: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = action.request.url,
url.scheme == "https",
let host = url.host,
["example.uz", "pay.example.uz"].contains(host) else {
decisionHandler(.cancel); return
}
decisionHandler(.allow)
}
В script message handler проверяйте message.frameInfo.securityOrigin.host и принимайте сообщения только от разрешённых origin. Не включайте исключения ATS (App Transport Security), такие как NSAllowsArbitraryLoads.
При выходе:
let store = WKWebsiteDataStore.default()
store.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(),
modifiedSince: .distantPast) { }
Для чувствительных страниц можно использовать WKWebsiteDataStore.nonPersistent() — тогда данные не записываются на диск.
Flutter (webview_flutter)
const allowedHosts = {'example.uz', 'pay.example.uz'};
final controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted) // только если нужно
..setNavigationDelegate(
NavigationDelegate(
onNavigationRequest: (request) {
final uri = Uri.parse(request.url);
final ok = uri.scheme == 'https' && allowedHosts.contains(uri.host);
return ok ? NavigationDecision.navigate : NavigationDecision.prevent;
},
),
)
..loadRequest(Uri.parse('https://example.uz/help'));
Если добавляете JavaScript channel, при получении сообщения проверяйте адрес текущей страницы (await controller.currentUrl()) и никогда не возвращайте токен через channel.
При выходе:
Future<void> clearWebData(WebViewController? controller) async {
await WebViewCookieManager().clearCookies();
if (controller != null) {
await controller.clearCache();
await controller.clearLocalStorage();
}
}
На Android cleartextTrafficPermitted="false" в network_security_config.xml тоже останавливает http-запросы в WebView — об этом мы писали в отдельной статье.
На стороне сервера
Страницы, открываемые в WebView, должны быть защищены как обычные веб-страницы: CSP, cookie с HttpOnly и Secure, корректное экранирование против XSS. XSS на странице означает доступ к нативному мосту внутри WebView, поэтому здесь веб-безопасность и мобильная безопасность сходятся.
Короткий чек-лист
- WebView открывает только
https-домены из allowlist; остальные — во внешнем браузере или блокируются. - В
onReceivedSslErrorтолькоcancel();proceed()нет. -
MIXED_CONTENT_NEVER_ALLOW, cleartext отключён, на iOS нет исключений ATS. - Доступ к файлам отключён; локальные файлы — через
WebViewAssetLoader. - JavaScript включён только в тех WebView, где он нужен.
- JavaScript-мосты минимальны, не возвращают секреты, origin проверяется.
- В release отладка WebView и
isInspectableотключены. - При выходе очищаются cookie, localStorage, кеш и история.
-
UIWebViewне используется. - На страницах, открываемых в WebView, есть CSP и защита от XSS.
Вывод
WebView — маленький браузер внутри приложения, но без многих защит браузера. Приложение должно чётко определять, что в него загружается, к чему загруженная страница имеет доступ и что остаётся после выхода пользователя. Allowlist, отключённый доступ к файлам, строгий TLS и полная очистка при выходе закрывают большинство находок экспертизы по WebView. О передаче URL в WebView через deep link подробно написано в статье deep link и экспортированные компоненты.
Если хотите до экспертизы узнать, правильно ли настроены WebView в вашем приложении, оставьте заявку на сканирование. Claude AI агент просмотрит код и настройки, а наш специалист проверит находки на устройстве и поможет их закрыть.


