对未部署 SSL 的网站能否保证数据传输安全的探究
2020-03-29

由于瘟疫的影响,我校从开学到现在一直在进行网上授课。

网上授课本身没有什么问题,然而有些平台,特别是那些杂七杂八“XX 慕课”平台,在拥有大量用户的情况下不知廉耻地对保护用户数据没有任何做为,比如拒不使用 HTTPS——甚至在用户登录流程中也是。 不过,假如有某种方式可以保证在没有 SSL 保护时用户数据的安全,就不能排除这些平台使用了这种方式保障安全,从“没有 SSL”批评这些平台的这个切入点也就不存在了。所以我决定试着探究一下“未部署 SSL 的网站能否保证数据传输安全”这个问题。

最近正好在写一个对安全要求比较高的小工具,当中涉及到了相关问题,所以写了这篇一半是笔记、一半是吐槽的文章。

通常,介绍 HTTPS 的文章大多是从“为什么用了 SSL 会安全”这个角度写的。我准备换个角度,写一写“为什么不用 SSL 会不安全”。

下面以用户登录为例,来探究无 SSL 安全传输数据的可能性。

明文传输数据的风险

当用户与服务器间的连接未被加密时,数据在传输途中经过的几乎所有设备都可以进行对其进行截取甚至修改。因此,在这种情况下最需要防范的就是重放攻击与中间人攻击。

重放攻击

爱丽丝给鲍勃写了一封信:亲爱的,我遇到了些麻烦,现在需要一千块钱,请寄给我,谢谢! 邮递员马洛里私自打开了这封信,把信的内容原封不动复印了一份留下。信件照常送到了鲍勃手中,鲍勃发了一封内有一千块钱的信件给爱丽丝,爱丽丝正常地收到了。 马洛里到处鬼混把工资花光了,于是他将之前复印的那封信交给了鲍勃,鲍勃依旧发了一封内有一千块钱的信件,但是这次钱落进了马洛里手里。

这就是重放攻击。攻击者抓取到了用户向服务器传输的登录凭证的数据包(不需要是明文),攻击者可以向服务器发送相同的数据包从而通过用户验证流程,侵入系统内。

HTTPS 连接天生免疫重放攻击,在每次连接握手的过程中,都会产生一串随机的加密密钥,用以加密这个连接会话中传输的数据。就算攻击者抓到了用户发送的登录凭证数据包,也不能用它通过用户验证流程,因为每个连接的密钥都是不同的。

中间人攻击

爱丽丝给鲍勃写了一封信:亲爱的,我遇到了些麻烦,现在需要一千块钱,请寄给我,谢谢! 邮递员奥斯卡私自打开了这封信,将“一千块钱”改成了“两千块钱”。信件送到了鲍勃手中,鲍勃发了一封内有两千块钱的信件给爱丽丝,奥斯卡拿走了其中的一千块钱,把剩下钱的交给了爱丽丝。

这就是中间人攻击。中间人可以截获并修改客户端与服务器之间传输的数据,从而达到目的,侵入系统内。

HTTPS 连接可以在一定程度上防御中间人攻击,因为连接是非对称加密的。但是如果客户端设备上安装了“虚假的证书”,那么中间人攻击者就可以通过这个证书劫持并解密连接内容。 考虑到你国绝大多数人在计算机方面的知识水平,欺骗他们安装“虚假的证书”非常容易,毕竟他们用电脑从来不看提示信息,从来不看对话框的内容就点“下一步”。 连 HTTPS 的中间人攻击都这么容易实现,更何况是 HTTP。

下面来探究HTTP连接是否可以通过某些方式防御上面两种攻击。

客户端向服务器传输数据

如何使客户端加密的数据只能在对应的服务器上解密?

首先,对称加密不可能做到这一点,因为加密所用的密钥不论是客户端还是服务器生成的,都不可避免地需要明文传输。 因此我们很自然地会想到用诸如 RSA 之类的非对称加密算法来保护数据。

使用非对称加密

我们可以设计出类似如下的流程:

  1. 服务器收到用户端对登录页面的请求
  2. 服务器在返回登录页面的同时返回预先设置好的 RSA 公钥
  3. 用户输入登录信息后点击登录按钮,此时用 JS 将用户输入的内容用 RSA 公钥加密,之后 POST 给服务器
  4. 服务器收到 POST 后使用存储的 RSA 私钥解密数据,并与存储的信息进行比对
  5. 依据验证是否成功向用户端返回响应的页面并在 session 中记录

