移动端如何做好数据防护

截至2022年底,移动互联网用户数接入数达14.6亿户,当年移动互联网接入流量达2617.6亿GB,比上年增长18.1%。流量的背后,是海量的数据,而数据又逐渐成为各个企业的核心资产。做好数据的防护工作,是每一个互联网企业需要认真对待的一件事情。

一、数据防护范围的界定

现在绝大多数的App都强依赖网络,客户端所展示的内容都是通过接口从服务器上获取,同时客户端也会向服务器上传数据,在两端通信的过程中,数据容易被第三方获取,导致数据泄漏或接口被盗刷。本文主要从防止数据被抓包的方向来分析在移动端如何做好数据防护。

二、抓包的核心原理

HTTPS抓包原理:Fiddler、Charles等抓包工具,其实都是采用了中间人攻击的方案: 将客户端的网络流量代理到MITM(中间人)主机,再通过一系列的面板或工具将网络请求结构化地呈现出来。

抓包HTTPS的两个突破点:CA证书校验是否合法;数据传递过程中的加密和解密。如果需要抓包,则需要MITM(中间人)伪造证书和使用自己的加解密方式。

抓包工作流程:
1.中间人截获客户端向发起的HTTPS请求,佯装客户端,向真实的服务器发起请求;
2.中间人截获真实服务器的返回,佯装真实服务器,向客户端发送数据;
3.中间人获取了用来加密服务器公钥的非对称秘钥和用来加密数据的对称秘钥,处理数据加解密。

  • 下图以Charles为例描述抓包原理

三、客户端通过关闭代理的方式防止抓包

3.1 检查手机是否打开了代理

(1)iOS实现方法

+ (BOOL)isProxy {    
    NSDictionary *proxySettings =  (__bridge NSDictionary *)(CFNetworkCopySystemProxySettings());    
    NSArray *proxies = (__bridge NSArray *)(CFNetworkCopyProxiesForURL((__bridge CFURLRef _Nonnull)([NSURL URLWithString:@"http://www.baidu.com"]), (__bridge CFDictionaryRef _Nonnull)(proxySettings)));    
    NSDictionary *settings = [proxies objectAtIndex:0];        
    if ([[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@"kCFProxyTypeNone"]) {
        //没有设置代理        
        return NO;    
    }else{       
        //设置代理了        
        return YES;    
    }
}

(2)安卓实现方法

private fun checkWifiProxy(): Boolean {
    val IS_ICS_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH        
    val proxyAddress: String?        
    val proxyPort: Int? 
    if (IS_ICS_OR_LATER) {            
        proxyAddress = System.getProperty("http.proxyHost")//获取代理主机
        val portStr = System.getProperty("http.proxyPort")//获取代理端口           
        proxyPort = Integer.parseInt(portStr ?: "-1")        
    } else {            
        proxyAddress = android.net.Proxy.getHost(this)            
        proxyPort = android.net.Proxy.getPort(this)       
    }        
    Log.i("cxmyDev","proxyAddress : ${proxyAddress}, prot : ${proxyPort}")
    return !TextUtils.isEmpty(proxyAddress) && proxyPort != -1    
}

3.2关闭代理

3.2.1 iOS客户端实现方法

(1)设置代理为空

// 禁止代理
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
configuration.connectionProxyDictionary = @{};

(2)不侵入代码的设置方式

@interface NSURLSession (Extensions)

+ (void)proxyEnabled:(BOOL)enabled;

@end

#import <objc/runtime.h>

@implementation NSURLSession (Extensions)

static BOOL proxyEnabled = YES;

+ (void)load {
    [super load];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [NSURLSession class];
        [self swizzleMethodWithClass:class
                    originalSelector:NSSelectorFromString(@"sessionWithConfiguration:")
                    swizzledSelector:NSSelectorFromString(@"_sessionWithConfiguration:")];
        [self swizzleMethodWithClass:class
                    originalSelector:NSSelectorFromString(@"sessionWithConfiguration:delegate:delegateQueue:")
                    swizzledSelector:NSSelectorFromString(@"_sessionWithConfiguration:delegate:delegateQueue:")];
    });
}

