网络劫持一般有两种情况,一种是DNS劫持,另一种是HTTP劫持

从表现上区分这两种劫持非常简单。

如果是DNS劫持,你输入的网址是google.com,然后出来的页面是百度。

如果是HTTP劫持,你打开了google.com,可是右下角弹出了百度推广的不孕不育广告。

URL域名解析成ip地址的过程被称作 DNS 解析。在这个过程中,由于 DNS 请求报文是明文状态,可能会在请求过程中被监测,然后攻击者伪装DNS服务器向主机发送带有假ip地址的响应报文,从而使得主机访问到假的服务器。这个就是DNS劫持的根本原理。

而另一种就是HTTP劫持。在运营商的路由器节点上,设置协议检测,一旦发现是HTTP请求,而且是html类型请求,则拦截处理。后续做法往往分为2种,1种是类似DNS劫持返回302让用户浏览器跳转到另外的地址,还有1种是在服务器返回的 HTML 数据中插入 js 或 dom 节点,从而使网页中出现自己的广告等等垃圾信息。

一般来说,针对各种网络劫持,大部分工作都是由前端来完成,针对这一方面的研究,也大多都是前端开发方向。但是其实客户端也可以通过一些方法来防劫持。

作为客户端开发,我们应该先了解我们的URL Loading System。

虽然 URL 加载系统包含的内容众多,但代码的设计上却非常良好,没有把复杂的操作暴露出来,开发者只需要在用到的时候进行设置。(苹果官方文档About the URL Loading System,是每个 iOS 开发者都应该认真研究的。)

DNS劫持

DNS劫持的问题,就可以基于 NSURLProtocol 实现 LocalDNS 防劫持方案。

关于LocalDNS防劫持方案,可以参考一篇大神文章DNS防劫持。

简单来说,在网页发起请求的时候获取请求域名,然后在本地进行解析得到ip,返回一个直接访问网页ip地址的请求。

结构体struct hostent用来表示地址信息:

struct hostent {char *h_name;                     // official name of hostchar **h_aliases;                 // alias listint h_addrtype;                   // host address type——AF_INET || AF_INET6int h_length;                     // length of addresschar **h_addr_list;               // list of addresses
};

通过C函数gethostbyname,使用递归查询的方式将传入的域名转换成struct hostent结构体,在本地将URL解析成123.123.25.53这种ip地址。具体实现参考文章中的代码。

另外还可以用从服务器下发对应的DNS解析列表来代替递归查询这种比较低效的方式,文章中也有介绍。

而无论是那种方式,NSURLProtocol都是处理的核心部分。

NSURLProtocol

NSURLProtocol 或许是 URL 加载系统中最功能强大但同时也是最晦涩的部分了。它是一个抽象类,你可以通过子类化来定义新的或已经存在的 URL 加载行为。

用了它,你不必改动应用在网络调用上的其他部分,就可以改变URL加载行为的全部细节。 NSURLProtocol 就是一个苹果允许的中间人攻击。

下面这么多需求,都可以通过 NSURLProtocol,在不改动其他代码的情况下,比较简单地就能实现:

  • 拦截图片加载请求,转为从本地文件加载

  • 为了测试对 HTTP 返回内容进行mock和stub

  • 对发出请求的header进行格式化

  • 对发出的媒体请求进行签名

  • 创建本地代理服务,用于数据变化时对URL请求的更改

  • 故意制造畸形或非法返回数据来测试程序的鲁棒性

  • 过滤请求和返回中的敏感信息

  • 在既有协议基础上完成对 NSURLConnection 的实现且与原逻辑不产生矛盾

官方文档对 NSURLProtocol 的描述是这样的:

An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.

在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的 NSURLProtocol 对象处理对应的 URL 请求,而我们需要做的就是写一个继承自 NSURLProtocol 的类,并通过 - registerClass: 方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。

如何使用 NSURLProtocol 拦截 HTTP 请求?有这个么几个问题需要去解决:

  • 如何决定哪些请求需要当前协议对象处理?

  • 对当前的请求对象需要进行哪些处理?

  • NSURLProtocol 如何实例化?

  • 如何发出 HTTP 请求并且将响应传递给调用者?