使用非对称加密是为了达到“只能用公钥加密”、“只能用私钥解密”的效果,对于用户端使用服务器发送的 RSA 公钥加密后的数据,除非持有对应的私钥,否则根本无法解密,因此中间人通过监听截获的只是一串无法解密的无意义乱码。

以上流程看起来是不是没有任何问题?很多登录系统采用的就是这种设计。

然而答案却是否定的。事实上,这种流程没有任何卵用。

对于重放攻击,这个 RSA 加密没有任何用处,它与明文传输唯一的区别仅在于攻击者不能得到明文密码,但攻击者仍然可以用抓到的数据包通过用户验证,从而侵入系统。

对于中间人攻击,这种流程也不会起到任何数据保护作用,可能会出现如下情况:

  1. 服务器收到用户端对登录页面的请求
  2. 服务器在返回登录页面的同时返回预先设置好的 RSA 公钥
  3. 中间人劫持服务器返回的数据,并将服务器返回的公钥替换为“虚假的公钥”
  4. 用户输入登录信息后点击登录按钮,此时用 JS 将用户输入的内容用“虚假的公钥”加密,之后 POST 给服务器
  5. 中间人劫持用户端发送的数据,用“虚假的私钥”解密用户发送的数据
  6. 用户的密码落到了中间人手中

这只是让攻击的流程多了几步而已,攻击者还是可以得到用户密码。 我们发现,只要有密钥传输的过程,中间人总可以“套”出明文密码。另外,只要传输的数据没有随机的因子,重放攻击就可以实现。

使用散列算法并加入一次性因子

发现这些问题后,下面我们可以有针对性地改善这些缺点,两个比较重要的点:

由此得到以下流程:

  1. 服务器收到用户端对登录页面的请求
  2. 服务器随机生成一串字符,并将其存储至当前 session 中
  3. 服务器在返回登录页面的同时返回这串字符
  4. 用户输入登录信息后点击登录按钮,此时用 JS 将这串字符作为盐,散列计算已处理过的用户输入的内容,之后 POST 给服务器
  5. 服务器收到 POST 后使用在 session 中存储的这串字符作为盐,散列计算保存的用户信息,并与收到的 POST 数据进行比对
  6. 依据验证是否成功向用户端返回响应的页面并在 session 中记录

这样,就算中间人修改了字符串并监听得到了用户 POST 的数据,他也无法得到明文的用户密码,毕竟加过盐的散列结果很难破解,只要你用的不是 MD5、SHA-1 之类的弱算法,并且进行了多次散列。 同样的,由于作为盐的字符串是一次性的,含有登录凭证的数据包被抓到也无法二次使用,所以重放攻击无法实现。 这么搞,数据是不是就安全了呢?

非也!

拥有用户与服务器之间路由的人能做的可远远不止上面那些,攻击者可以这样:

  1. 服务器收到用户端对登录页面的请求(同上)
  2. 服务器随机生成一串字符,并将其存储至当前 session 中(同上)
  3. 服务器在返回登录页面的同时返回这串字符(同上)
  4. 用户输入登录信息后点击登录按钮,此时用JS将这串字符作为盐,散列计算已处理过的用户输入的内容,之后POST给服务器(同上)
  5. 攻击者监听到了用户凭证数据包,保存后立即将其丢弃,服务器无法收到任何请求
  6. 攻击者伪造 session(非常容易,session 一般是通过 cookies、隐藏表单记录的,中间人可以轻松获取),然后重放这个包,由于服务器端仍在使用那串字符作为盐,所以攻击者可以通过用户验证从而进入系统

某堵墙可以丢掉你请求访问 Google 的数据包,攻击者凭什么不能丢掉你请求登录的数据包?

综上,可以看出,在没有 SSL 保护的情况下,保证客户端向服务器传输数据的安全非常非常难,这是否能说明没有 SSL 的话一切保护数据的努力都是徒劳? 先不急着得到结论,我们先讨论下一个部分。

服务器向客户端传输数据

通常情况下,登录各个网课系统后,页面上会显示出你的名字、学校、班级、学号之类的信息。 如果这些信息没有经过加密传输(很遗憾,很多网站的确没有对其加密传输),会是对用户隐私安全的毁灭性打击。因此我们需要向一些方法加密它们。 由于用户端是解密端,所以用非对称加密不合理。所以,需要设计一种能够保证密钥安全的对称加密流程。

