HTTP 协议冷知识大全

2018-03-28 21:45:13 +08:00
 codehole

如果不用 HTTPS,HTTP 协议如何安全的传输密码信息?

HTTP 协议是纯文本协议,没有任何加密措施。通过 HTTP 协议传输的数据都可以在网络上被完全监听。如果用户登陆时将用户名和密码直接明文通过 HTTP 协议传输过去了,那么密码可能会被黑客窃取。 一种方法是使用非对称加密。GET 登陆页面时,将公钥以 Javascript 变量的形式暴露给浏览器。然后用公钥对用户的密码加密后,再将密码密文、用户名和公钥一起发送给服务器。服务器会提前存储公钥和私钥的映射信息,通过客户端发过来的公钥就可以查出对应的私钥,然后对密码密文进行解密就可以还原出密码的明文。 为了加强公钥私钥的安全性,服务器应该动态生成公钥私钥对,并且使用后立即销毁。但是动态生成又是非常耗费计算资源的,所以一般服务器会选择 Pool 方法提供有限数量的公钥私钥对池,然后每隔一段时间刷新一次 Pool。

文件路径攻击

很多操作系统都会使用..符号表示上层目录。如果黑客在 URL 的路径里面使用..符号引用上层目录,而服务器没有做好防范的话就有可能导致黑客可以直接访问权限之外的文件。比如使用多级..符号就可以引用到根目录,进一步就可以访问任意文件。 所以很多服务器都禁止在 URL 路径里出现..符号以避免被攻击。 文件路径攻击也是很多黑客非常喜爱使用的攻击方法之一。如果你的服务器有一定的访问量,打开你的 nginx 日志,你就会偶尔发现有一些奇怪的 URL 里面有一堆..符号,这种 URL 的出现就表示网络上的黑客正在尝试攻击你的服务器。

DNS 欺骗

HTTP 协议严重依赖于 DNS 域名解析。任意一个域名类网址的访问都需要经过域名解析的过程得到目标服务的 IP 地址才能成功继续下去。 如果掌管 DNS 服务的运营商作恶将域名解析到不正确的 IP,指向一个钓鱼的网页服务。用户如果没有觉察,就可能会将自己的敏感信息提交给冒牌的服务器。

谨慎使用外部的 HTTP 代理

HTTP 代理作为客户端到服务器之间的中间路由节点,它起到传话人和翻译官的角色。 如果这个翻译官不靠谱的话,客户端是会拿到错误的返回数据的。它同 DNS 欺骗一样,是可以对客户端进行钓鱼攻击的。 如果这个翻译官口风不严的话,它可能会将它听到的敏感信息泄露给别人。

413 Request Entity Too Large

客户端上传图片太大超过服务器限制时,服务器返回 413 错误。

414 Request-URI Too Long

客户端访问的 URI 太长,超出了服务器允许限制,服务器返回 414 错误。

202 Accepted

常用于异步请求。客户端发送请求到服务器,服务器立即返回一个 202 Accepted 表示已经成功接收到客户端的请求。 后面怎么处理由服务器自己决定,一般服务器会给客户端预留一个可以查询处理状态的接口,客户端可以选择轮训该接口来知道请求的处理进度和结果。

POST 提交数据的方式

application/x-www-form-urlencoded

提交数据表单时经常使用,Body 内部存放的是转码后的键值对。

POST http://xyz.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8

a=1&b=2&c=3&c=4

application/json

提交结构化表单时使用,Body 内部存放的是 JSON 字符串。ElasticSearch 的查询协议使用的是这种方式。

POST http://xyz.com HTTP/1.1
Content-Type: application/json;charset=utf-8

{"a": 1, "b": 2, "c": [3, 4]}

multipart/form-data

上传文件时经常使用。这种格式比较复杂,它是为了支持多文件上传混合表单数据而设计的一种特殊的格式。

<form action="http://example.com/upload" method="post" enctype="multipart/form-data">
  <p><input type="text" name="key1" value="value1">
  <p><input type="text" name="key2" value="value2">
  <p><input type="file" name="file1">
  <p><input type="file" name="file2">
  <p><button type="submit">Submit</button>
</form>

用户填充了表单设置了待上传的文件,点击 Submit,传输数据大致如下

POST /upload HTTP/1.1
Content-Length:xxxxx
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryKOThiwE6HubGib7j
Host:example.com
------WebKitFormBoundaryKOThiwE6HubGib7j
Content-Disposition: form-data; name="key1"

value1
------WebKitFormBoundaryKOThiwE6HubGib7j
Content-Disposition: form-data; name="key2"

