前段时间跟QQ群里的群友聊天时无意聊到了抓包的话题。抓包可以说是程序员日常开发调试问题的一个重要手段,可以帮助我们理清客户端与服务器之间的数据传输问题,以便于甩锅。
在过去,网络请求基本都是靠的http协议,那个时候的抓包是一件非常简单的事情。然而这几年,http协议在逐渐被淘汰,几乎所有的网络请求都变成了https协议,这就使事情变得复杂了。
群里一位朋友说,https是不可能被抓包的,不然怎么保证https传输的安全性,毕竟那么多大公司都在用这个协议来传输重要的数据。
这其实是一个比较有意思的话题,https确实是非常安全的。但同时,https也确实是可以抓包的,它们两者之间并不冲突。
考虑到仍然有许多朋友在这方面还有些不太了解,我准备写两篇文章来讲讲https抓包的相关知识。本篇文章先讲实践,教大家如何在Android手机上对https请求进行抓包。下一篇文章会讲原理,我们一起解析一下,为什么如此安全的https协议却仍然可以被抓包呢?
那么先从实践看起吧。
抓包工具的使用
要对网络请求进行抓包,首先肯定要选择一个抓包工具才行。
专业的抓包工具有很多,根据我的观察,国内的大多数开发者都比较喜欢用Charles这个工具来进行抓包。不过我个人更喜欢用Fiddler这个工具,而且我们平时工作时如果要进行抓包也都是用的Fiddler。因为Fiddler和微软内部的日志分析工具是相互兼容的,并且Fiddler的作者也在微软工作。
那么本篇文章我都会以Fiddler这个工具来进行举例讲解,当然如果你习惯用Charles也完全没有问题,只是在工具的操作上可能会有所区别,原理是完全相同的。
首先需要在你的电脑上安装Fiddler,这个工具是完全免费的,下载地址是:
https://www.telerik.com/fiddler/fiddler-everywhere
安装完成之后登录一下就可以使用了,它会自动抓取你当前这台电脑上的所有网络请求包。
但是如果我们想要抓取手机上的网络请求,那么还需要做点额外的配置才行。
首先从Fiddler顶部工具栏依次点击View -> Preferences -> Connections,将会看到如下所示界面:
这里有两点需要注意,一个是端口号,默认值是8866,如果没有什么特殊需求的话可以不用修改。
第二个是Allow remote computers to connect这个选项是一定要勾上的,不然手机上的网络请求将无法抓到。
勾上第二个选项,点击SAVE,这样电脑端的配置就完成了。
接下来我们还需要在手机端进行一些简单的配置。
要确保的是,你的手机和用于抓包的这台电脑必须在同一个局域网下。
然后修改手机当前连接Wifi的高级选项,将代理类型改为手动,将代理主机名改成电脑的ip地址,将代理端口改成8866,如下图所示:
点击保存即可。
完成以上配置之后,其实我们就可以使用Fiddler来对手机上的网络请求进行抓包了。不信你可以试一试在手机的浏览器上访问以下地址:
http://guolin.tech/api/china/
应该会看到如下界面:
然后再到Fiddler中查看一下,你就能发现刚才手机上的网络请求包已经成功被Fiddler抓到了(有时Fiddler中显示的包信息过多,不方便查看,可以使用Ctrl X清空信息):
可以看到,这条网络请求的所有细节在Fiddler中一览无余,包括请求的头信息,响应的头信息,响应的body内容等等。
抓包工具将网络通讯的背后细节全部搬到了台面上,这样当客户端和服务器遇到联调问题时,到底是客户端的锅还是服务器的锅,看一看抓出来的包就全部清楚了。
以上就是抓包工具最传统的用法,然而这种用法现在已经不那么好使了,因为还在使用http协议的网络请求已经越来越少,绝大部分的网络请求都变成了https协议。
对https请求进行抓包
https协议是一种加密传输的网络协议,所传输的数据不再是以明文的方式来传输,而都是加密过后再进行传输的。
这种协议保障了用户的数据安全,但对于抓包而言却是一件苦恼的事情。因为数据都加密了呀,我们抓到的包也都是一些密文信息,所以根本就无法用于定位问题。
比如我们可以尝试在手机浏览器中访问一下必应,然后观看Fiddler中抓到的包信息,如下图所示:
可以看到,Fiddler虽然能够捕获到访问必应的网络请求,但是却无法解密出具体的传输内容,这种包对于我们分析问题并没有任何帮助。
那么对于https请求的网络包我们到底要怎么抓呢?别担心,Fiddler是支持这个功能的,下面跟着我一步步操作就行。
首先需要在Fiddler中开启https抓包功能,从Fiddler顶部工具栏依次点击View -> Preferences -> HTTPS。
在HTTPS设置页面中,先点击Trust root certificate来安装证书,然后勾选Capture HTTPS traffic选项,如下图所示:
点击SAVE保存,这样你就可以抓到电脑上https请求的包了。
但是手机上https请求的包我们还是抓不到的,你可以试试再次在手机上访问必应,将会看到如下界面:
出现这种错误基本都是证书的原因导致的,在下篇文章中我会详细分析这个错误出现的原因,本篇文章中我们先将它解决就好了。
在你的手机浏览器中访问如下地址:
http://ipv4.fiddler:8866/
将会看到一个由Fiddler内置的网页:
点击FiddlerRoot certificate这个链接,下载并安装由Fiddler提供的手机证书。
安装完成之后再次访问必应,你就会发现不会再报错了,而是可以正常显示出网页的内容:
然后观察Fiddler,可以看到,请求必应首页的网络包也被成功抓到了,而且这次不再是密文,而是解密后的数据:
对https请求的抓包问题,就这样解决了!
对Android应用进行抓包
如此看来,https抓包貌似也并不是一件难事。
没错,但还有一个细节需要大家注意。上述方案只适用于对浏览器中的网络请求进行抓包,如果你是想要对其他应用程序的网络请求抓包的话,仍然还是抓不到的。
为了证实这一点,我们就来新建一个应用程序,并编写一段最简单的网络请求代码,看看到底能不能抓到它发出的网络请求。
整个程序非常简单,我们在MainActivity中加入一个按钮,当点击按钮时就发起一个网络请求,代码如下所示:
代码语言:javascript复制class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button: Button = findViewById(R.id.button)
button.setOnClickListener {
sendRequest()
}
}
private fun sendRequest() {
thread {
var connection: HttpURLConnection? = null
try {
val response = StringBuilder()
val url = URL("https://www.bing.com")
connection = url.openConnection() as HttpURLConnection
val input = connection.inputStream
val reader = BufferedReader(InputStreamReader(input))
reader.use {
reader.forEachLine {
response.append(it)
}
}
Log.d("TAG", response.toString())
} catch (e: Exception) {
e.printStackTrace()
} finally {
connection?.disconnect()
}
}
}
}
没错,总共就这么多代码。但是不要忘记我们还得在AndroidManifest.xml中声明网络权限:
代码语言:javascript复制<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.capturetest">
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
好了,现在来运行一下程序看一看效果吧。点击界面上的按钮,会向必应主页发起一条网络请求,然后观察Fiddler中的数据包:
可以看到,我们是无法像之前在浏览器中那样,成功抓到并解析出https请求的包信息的。
为什么会这样呢?这是因为Android在7.0系统中进行了一项安全升级。从Android 7.0系统开始,只是在手机上安装了抓包工具的证书,仍然是无法对https请求进行抓包的,还必须要在应用程序的代码中加入一段网络安全配置才行。
这项升级使得每个应用程序都变得更加安全,因为对https抓包确实是一个比较危险的行为,所有加密传输的数据都以明文的形式展示出来了。当然,如果是为了调试程序而抓包,这算是一个正当理由,但是你也理应只能对自己的程序进行抓包调试而已。如果只要在手机上安装了证书就可以对所有App的https请求进行抓包,那么无疑大大降低了这些App的安全性。
因此,Android 7.0系统中才做了这项安全升级。默认情况下,我们无法对各个App的https请求进行抓包,如果你是想要对自己App的https请求抓包的话,那么可以这样做。
在res/xml目录下创建一个network_security_config.xml文件,然后加入如下配置:
代码语言:javascript复制<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="user"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>
接下来还需要在AndroidManifest.xml中配置android:networkSecurityConfig属性来让上述配置生效:
代码语言:javascript复制<application
...
android:networkSecurityConfig="@xml/network_security_config">
...
</application>
这样我们就可以对当前的应用程序发出的https网络请求进行抓包了。
重新运行一下程序,让我们再来试试吧,结果如下图所示:
结果正如我们预期的那样工作了。
最后一个小疑惑
那么本篇文章的内容到这里就差不多该结束了。
但是不知道大家有没有产生一个小疑惑,既然是从Android 7.0开始必须要在自己的应用程序中加入网络安全配置才能对https请求抓包,为什么我们一开始在浏览器中什么都没配,却也成功抓到了https请求的网络包呢?
这个问题其实让我困惑了很久,直到现在加入了微软Edge项目组才终于解开了这个疑惑。
Edge是一款基于Chromium内核的浏览器,Chrome也是,许多主流的浏览器都是。其实答案一直都在Chromium的源码中,只是我之前从来没有勇气去看过。
我们来查看一下Chromium源码中的AndroidManifest.xml文件,部分代码如下图所示:
可以看到,Chromium的源码中也加入了一段android:networkSecurityConfig配置,那么我们继续跟进去看看里面到底配置了什么:
这不是和我们刚才在Demo中配置的内容一模一样吗?
自此真相大白了,原来之所以浏览器不需要做额外的配置也能对https请求进行抓包,是因为Chromium源码中已经对此做好了配置,而所有基于Chromium内核的浏览器也就都自动拥有了这个功能。
如果你想要在线查看Chromium的源码,可以访问这个地址:
https://source.chromium.org/
好了,本篇文章的内容就到这里。相信看完这篇文章,会对大家平时的网络开发与调试工作产生一定的帮助。
解决了怎么用的问题,接下来就要去了解原理了。下篇文章中我们来一起探讨一下为什么传说中如此安全的https协议却仍然可以被抓包呢?我们下期再见。