我只能想到下面这一种实现方式:

  1. 用户在输入密码后,取散列后的密码的一部分(比如散列后得到一串 512 位的字符串,取其 16~32 位),再次 hash,可以多次重复这个过程,然后将结果存储于 HTML5 Web Storage 中
  2. 服务器端需要安全地向用户端传输数据时,取保存的用户登录凭证用相同的算法处理,作为密钥
  3. 用户端收到信息后调用存储于 HTML5 Web Storage 中的字符串进行解密

这里使用 HTML5 Web Storage 而非 Cookies 存储密钥是因为 HTML5 Web Storage 相对安全一些。Cookies 会在请求 header 中被明文传输,使用 Cookies 存储密钥和一叶障目差不多。 这是权衡后的实现,既能在一定程度上保证用户密码、密钥的安全性,也能保证密钥无需传输。

然而,就算能保证服务器向客户端传输数据的安全,还是会涉及到那个问题:

先有鸡,还是先有蛋?

客户端不向服务器传输数据,服务器向客户端传输什么数据?

由此,我们可以得出结论:

保证HTTP连接的数据传输安全性?无解。

从理论上讲,保证 HTTP 连接的数据传输安全是不可能的,以上一切的努力都是徒劳。 就算你找到了你认为更好的加密流程,只要不用 SSL,中间人永远都能获取他想要的信息。 你在 JS 里写了一大堆算法,中间人只需要插入一小段 JS,将用户输入的发送到攻击者的服务器,你的所有努力就都白费了。 你做的只是微微增加了破解难度,但根本无法阻止破解。

了解过 HTTPS 原理的朋友应该能看出,上面那些对数据传输安全的尝试几乎就是在自行实现一个简单 SSL 协议了。这可不是一个网站该干的事情,况且本身就是无法实现的。 HTTPS 的根基,是在客户端预装可信的根证书,这保证了用户与服务器之间的加密无需交换密钥。几段简简单单的 JS 怎么可能做到这点?

想自行实现 SSL?不是傻就是疯。都 2020 年了,为什么不乖乖上 SSL?更何况现在连免费的证书都出现了!

不用 SSL,就是对用户最大的不尊重。

最后的吐槽

令人无奈的现状

当年应该有很多人玩过“贴吧云签到”,还记得这些程序是如何实现百度帐号登录的吗? 没错,是 BDUSS 这个 cookie。 只要你拿到某个百度帐号的 BDUSS,你就可以非常轻松地登入这个帐号,甚至无需用户名与密码。

连百度这种体量的公司都不舍得用一次性 token,可见中国的企业在用户安全上做得有多差。

当年 CSDN 被脱裤、爆出明文存储用户密码后,我觉得应该不会再有大企业敢这么干了。毕竟 CSDN 仅仅是一群脑残用户自嗨的地方,影响面不会很大。

然而就在前几天,微博被曝脱裤了,这次的影响面可不小。还是同样的愚蠢问题,竟然被这些大公司一次次地重演,只能感叹可悲了。

出现这类事故,责任在谁身上?写代码的程序员?我觉得他们的失误并非这些事件最大的原因。

真正应该付全责的应该是这个大环境。

那些服务的用户们,他们不就是那群“每天没事就刷抖音的人”吗?他们不会关注、也不想关注自己的信息卖给了谁,他们也不会试图去搞清自己的信息有没有被加密传输。

写这些代码的程序员,无非是每天 996、每天忙着在完成任务的前提下抽空摸鱼的普通人。人们都想着在高强度工作下自保,用户信息的安全?屁!拿这些信息换 KPI 才是正确的选择!

人是有极限的,这些程序员在高强度工作下能写出真正的好代码吗?明显不能。他们只会碌碌无为地 Copying and Pasting from Stack Overflow,甚至有些人连 Stack Overflow 是什么都不知道,Copying and Pasting from CSDN。等到了 35 岁转行去送外卖。

可悲的是,由于这个国家劳动力的富足,以及大量“每天没事就刷抖音的人”涌入计算机行业,企业不会缺少为他们 996 的人。

他们就是韭菜,割了一茬,又长一茬。

我们能做些什么?

如果你认为“谁闲得无聊会去偷你的个人信息?”,不好意思,请赶快关掉这个浏览器页面,以节省您宝贵的时间去多刷刷抖音,顺便还能减轻我服务器的负担。 你是个丑八怪老太婆,没胸没屁股,没人会强奸你,那么你就可以不穿衣服出门吗?

如果你讨厌现在的网络安全环境,请你:


由于我的技术水平限制,在这篇文章中,小到用词错误、大到概念错误,都可能会出现。如果你在文章中发现了问题或有更好的解决方案,请在下面评论,谢谢!😂