HTTP是一个美好的东西:一个存在了20多年而没有太多变化的协议。
正如我们在前一篇文章中看到的,浏览器通过HTTP协议与web应用程序交互,这是我们深入研究这个主题的主要原因。如果用户在网站上输入他们的信用卡信息,黑客就能在数据到达服务器之前拦截数据,我们肯定会有麻烦。
了解HTTP是如何工作的,我们如何保护客户端和服务器之间的通信,以及该协议提供了哪些与安全相关的特性,这是改进安全状态的首步。
但是,在讨论HTTP时,我们应该始终区分语义和技术实现,因为它们是HTTP工作方式的两个非常不同的方面。
两者之间的关键区别可以用一个非常简单的类比来解释:20年前,人们像现在一样关心他们的亲人,尽管他们互动的方式已经发生了巨大的变化。我们的父母可能会开着车去他们姐姐家,这样就能赶上和家人在一起。
相反,现在更常见的是在 WhatsApp 上留言、打电话或使用 Facebook 群组,这在以前是不可能的。这并不是说人们或多或少地交流或关心,而是说他们交流的方式改变了。
HTTP 也不例外:协议背后的语义没有太大的变化,而客户端和服务器之间通信的技术实现已经经过多年的优化。如果您查看 1996 年的 HTTP 请求,它看起来与我们在前一篇文章中看到的请求非常相似,尽管这些数据包通过网络的方式非常不同。
概述
如前所述,HTTP遵循请求/响应模型,其中连接到服务器的客户端发出请求,服务器对其进行响应。
HTTP消息(请求或响应)包含多个部分:
- 请求行
- 请求头
- 请求体
一部分:请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本。
- GET /players/lebron-james HTTP/1.1
GET说明请求类型为 GET,/players/lebron-james 为要访问的资源,该行的后一部分说明使用的是 HTTP1.1 版本。
第二部分:请求头部,紧接着请求行之后的部分,用来说明服务器要使用的附加信。
- GET /players/lebron-james HTTP/1.1
- Host: nba.com
- Accept: */*
- Coolness: 9000
例如,在此请求中,客户端已为请求附加了3个附加标头:Host,Accept和 Coolness。
等一下,Coolness 是什么
报头不必使用特定的保留名称,但通常建议依赖于 HTTP 规范标准化的名称:越偏离标准,交换中的另一方就越不理解你。
例如,Cache-Control 是一个头文件,用于定义响应是否是可缓存的:大多数代理和反向代理都完全按照 HTTP 规范来理解它。如果将 Cache-Control 头重命名为 Awesome-Cache-Control,代理将不再知道如何缓存响应,因为它们不是按照你刚刚提出的规范构建的。
但有时候,在消息中包含“自定义”标题可能是有意义的,因为你可能希望添加实际上不属于 HTTP 规范的元数据:服务器可以决定在其响应中包含技术信息,以便客户端可以同时执行请求并获取有关回复的服务器状态的重要信息:
- ...
- X-Cpu-Usage: 40%
- X-Memory-Available: 1%
- ...
使用自定义标头时,始终为它们添加一个键,以便它们不会与将来可能成为标准的其他标头冲突:从历史上看,这一直很有效,直到每个人都开始使用“非标准” X 前缀 反过来,这成为常态。 X-Forwarded-For 和 X-Forwarded-Proto标 头是负载平衡器和代理广泛使用和理解的自定义标头的示例,即使它们不是 HTTP 标准的一部分。
如果你需要添加自己的自定义头,那么现在通常使用一个自动生成的前缀,例如 Acme-Custom-Header 头或 A-Custom-Header 头。
在标题之后,一个请求可能包含一个主体,它与标题之间用空行隔开:
- POST /players/lebron-james/comments HTTP/1.1
- Host: nba.com
- Accept: */*
- Coolness: 9000
- Best Player Ever
我们的请求完成了:首行(位置和协议信息)、请求头和请求体。注意,请求体是完全可选的,在大多数情况下,它只在我们想要向服务器发送数据时使用——这就是上面的示例使用 POST 的原因。
响应没有太大的不同:
- HTTP/1.1 200 OK
- Content-Type: application/json
- Cache-Control: private, max-age=3600
- {"name": "Lebron James", "birthplace": "Akron, Ohio", ...}
响应发布的一个信息是它使用的协议版本以及该响应的状态。请求头也一样,如果需要的话,在正文后面加一个换行符。
如前所述,该协议经过了多次修订,并随着时间的推移添加了一些特性(新的头文件、状态代码等),但是底层结构并没有太大的变化(请求行、请求头和正文)。真正改变的是客户端和服务器如何交换这些消息——让我们更仔细地研究一下。
HTTP vs HTTPS vs H2
HTTP 已经经历了 2 个相当大的语义变化: HTTP/1.0 和 HTTP/1.1。
那,“HTTPS 和 HTTP2 在哪里?”
HTTPS 和 HTTP2 (缩写为 H2)是更多的技术更改,因为它们引入了在互联网上传递消息的新方法,而不会严重影响协议的语义。
HTTPS 是 HTTP的一种“安全”扩展,它涉及在客户机和服务器之间建立一个公共秘密,确保我们与正确的一方进行通信,并对与公共秘密交换的消息进行加密(稍后将对此进行详细介绍)。HTTPS 的目标是提高协议HTTP 的安全性,而 H2 的目标是为其带来更快的速度。
H2 使用二进制而不是纯文本消息,支持多路复用,使用 HPACK 算法压缩报头……长话短说,H2 是对HTTP/1.1 的性能提升。
网站所有者不愿意切换到 HTTPS,因为它涉及客户端和服务器之间的额外往返(如上所述,需要在两方之间建立共同的秘密),从而减慢用户体验:使用 H2 加密 默认情况下,他们就没有借口了,因为多路复用和服务器推送等功能使其 性能优于普通的 HTTP/1.1。
HTTPS
HTTPS的目标是让客户端和服务器通过 TLS(传输层安全性)安全地进行通信,TLS 是SSL(安全套接字层)的继承者。
TLS 所针对的问题相当简单,可以用一个简单的比喻:你的另一半中午打电话给你,当你在一个会议上,并询问你告诉他们你的网上银行账户的密码,因为他们需要执行一个银行转账,以确保你儿子的教育费用按时支付。重要的是你现在就告诉他们,否则第二天早上你的孩子可能会被学校拒之门外。
你们现在面临着两个挑战:
- 身份验证: 确保你真的在和你的另一半说话,因为有可能别人会假装他们
- 加密: 在同事无法理解和记录下密码的情况下进行通信
这正是 HTTPS 试图解决的问题。
为了验证你正在与谁交谈,HTTPS 使用公钥证书,这只是声明特定服务器背后身份的证书:当你通过 HTTPS 连接到 IP 地址时,该地址背后的服务器将向你提供其证书,以验证其身份。回到我们的类比,这可能只是你让你的另一半拼写他们的社会保险号。一旦验证了数字的正确性,你就获得了额外的信任级别。
但是,这并不能阻止黑客学习受害者的社会安全号码,偷走你伴侣的智能手机并给你打电话。 我们如何验证来电者的身份?
你不是直接让你的另一半拼他们的社会保险号,而是打电话给你的妈妈(她正好住在你隔壁),让她去你的公寓,确保你的另一半拼的是他们的社会保险号。这增加了额外的信任级别,因为你不认为你的母亲是一个威胁,并依赖她来验证调用者的身份。
在 HTTPS 术语中,你的妈妈称为 CA,证书颁发机构 (Certificate Authority)的简称:CA 的工作是验证特定服务器后面的身份,并颁发具有自己的数字签名的证书:这意味着,当我连接到特定域时,我不会出示由域所有者生成的证书(称为自签名证书),而是由 CA 颁发。
权威机构的职责是确保他们验证域名后面的身份并相应地颁发证书:当你“订购”证书时(通常称为 SSL 证书,即使现在使用 TLS 代替 ), 当局可能会给人打电话或要求你更改 DNS 设置,以验证你是否可以控制相关域。 验证过程完成后,它将颁发证书,然后你可以在 Web 服务器上安装该证书。
像浏览器这样的客户端将连接到您的服务器并获得此证书,以便他们可以验证它看起来是真实的:浏览器与CA有某种“关系”,因为它们跟踪可信CA的列表。 为了验证证书是否真的值得信赖。 如果证书未由受信任的机构签名,则浏览器将向用户显示一条信息量大的警告:
确保你和你的另一半之间的通信安全已经完成了一半:现在我们已经解决了身份验证(验证调用者的身份),我们需要确保我们可以安全地通信,而不会在此过程中被其他人窃听。正如我提到的,你正在开会,需要拼写你的网上银行密码。你需要找到一种方法来加密你的交流,这样只有你和你的伴侣才能理解你的谈话。
您可以通过在双方之间建立共享密钥来实现此目的,并通过该密钥加密消息:例如,你可以根据婚礼日期决定使用 Caesar cipher 的变体。
如果双方都有一段稳定的关系,就像你和你的灵魂伴侣一样,这将会很有效,因为他们可以在别人不知道的共同记忆的基础上创造一个密钥。但是,浏览器和服务器不能使用相同的机制,因为它们事先不了解彼此。
取而代之的是 Diffie-Hellman 密钥交换协议的变体,它确保没有预先知道的各方建立共享的密钥,而其他人无法“嗅探”它。这需要用到一点数学知识,这是留给读者的一个练习。
一旦密钥建立起来,客户端和服务器就可以进行通信,而不必担心有人会截获它们的消息。即使黑客这样做,他们也没有解密消息所需的公共密钥。
HTTPS无处不在
还在争论你是否应该在你的网站上支持HTTPS? 我没有好消息:浏览器已经开始推动用户远离不支持HTTPS 的网站,以“强迫”网络开发者提供完全加密的浏览体验。
在 “HTTPS无处不在” 的口号背后,浏览器开始反对未加密的连接——谷歌宣布从 Chrome 68(2018年7月) 开始将把HTTP网站标记为“不安全”:
对于不使用HTTPS的网站来说,更令人担忧的是,一旦用户在网页上输入任何内容,“不安全”标签就会变成红色——这一举动应该会鼓励用户在与不支持HTTPS的网站交换数据之前三思而后行。
将此与在HTTPS上运行并配备有效证书的网站的外观进行比较:
从理论上讲,网站不一定是安全的,但在实践中,这会吓跑用户 - 这是理所当然的。 当 H2 还没普遍时,坚持使用未加密的HTTP通信是有意义的,如今几乎没有理由这样做。
GET 和 POST
正如我们前面看到的,HTTP请求以一个特殊的请求行开始:
首先,客户端告诉服务器它正在使用什么动词来执行请求:常见的 HTTP 动词包括 GET,POST,PUT 和 DELETE,但列表可以继续使用不常见(但仍然是标准的)动词,如 TRACE, OPTIONS,或 HEAD。
理论上,没有一种方法比其他方法更安全;实际上,事情并没有那么简单。
GET 请求通常不带主体,因此参数包含在 URL 中(如 www.example.com/articles?article_id=1),而 POST 请求通常用于发送(“post”)包含在内的数据。
另一个区别在于这些动词带有的副作用:GET 是一个幂等动词,意思是无论你要发送多少个请求,你都不会改变网络服务器的状态。 相反,POST 不是幂等的:对于你发送的每个请求,你可能正在更改服务器的状态(例如,考虑发布新的付款 - 现在您可能理解为什么站点要求你在执行时不刷新页面 交易)。
幂等性:指一次和多次请求某一个资源应该具有同样的副作用,也就是一次访问与多次访问,对这个资源带来的变化是相同的。
为了说明这些方法之间的一个重要区别,我们需要看一看 web 服务器的日志,这些日志你可能已经很熟悉了:
- 192.168.99.1 - [192.168.99.1] - - [29/Jul/2018:00:39:47 +0000] "GET /?token=1234 HTTP/1.1" 200 525 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36" 404 0.002 [example-local] 172.17.0.8:9090 525 0.002 200
- 192.168.99.1 - [192.168.99.1] - - [29/Jul/2018:00:40:47 +0000] "GET / HTTP/1.1" 200 525 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36" 393 0.004 [example-local] 172.17.0.8:9090 525 0.004 200
- 192.168.99.1 - [192.168.99.1] - - [29/Jul/2018:00:41:34 +0000] "PUT /users HTTP/1.1" 201 23 "http://example.local/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36" 4878 0.016 [example-local] 172.17.0.8:9090 23 0.016 201
如你所见,web服务器记录请求路径:这意味着,如果你在 URL 中包含敏感数据,那么它将被 web 服务器泄露并保存在你的日志中的某个位置—你的密钥将以明文的形式出现,这是我们绝对需要避免的。假设黑客能够访问你的一个旧日志文件,该文件可能包含信用卡信息、私有服务的访问令牌等等:这将是一场彻底的灾难。
Web 服务器不记 录HTTP标头或主体,因为要保存的数据太大 - 这就是为什么通过请求主体而不是URL发送信息通常更安全。 从这里我们可以得出 POST(和类似的,非幂等方法)比 GET 更安全,即使更多的是使用特定动词时数据的发送方式而不是特定动词本身比其他动词更安全:如果你 将敏感信息包含在 GET 请求的主体中,然后你不会遇到比使用 POST 时更多的问题,即使这种方法被认为是不寻常的。
我们信任 HTTP 报头
在本文中,我们研究了HTTP,它的演变以及它的安全扩展如何集成身份验证和加密,以使客户端和服务器通过安全通道进行通信:这不是所有 HTTP 在安全性方面提供的。
正如我们将在下一篇文章中看到的,HTTP安全头文件提供了一种改进应用程序安全状态的方法,下一篇文章将致力于理解如何利用它们。