+ (void)proxyEnabled:(BOOL)enabled {
    proxyEnabled = enabled;
}

+ (NSURLSession *)_sessionWithConfiguration:(NSURLSessionConfiguration *)configuration {
    if (!proxyEnabled) {
        configuration.connectionProxyDictionary = @{};
    }
    return [self _sessionWithConfiguration:configuration];
}

+ (NSURLSession *)_sessionWithConfiguration:(NSURLSessionConfiguration *)configuration
                                   delegate:(nullable id<NSURLSessionDelegate>)delegate
                              delegateQueue:(nullable NSOperationQueue *)queue {
    if (!proxyEnabled) {
        configuration.connectionProxyDictionary = @{};
    }
    return [self _sessionWithConfiguration:configuration delegate:delegate delegateQueue:queue];
}

+ (void)swizzleMethodWithClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
    Method originalMethod = class_getClassMethod(cls, originalSelector);
    Method swizzledMethod = class_getClassMethod(cls, swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}
3.2.2 安卓客户端实现方法

(1)设置代理为空

URL url1 = new URL("url");
HttpURLConnection uc = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);

(2)在okhttp中设置禁止使用代理

var httpBuilder = OkHttpClient.Builder().addInterceptor(defaultInterceptor()).connectTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS).writeTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS).readTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS).proxy(Proxy.NO_PROXY)

使用这种方式来防止抓包,优点是实现简单,无系统版本兼容问题;缺点是该方案比较粗暴,对于合理诉求需求使用网络代理的场景无法满足。且可以通过VPN导流进行抓包,即现将手机请求导到VPN,再对VPN的网络进行代理,绕过对App的代理。

四、其他方案

关闭代理的方式只是一种较为初步的防范手段,除此之外,还可以通过证书校验、双向认证等方式来避免App被恶意抓包。

4.1 证书校验(单向认证)

基于“中间人攻击”时会伪造证书,在通信前进行证书校验。证书校验即客户端下载服务端公钥证书,然后将证书保存到App本地,由应用在交互过程中验证证书的合法性。证书校验流程如下:

4.2 双向认证

单向认证安全性相对较高,证书锁定破解比较复杂,但通过一些工具,采用hook各网络框架的证书认证方法,替换原有逻辑,可以使校验失效。如果需要更高等级的保护手段,可以选择双向认证的方式。

双向认证是指除了客户端需要校验服务端的真实性,服务端也需要校验客户端的真实性。双向认证需要服务端支持,客户端必须内置一套公钥证书和私钥。在SSL/TLS握手过程中,服务端会向客户端请求证书,客户端必须将内置公钥发给服务端,服务端验证公钥证书的真实性。用于双向认证的公钥证书和私钥代表客户端身份,所以是隐秘的,一般都是用.p12或者client.crt+client.key进行存放。双向认证流程如下:


双向认证的安全性非常高,使用工具也不易破解,但由于增加了服务端校验成本,降低了响应速度,该方案比较适合对安全等级要求比较高的业务。

五、总结

以上列举的只是部分数据保护方案,每个业务根据自身对数据的安全的要求,选择适合自身的方案。此文仅从数据安全的一个方面来探讨如何做好数据防护,一方面分享几种数据防抓包的方案,另一方面可以引起大家对数据安全的重视和对数据安全方面的兴趣。

参考

  • 移动端防抓包方案设计和实践:https://www.wanandroid.com/blog/show/3490
  • iOS app 防止抓包:https://www.jianshu.com/p/bb688ec7e5d5
  • App防止抓包小技巧:https://cloud.tencent.com/developer/article/1445715
  • Android抓包攻防技术(代理检测与反代理):https://www.cnblogs.com/zgq123456/articles/17460263.html

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。

关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值