value2
------WebKitFormBoundaryKOThiwE6HubGib7j
Content-Disposition: form-data; name="file1"; filename="file1name.png"
Content-Type: image/png

file1 content here
------WebKitFormBoundaryKOThiwE6HubGib7j
Content-Disposition: form-data; name="file2"; filename="file2name.jpeg"
Content-Type: image/jpeg

file2 content here
------WebKitFormBoundaryKOThiwE6HubGib7j--

Cookie

浏览器请求的 Cookie 中往往会携带敏感信息。服务器一般会将当前用户的会话 ID 存在 cookie 里,会话的具体内容存在服务器端,会话的内容很敏感。

浏览器请求时会携带 Cookie 信息,服务器根据 Cookie 信息中的会话 ID 找到对应的会话内容。会话内容里可能存储了用户的权限信息,拿到这部分权限信息后就可能随意控制修改用户的数据。

因为 HTTP 协议的不安全性,请求数据包很容易被窃听,Cookie 中的会话信息很容易被盗。解决方案之一就是在会话中记录用户的终端信息和 IP 地址信息,如果这些信息突然发生改变,需要强制用户重新认证。

不过高级的黑客是可以伪造出和用户真实请求一摸一样的数据包的。最彻底的解决方案还是采用 HTTPS 协议。

普通的 Cookie 信息可以通过 Javascript 脚本获取到。如果黑客通过某种方式在网页中植入不安全的脚本,将用户的 Cookie 拿到然后发送到远程的第三方服务器中,那么 Cookie 中的信息就被泄露了。

Cookie 的两个重要属性

Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

被标记为 Secure 的 Cookie 信息在 HTTP 请求中不会被传送,它只会在 HTTPS 请求中传送,避免数据被泄露。

被标记为 HttpOnly 的 Cookie 信息是无法通过 Javascript API 获取到的,它只会在请求中传送。这样可以避免黑客通过网页脚本方式窃取 Cookie 中的敏感信息。

Cookie(甜点)如此好吃,黑客们总想通过 Cookie 做各种文章。

CSRF(Cross-Site Request Forgery)

CSRF 跨站请求伪造有很多别名,比如 One-Click Attack(一键攻击),比如 Session Riding(搭便车攻击)

假设在在一个社区博客网站中,删除个人的文章只需要一个 URL 就可以,Cookie 中的会话权限信息会自动附加到请求上。

# 123456 为文章的 ID
http://example.com/blog/123456/delete

那么当别人伪造了一个上面的链接地址诱惑你去点击,比如通过站内信件、私聊、博客评论、图片链接或者在别的什么网站上随机制造的一个链接。你不经意点了一下,就丢了你的文章。所以它被称为一键攻击。因为这是借用了你当前登陆的会话信息来搞事,所以也被称为搭便车攻击。

如果在一个金融系统中,转账要是也可以通过一个简单的 URL 进行的话,那这种危险就非同小可。

这就要求修改性的操作务必不得使用简单的 GET 请求进行处理。但是即使这种情况下你改成了 POST 请求,黑客依然有办法伪造请求,那就是通过 iframe。

黑客在别的什么网站上伪造了一个 POST 表单,诱惑你去 submit。如果只是普通的内嵌进 HTML 网页的表单,用户提交时会出现跨域问题。因为当前网站的域名和表单提交的目标域名不一致。但是如果通过 iframe 来内嵌表单,则可以绕过跨域的问题,而用户却完全没有任何觉察。

为了防范 CSRF 攻击,聪明的网站的 POST 表单里都会带上 CSRF_TOKEN 这个隐藏字段。CSRF_TOKEN 是根据用户的会话信息生成的。当表单提交时,会将 token 和用户的会话信息做比对。如果匹配就是有效的提交请求。

<form method="POST" action="/blog/delete">
<label for="blog_id">博客 ID</label>
<input type="text" name="blog_id" value="12345">
<input type="hidden" name="csrf_token" value="xxxxxxxxxxxx">
</form>

黑客必须拿到 CSRF_TOKEN 才可以借用用户的会话信息实施 CSRF 攻击,但是 CSRF_TOKEN 又必须由用户的会话信息才可以生成。黑客没有用户的会话信息,从而无法实施 CSRF 攻击。

XSS(Cross Site Scripting)

如果黑客可以在你的网页中植入任意 Javascript 脚本,那他就可以随意鱼肉你的账户。通过 Javascript 可以获取 Cookie 的信息,可以借用你的会话去调用一些隐秘的 API,而这一些行为都是在偷偷的进行,你根本完全不知道。

<div>
# 用户内容 Start
<script>send_to_hacker(document.cookie)</script>
# 用户内容 END
</div>