这几个问题其实都可以通过 NSURLProtocol 为我们提供的 API 来解决,决定请求是否需要当前协议对象处理的方法是:+ canInitWithRequest,每一次请求都会有一个 NSURLRequest 实例,上述方法会拿到所有的请求对象,我们就可以根据对应的请求选择是否处理该对象。

那么究竟是否要处理对应的请求。由于网页存在动态链接的可能性,简单的返回YES可能会创建大量的NSURLProtocol对象,因此我们需要保证每个请求能且仅能被返回一次YES

请求经过 + canInitWithRequest: 方法过滤之后,我们得到了所有要处理的请求,接下来需要对请求进行一定的操作,而这都会在 + canonicalRequestForRequest: 中进行,虽然它与 + canInitWithRequest: 方法传入的 request 对象都是一个,但是最好不要在 + canInitWithRequest: 中操作对象,可能会有语义上的问题。

所以,我们需要覆写 + canonicalRequestForRequest: 方法提供一个标准的请求对象:

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {return request;
}

这里就可以对request做一些修改,比如加个header什么的,只需要最后能返回一个NSURLRequest即可。

如果处理请求返回了YES,那么下面两个回调对应请求开始和结束阶段。在这里可以标记请求对象已经被处理过。

- (void)startLoading;
- (void)stopLoading;

在之前的 iOS 客户端基于 WebP 图片格式的流量优化 这篇文章中,就是利用- (void)startLoading;来替换图片请求的。当时替换 WebP 图片的核心功能,实际上就是在NSURLProtocol中完成的。

HTTP劫持

实际上,这种劫持对于大部分客户端来说,是无能为力的,需要前端来处理。不过,有些特殊情况下,客户端也可以有一些针对 HTTP劫持的办法。

比如像媒体新闻类客户端的文章中,防止js注入

在具体的实现方式之前,需要有一些准备工作,就是关于URL Loading System中的NSURLCache

NSURLCache

NSURLCache 为您的应用的 URL 请求提供了内存中以及磁盘上的综合缓存机制。 作为基础类库 URL Loading System 的一部分,任何通过 NSURLConnection 加载的请求都将被 NSURLCache 处理。

当一个请求完成得到来自服务器的Response,在本地保存作为cache。下一次同一个请求再发起时,本地保存的Response就会马上返回,不需要连接服务器。NSURLCache 会 自动 且 透明 地返回回应。

在NSURLConnection加载系统中,缓存被设计为request对象的一个属性,由NSURLRequest对象的cachePolicy属性指定。而在NSURLSession加载系统中,缓存被设计为 NSURLSessionConfiguration对像的一个属性,该属性所指定的策略被该session的所有request所共享。

作为一个Cache,它头文件中提供的方法并不复杂,就是基本的增删查改,(其中增和改可以算是一个功能,没有就增,改就是覆盖)。主要方法仅六个:

// 初始化方法
- (instancetype)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(nullable NSString *)path;
// 查询方法
- (nullable NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request;
// 存储方法
- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request;
// 删除方法
- (void)removeCachedResponseForRequest:(NSURLRequest *)request;
- (void)removeAllCachedResponses;
- (void)removeCachedResponsesSinceDate:(NSDate *)date NS_AVAILABLE(10_10, 8_0);

非常简洁的类,功能高度封装,用起来很简单。但是它也开辟了一个新世界,就是你可以实现一个子类,来接管系统的URLCache功能。只需要一个简单的步骤:

- (BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{STURLCache *URLCache = [[STURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024diskCapacity:20 * 1024 * 1024diskPath:nil];[NSURLCache setSharedURLCache:URLCache];
}

这样,STURLCache就可以接管缓存的管理了。当系统调用URLCache的增删改查方法时,都可以由子类来接管。

NSURLCache的缓存策略,以及和HTTP header之间的关系,可以参考NSHipster NSURLCache文章,不再深入。

这样,配合NSURLProtocol,就可以对缓存做精准控制了。

js签名防注入

这个方法,适用于新闻类app,因为新闻类app的web页都是自己写的,所以,js和css都是可知的。

防注入的方式就是这样:

  1. 发版前,在 bundle 中存一份最新的前端js文件

  2. 后台在返回 js 文件 URL 的时候,对 js 文件内容进行 SHA-256 ,得到的 hash 值拼接到 js 的文件名中

  3. 请求 js 资源文件时,在NSURLCache中的- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request方法中拦截请求

  4. 拦截请求之后,判断本地是否有缓存,如果有,则直接返回缓存文件包装成 response

  5. 下载 js 资源时,走NSURLProtocol 代理方法- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data,对 data 进行SHA-256签名比对,如果签名一致,将 data 通过;如果签名不一致,代表 js 被污染,直接丢弃,从bundle取出本地预存的 js 文件返回回来。

代码本身不会有什么难处,所以就只写出基本逻辑。

在自定义的URLCache实现文件中:

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {NSString* ua = [request valueForHTTPHeaderField:@"User-Agent"];if (!EmptyString(ua) && [ua lf_containsSubString:@"AppleWebKit"]) {if ([CacheManager shouldVerifyHashCode:request]) { //包含64位hashcode的js css文件// 取本地JS缓存NSData *resultData = [CacheManager getJSCache];if (resultData) {NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:nil expectedContentLength:resultData.length textEncodingName:nil];return [[NSCachedURLResponse alloc] initWithResponse:response data:resultData];} else {return nil;}}return [super cachedResponseForRequest:request];
}- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request {NSString* ua = [request valueForHTTPHeaderField:@"User-Agent"];if ([CacheManager shouldVerifyHashCode:request] && [ua lf_containsSubString:@"AppleWebKit"]) {// 将请求回来的,并且通过验证的新js放到缓存中[[CacheManager defaultManager] storeCachedResponse:cachedResponse forRequest:request])return;}    [super storeCachedResponse:cachedResponse forRequest:request];
}

而在自定义的NSURLProtocol子类中

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {NSString *ua = [request valueForHTTPHeaderField:@"User-Agent"];if ([CacheManager shouldVerifyHashCode:request] && [ua lf_containsSubString:@"AppleWebKit"]) {// 拦截js请求return YES;}return NO;
}// 收到请求返回data的代理方法
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {if([data verifySHA256Success]) {[self.client URLProtocol:self didLoadData:data]; } else {localData = [CacheManager bundleCacheFromUrl:url];[self.client URLProtocol:self didLoadData:localData]; }
}

比较抽象的一点就是,这个方案是不是不能再更新 js 了?

当然不会,因为当后台的 js 文件有更新时,新 js 文件的签名就会发生变化,js 文件的URL也就自然变化,于是本地请求的时候,缓存是无法命中的,所以,也就会直接走下载 js 的那个路径。

这种方案的缺点就是:

  1. 在发生 js 劫持的时候,只能使用本地 js,可能会比最新版本 js 落后

  2. js 文件必须是由自己的服务端提供,并控制,才好对 js 进行签名,所以适用范围略窄

作为这个方案的扩充,可以考虑再次利用NSURLProtocol,当发现 js 被污染,重定向URL,此URL由服务端返回一个加密的 js 文件,对称加密,密钥插入在 js 的密文中,本地解密 js 文件,就可以保证得到最新的,安全的 js 文件了。

不过话说回来,既然都这么费劲的话,为啥不让前端来帮忙做呢,或者直接上HTTPS,才是真正的防劫持之道。

总结

关于运营商网络劫持,我本人毕竟不是前端开发,所以有很多问题可以理解也不是很准确,肯定也不是太专业。只是提供一个比较少看到的功能实现。其实防劫持最终的解决办法就是HTTPS,不过我们还是可以通过这方面的思索,来尝试一些更深入,更好玩的东西。而且可以更深入地去探索苹果框架下的URL Loading System。

References

NSHipster NSURLProtocol

NSHipster NSURLCache

DNS劫持

iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求

About the URL Loading System


更多其他文章欢迎访问我的博客 http://suntao.me

iOS 客户端对于运营商劫持的一点点对抗方式相关推荐

  1. 运营商劫持可以分为几种方式?

    运营商劫持大致分为两种方式: 1.DNS强制解析的方式 2.访问请求的302跳转. 不过这两种方式都是可以检测出来的. iis7网站监控 检查域名是否被墙.DNS污染检测.网站打开速度检测.网站是否被 ...

  2. 当部分地区电信运营商劫持页面,如何识别及解决

    [网络安全篇]关于近期个别用户反馈部分地区电信运营商劫持页面,如何识别及解决. 01 DNS是什么 DNS全称是域名系统,它所起到的作用,在于把域名解析为IP地址.我们能访问到某个网站,靠的是连接到该 ...

  3. 什么运营商劫持?运营商劫持分几种?解决办法都有什么?

    一.什么是运营商劫持? (1)首先了解什么是运营商: 运营商是指那些提供宽带服务的ISP,包括三大运营商中国电信.中国移动.中国联通,还有一些小运营商,比如长城宽带.歌华有线宽带.运营商提供最最基础的 ...

  4. 《运营商劫持, 中间人攻击, 黑客入侵怎么办?》- HTTPS 技术反制

    目录 1.HTTPS 是什么 1.1 运营商劫持 1.2 加密是什么 2. HTTPS 的工作过程 2.1 对称加密 2.2 非对称加密 2.3 引入证书 1.HTTPS 是什么 HTTPS 也是一个 ...

  5. 遇到DNS运营商劫持怎么办、运营商劫持的解决方法有那些

    运营商劫持在如今很普遍,相信很多人还不清楚,什么是运营商劫持?遇到DNS运营商劫持怎么办?本次在这里就为大家科普下. 什么是运营商劫持? 运营商是指那些提供宽带服务的ISP,包括三大运营商中国电信.中 ...

  6. 运营商劫持(DNS/HTTP302)

    本人以网络技术出身,近两年接触CDN网络,处理了一些CDN方面的网络问题,大多数以运营商丢包,延迟抖动为主,也处理一些硬件故障,比如机械硬盘的读写io测试,内存条兼容性测试,服务器IPMI规划等.这篇 ...

  7. 网络劫持运营商劫持解决办法

    怎么检测自己的网站是不是有劫持的情况? iis7网站监控 网站打开速度查询.DNS污染.地区电信劫持等问题检测. 网络劫持最主要的就是运营商层面的劫持.可以说,我们每个人随时随地都在与它打交道. 而运 ...

  8. 你可能不知道你已经被运营商劫持了

    大家想必一定遇到过这种小助(guang )手( gao ): 这个是移动,当然联通和电信也绝对不会放过你,而且网页中间也能加 还有PC端 其实,你看到的这些广告都是中途被别人暗地里加的,这个别人就是中 ...

  9. CSP(内容安全策略)防运营商劫持

    (一)前言 CSP英文全称Content Security Policy,中文意思是 内容安全策略. CSP以白名单的机制对网站加载或执行的资源起作用,在网页中,这样的策略通过 HTTP 头信息或者 ...

  10. 电信等运营商劫持的解决方法

    老是弹出广告,其实这个是由于当地电信或者网通等运营商劫持DNS,然后进行投放他们的广告. 弹出广告一般的现象是先弹出一个114或者vnet.cn的网站,类似114.vnet.cn等等,然后进行跳转到广 ...

最新文章

  1. 一生只见一次的大彗星今天来了!
  2. offsetof宏和container_of宏
  3. 使用IntentService在Service中创建耗时任务
  4. 【场景演示解读】AI一体机高速自由流收费稽核解决方案
  5. oracle忘记密码,修改密码
  6. Html、CSS、JavaScript 实时效果在线编辑器 - 学习的好工具,算不算?!
  7. 不错的html学习网址。
  8. Android Sensors (3) 传感器坐标系统
  9. HTML5 开源游戏引擎 LayaAir
  10. bzoj 3437 小p的农场
  11. 素因子分解算法python语言_python中求取最小公倍数的两种方法
  12. 零基础入门实践目标检测项目
  13. 项目管理中的小组周报模板
  14. 【前端】html页面的字体代码表及字体效果对比
  15. 转换到coff期间_error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
  16. 云服务器的带宽是什么意思?怎么选择带宽大小?
  17. Proximal Policy Optimization(PPO)算法原理及实现!
  18. 1072 开学寄语 C++实现
  19. ai与虚拟现实_AI医疗的神话与现实
  20. MySQL忘记root密码解决方案

热门文章

  1. android之volley学习
  2. 面试题13:在O(1)时间删除链表结点
  3. bzoj 2957 楼房重建 分块
  4. CSS3 Transform 变形
  5. Linux下启动Oracle数据库
  6. A1086. Huffuman树
  7. iOS 中 .a 和 .framework 静态库的创建与 .bundle 资源包的使用
  8. 201709020工作日记--synchronized、ReentrantLock、读写锁
  9. 第10题 正则表达式匹配(动态规划)
  10. js requestAnimationFrame