这类攻击在一些 UGC 网站中非常常见,常见的博客类网站就是 UGC 网站,用户可以通过编辑内容来生成网页。

黑客也是用户。他可以编辑一段 Javascript 脚本作为内容提交上去。如果服务器没有做好防范,这段脚本就会在生成的网页中运行起来。当其它用户在登陆的状态下来浏览这个网页的时候,就悲剧了。

防范 XSS 一般是通过对输出的内容进行内容替换做到的。在 HTML 页面中不同的位置会有不同的内容替换规则。 比较常见的是使用 HTML entity 编码将 HTML 标签之间的内容中的一些特殊的字符进行转码。

<div>
# safe now
&lt;script&gt;send_to_hacker(document.cookie)&lt;/script&gt;
</div>

还有些 UGC 内容在 HTML 标签的属性中、Javascript 的变量中、URL、css 代码中,他们转码的规则并不一样,具体方法可以去 Google 相关文档。

跨域

跨域是个很头痛的问题。

当你有多个后端服务,但是只有一个前端的时候,你想做前后端分离,就会遇到跨域问题。你发现你的前端 js 调用后端服务时控制台告诉你不 ok。然后只好把这些服务都挂在了同一个 nginx 域名下面,通过 url 前缀区分。

这时候你会想,跨域太 TM 讨厌了。既然跨域这么讨厌,那为什么浏览器非要限制跨域呢?

还是安全原因。

让我们回到上文的搭便车攻击(Session Riding),也就是骑着别人的会话来搞事情。

假设现在你的浏览器开了一个站点 A,登陆了进去,于是 cookie 便记录了会话 id。 然后你又不小心开了另一个站点 B,这个站点页面一打开就开始执行一些恶意代码。这些代码的逻辑是调用站点 A 的 API 来获取站点 A 的数据,因为可以骑着(Ride)站点 A 的会话 cookie。而这些数据正好是用户私密性的。于是用户在站点 A 上的私有信息就被站点 B 上的代码窃走了。这就是跨域的风险。

但是有时候我们又希望共享数据给不同的站点,该怎么办呢?

答案是JSONP & CORS

JSONP(JSON Padding)

JSONP 通过 HTML 的 script 标记实现了跨域共享数据的方式。JSONP 通过在网页里定义一个回调方法,然后在页面上插入一个动态 script 标签,指向目标调用地址。服务器会返回一段 javascript 代码,一般是some_callback(data)这种形式的回调。该段代码会在浏览器里自动执行,于是网页就得到了跨域服务器返回的数据。

<script>
function some_callback(data) {
    console.log(data)
}
</script>
<script src="http://example.com/someapi?callback=some_callback"></script>

因为 JSONP 是不携带 cookie 信息的,所以能有效避免搭便车攻击。JSONP 是否可以获取到数据还需要服务器对这种调用提供显示支持,服务器必须将数据以 javascript 代码的形式返回才可以传递给浏览器。

CORS(Cross-Origin Resource Sharing)

JSONP 的不足在于它只能发送 GET 请求,并且不能携带 cookie。而 CORS 则可以发送任意类型的请求,可以选择性携带 cookie。

CORS 是通过 Ajax 发送的跨域请求技术。CORS 的请求分为两种,一种是简单请求,一种是复杂请求。简单请求就是头部很少很简单的 GET/HEAD/POST 请求。复杂请求就是非简单请求。

浏览器发现 Ajax 的请求是跨域的,就会在请求头添加一个 Origin 参数,指明当前请求的发起站点来源。服务器根据 Origin 参数来决定是否授权。

如果是简单请求,Ajax 直接请求服务器。服务器会当成普通的请求直接返回内容,不同的是还会在响应头部添加几个重要的头部,其中最重要的头部是Access-Control-Allow-Origin: http://example.com

浏览器如果在响应中没有读到这个头部,就会通知 Ajax 请求失败。虽然服务器返回了数据,浏览器也不让脚本读到数据,这就保证了跨域的安全。服务器就是通过请求的 Origin 参数来决定要不要响应 Access-Control-Allow-Origin 头部来决定是否允许指定网站的跨域请求。

如果是复杂请求,要走一个预检的流程。预检就是浏览器先向服务器发送一个 Method 为 Options 的请求,如果服务器允许跨域请求,浏览器再发起这个 Ajax 请求。所以 CORS 的复杂请求会比简单请求额外耗费一个 TTL 的时间。

CORS 的细节请参见大神阮一峰的博文《跨域资源共享 CORS 详解》

阅读相关文章,关注公众号 [码洞]

1763 次点击
所在节点    程序员
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/442268

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX