网络拾遗

网络拾遗

Part.1 – HTTP 协议

1. HTTP 特性

  • HTTP 协议构建于 TCP/IP 协议之上,是一个应用层协议,默认端口号是 80。
  • HTTP 是无连接无状态的。

2. HTTP 报文

HTTP 协议是以 ASCII 码进行传输的,建立在 TCP/IP 协议上的应用层规范。规范把 HTTP 分为三个部分:状态行、请求头、请求主体。HTTP 定义了与服务器交互的不同方法,常用的有如下四种: GETPOSTDELETEPUT

URL 全称是资源描述符,一个 URL 地址用于描述一个网络上的资源,而 HTTP 中的 POSTDELETEPUTGET 就对应着对这个资源的 增、删、改、查 四个操作。其他请求方式还有: HEADOPTIONSTRACEPATCH

  • GET 用于信息的获取。(安全和幂等)注:安全意味着该操作用于获取信息而非修改信息。幂等意味着对同一 URL 的多个请求应返回同样的结果。
  • POST 表示可能修改服务器上的资源的请求。(非安全、非幂等)
  • HEADGET 方法类似,但不返回 message body内容,仅仅是获得获取资源的部分信息(content-type、content-length)(安全和幂等)
  • PUT 用于创建、更新资源。(非安全、幂等)
  • DELETE 删除资源。(非安全、幂等)
  • OPTIONS 用于 URL 验证,验证接口服务是否正常。(安全、幂等)
  • TEACE 回显服务器收到的请求,这样客户端可以看到(如果有)哪一些改变或者添加已经被中间服务器实现.(安全、幂等)
  • PATCH 用于创建、更新资源,于PUT类似,区别在于PATCH代表部分更新;
    后来提出的接口方法,使用时可能去要验证客户端和服务端是否支持;(非安全、幂等)

GET 提交的数据量受 URL 长度的限制,HTTP 协议没有对 URL 长度进行限制,这个限制是浏览器和服务器对他的限制;理论上 POST 也是没有大小限制的,HTTP 协议也没有进行大小限制,出于安全考虑,服务器会做一定的限制。

3. POST 提交数据的方式

HTTP 协议中规定 POST 提交的数据必须在 Body 部分中,但协议并未规定数据需要采用何种数据格式或编码方式,服务端通常通过请求头中的 Content-Type 字段来获知请求中的消息主体是以何种方式编码,再对主体进行解析。

POST 提交数据的方案,包含:Content-Type 和消息主体编码方式两部分:

  • application/x-www-form-urlencoded :最常见的 POST 数据提交方式,浏览器的原生
    表单,如果不设置
    enctype 属性,最终就会以
    application/x-www-form-urlencoded 方式提交数据。
  • multipart/form-data :使用表单上传文件时,必须让表单的 enctype 等于 multipart/form-data 。这种方式一般用于上传文件。
  • application/json
  • text/xml
  • application/x-protobuf

只要服务器可以根据 Content-Type 和 Content-Encoding 正确解析出请求即可。

4. 响应报文

HTTP 响应跟 HTTP 请求类似,也是由三部分构成:状态行、响应头、响应正文。

状态行由协议版本、数字形式的状态代码、响应的状态描述构成,各元素以空格分割。常见的状态码:

200 OK
301 Moved Permanently
302 Moved Temporarily
304 Not Modified
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error
503 Service Unavailable

5. 条件 GET

HTTP 条件 GET 是HTTP 协议为了减少不必要的宽带浪费,提出的一种方案。

使用时机:客户端之前已经访问过该网站,并想再次访问。

使用方法:客户端向服务端发送一个包询问是否在上次访问网站后的时间后更改了页面,如果服务器没有更新,显然不需要把整个网页传给客户端,客户端只需要使用本地缓存即可,如果服务器对照客户端给出的时间已经更新了客户端请求的网页,则发送这个更新了的网页给用户。

6. 持久连接

一般情况下,HTTP 协议采用“请求-应答”模式,当使用普通模式,即非 Keep-Alive 模式时,每个请求/应答客户端和服务器都要新建一个连接,完成之后立即断开连接(HTTP 协议为无连接的协议);当使用 Keep-Alive 模式(又称持久连接、连接重用)时,Keep-Alive 功能使客户端到服务端的连接持续有效,当出现对服务器的后续请求时,Keep-Alive 功能避免了建立或者重新建立连接。

在 HTTP 1.0 中: 如果客户端浏览器支持 Keep-Alive,那么就在 HTTP 请求头中添加一个字段 Connection-Keep-Alive

在 HTTP 1.1 中:默认所有连接都被保持。

  • HTTP Keep-Alive 简单来说就是保持当前 TCP 连接,避免重新建立连接。
  • HTTP 长连接不可能一直保持,例如 Keep-Alive: timeout=5, max=100 ,表示这个TCP通道可以保持5秒,max=100,表示这个长连接最多接收100次请求就断开.
  • HTTP 是一个无状态协议,这意味着每个请求都是独立的, Keep-Alive 没有改变这个结果, Keep-Alive 无法保证客户端和服务端的连接一定是活跃的,唯一能保证的是当连接被断开时将会收到一个通知。
  • 使用长连接之后,客户端、服务端怎么知道本次传输结束呢?1. 判断传输数据是否达到了 Content-Length 指示的大小,2.动态生成的文件没有 Content-Length ,它是分块传输(chunked),这时候就要根据 chunked 编码来判断,chunked 编码的数据在最后有一个空 chunked 块,表明本次传输数据结束.

7. Transform-Encoding

Transform-Encoding 是一个用来标明 HTTP 报文传输格式的头部值,当前的 HTTP 规范里只定义了一种传输格式 – chunked .

如果一个 HTTP 消息请求或应答消息的 Transform-Encoding 消息头的值是 chunked ,那么消息体由数量未定的块组成,并以最后一个大小为 0 的块为结束。

  • chunkedmultipart 两个名词在意义上有类似的地方,不过在 HTTP 协议当中这两个概念则不是一个类别的。multipart 是一种 Content-Type ,标示 HTTP 报文内容的类型,而 chunked 是一种传输格式,标示报头将以何种方式进行传输。
  • chunked 传输不能事先知道传输内容的大小,只能靠最后的空 chunked 块来判断结束,所以对于下载请求,是无法知道下载进度的。
  • chunked 优势在于服务端可以边生成内容边发送,无需事先知道全部内容。HTTP/2 是不支持 Transfer-Encoding: chunked 的,因为 HTTP/2 有自己的 streaming 传输方式: Source:MDN - Transfer-Encoding

8. HTTP Pipelining (HTTP 管线化)

默认情况下,HTTP 协议中每个传输层连接只能承载一个 HTTP 请求和响应,浏览器会在收到上一个请求的响应之后再发送下一个请求。在使用持久连接的情况下,某个连接上消息的传递类似于: 请求1 -> 响应1 -> 请求2 -> 响应2 -> 请求3 -> 响应3

使用 HTTP Pipelining 是将多个 HTTP 请求打包传递的技术,在传送过程中无需等待服务端的回应,某个连接上消息的传递类似于: 请求1 -> 请求2 -> 请求3 -> 响应1 -> 响应2 -> 响应3

  • 管线化机制通过持久连接(persistent connection)完成,仅 HTTP/1.1 支持此技术(HTTP/1.0不支持)
  • 只有 GET 和 HEAD 请求可以实现管线化,POST 则有所限制。
  • 初次创建连接时不应启动管线机制,因为对方(服务器)不一定支持 HTTP/1.1 版本的协议。
  • 管线化不会影响响应到来的顺序。
  • HTTP 1.1 要求服务端实现管线化,不要求服务端也对响应进行管线化处理,只要求对管线化请求不失败即可。
  • 由于上面提到的服务器端问题,开启管线化很可能并不会带来大幅度的性能提升,而且很多服务器端和代理程序对管线化的支持并不好,因此现代浏览器如 Chrome 和 Firefox 默认并未开启管线化支持。

9. 会话跟踪

会话

客户端打开与服务端的连接发送请求到服务端响应客户端请求的全过程称之为会话。

会话跟踪

会话跟踪是对同一个用户对服务器的连续的请求和接收响应的监视。

为何需要会话跟踪

客户端跟服务端的通信如果是采用 HTTP 协议通信,而 HTTP 协议是无状态的,它无法保存用户的状态(信息),即一次响应后就断开了,下次请求需要重新连接,此时需要判断是否为同一个用户,所以需要会话跟踪技术实现这种需求。

会话跟踪常用技术

  • URL 重写
    • URL(统一资源定位符)是Web 上特定界面的地址,URL 重写技术就是在 URL 结尾添加一个附加数据来标识该会话,把会话的 ID 通过 URL 传输给服务端,以便在服务端区分不同用户。
  • 隐藏表单域
    • 将会话 ID 添加到HTTP表单元素中提交到服务器,此表单元素客户端不可见
  • Cookie
    • Cookie 是服务端发送给客户端的一小段信息,客户端请求时可读取该信息发送到服务端,进而进行用户识别,对于客户端的每次请求,服务器会将Cookie 下发到客户端,在客户端可以进行保存以便下一次使用。
    • Cookie 可存放在客户端内存中,称为临时Cookie,客户端关闭即清除;另外可存放在磁盘中,成为永久 Cookie。
  • Session
    • 每一个用户都有一个特定的 Session,各个用户之间不可共享,是每个用户独享的,在 Session 中可以存放信息。
    • 在服务端会创建一个 Session 对象,产生一个 SessionID 来标示这个Session对象,然后将这个 SessionID 放入 Cookie 中发送给客户端,下一次访问时,SessionID 会再次发送给服务端,在服务端进行识别不同用户
    • Session 的实现依赖于 Cookie,若 Cookie 被禁用,那么 Session 也将失效。

10.跨站攻击

CSRF (Cross-Site request forgery)- 伪造请求,冒充用户在站内的正常操作。

如何防止 CSRF 跨站攻击:

  • 关键操作只接受 POST 请求
  • 验证码
    • 使用验证码,那么每次操作都需要用户进行互动,从而简单有效的防御了CSRF攻击。但是如果你在一个网站作出任何举动都要输入验证码会严重影响用户体验,所以验证码一般只出现在特殊操作里面,或者在注册时候使用。
  • 检测 Referer
    • 比如你在论坛留言,那么不管你留言后重定向到哪里去了,之前的那个网址一定会包含留言的输入框,这个之前的网址就会保留在新页面头文件的 Referer 中, 通过检查 Referer 的值,我们就可以判断这个请求是合法的还是非法的,但是问题出在服务器不是任何时候都能接受到 Referer 的值,所以 Referer Check 一般用于监控 CSRF 攻击的发生,而不用来抵御攻击。
  • Token
    • 对参数进行加密预防 CSRF 攻击。
    • 添加一个新参数 Token,不知道 Token 是无法构造出合法的请求进行攻击的。
    • Token 使用时机:
      • Token 要足够随机
      • Token 是一次性的,即每次请求成功后都要更新 Token
      • Token 要注意保密性

XSS (Cross Site Scripting,跨站脚本攻击) – 是注入攻击的一种

如果防御 XSS:

将用户的输入使用 HTML 解析库进行解析,获取其中的数据。然后根据用户原有的标签属性,重新构建 HTML 元素树。构建的过程中,所有的标签、属性都只从白名单中拿取。

Part.2 – HTTP over SSL/TLS

1. HTTPS 基本过程

HTTPS 即 HTTP over SSL/TLS ,是一种在加密通道进行 HTTP 内容传输的协议。

TLS 基本过程:

ClientHello
ServerHello
finished
finished

TLS 的完整过程需要三个算法(协议),密钥交互算法、对称加密算法、消息认证算法

2. TLS 证书机制

HTTPS 过程中有一个很重要的步骤,就是服务器要有 CA 证书机构颁发的证书,客户端根据自己信任的 CA 列表验证服务器的身份。

现代浏览器中,证书的验证过程依赖于证书信任链:即一个证书需要依靠上一个证书来证明自己的可信的,最顶层的证书是根证书,拥有根证书的机构被称为 根 CA(一般操作系统自带)。

3. 中间人攻击

所谓中间人攻击,指攻击者与通信的两端都建立独立的联系,并交换其所接受到的数据,使通信的双方都认为他们正在通过私密的连接直接与对方进行通话,事实上整个会话都会被攻击者完全控制。在中间人攻击中,攻击者可以拦截双方的通信并插入新的内容。

SSL 剥离 问题

SSL 剥离即阻止用户使用 HTTPS 访问网站。由于并不是所有网站都只支持 HTTPS,大部分网站会同时支持 HTTP 和 HTTPS 两种协议。用户在访问网站时,也可能会在地址栏中输入 http:// 的地址,第一次的访问完全是明文的,这就给了攻击者可乘之机。通过攻击 DNS 响应,攻击者可以将自己变成中间人。

HSTS

用于强制浏览器使用 HTTPS 访问网站的一种机制。它的基本机制是在服务器返回的响应中,加上一个特殊的头部,指示浏览器对于此网站,强制使用 HTTPS 进行访问。

HSTS 有一个很明显的缺点,是需要等待第一个服务器的影响中的头部才能生效,但如果第一次访问该网站就被攻击呢?为了解决这个问题,浏览器中会带上一些网站的域名,被称为 HSTS preload list。对于在这个 list 的网站来说,直接强制使用 HTTPS。

伪造证书攻击

HSTS 只解决了 SSL 剥离的问题,然而即使在全程使用 HTTPS 的情况下,我们仍然有可能被监听。

第一步是需要攻击 DNS 服务器。第二步是攻击者自己的证书需要被用户信任,这一步对于用户来说是很难控制的,需要证书颁发机构能够控制自己不滥发证书。

HPKP

HPKP 技术是为了解决伪造证书攻击而诞生的。

HPKP(Public Key Pinning Extension for HTTP)在 HSTS 上更进一步,HPKP 直接在返回头中存储服务器的公钥指纹信息,一旦发现指纹和实际接受到的公钥有差异,浏览器就可以认为正在被攻击。

和 HSTS 类似,HPKP 也依赖于服务器的头部返回,不能解决第一次访问的问题,浏览器本身也会内置一些 HPKP 列表。

Part.3 – TCP 协议

1. TCP 的特征

  • TCP 提供一种面向连接的、可靠的字节流服务。
  • 在一个 TCP 连接中,仅有两方进行彼此通信,广播和多播不能用于 TCP。
  • TCP 使用校验、确认和重传机制来保证可靠传输。
  • TCP 给数据分节进行排序,并使用累积确认和保证数据的顺序不变和非重复。
  • TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制

注意:TCP 并不能保证数据一定会被对方接收到,因为这是不可能的。TCP 能做到的是:如果有可能就把数据递送给对方,否则就通知用户(使用放弃重传并中断连接这一方式实现)。因此准确说 TCP 也不是 100% 可靠的协议,他所能提供的是数据的可靠递送或故障的可靠通知。

2. 三次握手与四次挥手

三次握手

所谓三次握手,是指建立一个 TCP 连接,客户端和服务端需要传送三个包。

三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息,在 socket 编程中,客户端执行 connect() 触发三次握手。

  • 第一次握手:(SYN = 1,seq = x)
    • 客户端发送一个 TCPSYN 标志位置 1 的包,指明客户端需要连接的端口和初始序号 X, 保存在包头的序列号(Sequence Number)字段里。
    • 发送完毕后,客户端进入 SYN_SEND 状态。
  • 第二次握手:(SYN = 1,ACK = 1,seq = y,ACKnum = x + 1)
    • 服务端发回确认包(ACK)应答,即 SYNACK 均为 1,服务端选择自己的 ISN 序号,放到 seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即 X+1
    • 发送完毕后,服务端进入 SYN_RCVD 状态。
  • 第三次握手:(ACK = 1,ACKnum = y + 1)
    • 客户端再次发送确认包(ACK), SYN 标志位为 0, ACK 标志位为 1,并且把服务端发送的 ACK 的序号字段 + 1。
    • 发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态, TCP 握手结束。

三次握手示意图:

四次挥手

TCP 的拆除需要发送四个包,因此称为四次挥手,也叫改进的三次握手。客户端和服务端均可主动发起挥手动作,在 socket 编程中,任意一端执行 close() 即可产生挥手操作。

  • 第一次挥手:(FIN = 1,seq = x)
    FIN
    FIN_WAIT_1
    
  • 第二次挥手:(ACK = 1,ACKnum = x + 1)
    • 服务端确认客户端的 FIN 包,并发送一个确认包,表明自己接收到了客户端关闭连接的请求,但还没有准备好关闭连接。
    • 发送完毕后,服务端进入 CLOSE_WAIT 状态,客户端接收到这个确认包后,进入 FIN_WAIT_2 状态,等待服务端关闭连接。
  • 第三次挥手:(FIN = 1,seq = y)
    • 服务端准备好关闭连接时,向客户端发送结束连接请求, FIN 置为 1。
    • 发送完毕后,服务端进入 LAST_ACK 状态,等待来自客户端的最后一个 ACK
  • 第四次挥手:(ACK = 1,ACKnum = y + 1)
    • 客户端接收到来自服务端的关闭请求,发送一个确认包,并进入 TIME_WAIT 状态。等待可能出现的要求重传的 ACK 包.
    • 服务端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
    • 客户端等待了某个固定时间(两个最大段声明周期)之后,没有收到服务器端的 ACK ,以为服务端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。

    四次挥手示意图:

3. SYN 攻击

什么是 SYN 攻击?

在三次握手过程中的第二次握手时,服务器发送 SYN_ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接。此时服务器处于 SYN_RCVD 状态,当收到 ACK 后,服务器才能转入 ESTABLISHED 状态。

SYN 攻击指的是,”攻击客户端” 在短时间内伪造大量不存在的 IP 地址,向服务端不断地发送 SYN 包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,正常的 SYN 请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。

SYN 攻击是一种典型的 Dos/DDos 攻击

如何检测 SYN 攻击?

当服务器出现大量的半连接状态时,特别是源 IP 地址是随机的,基本可以断定这是一次 SYN 攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

如何防御 SYN 攻击?

SYN 攻击不能完全被阻止,除非将 TCP 协议重新设计。可以尽可能减轻 SYN 攻击的危害:

SYN cookie

4. TCP KeepAlive

TCP 的连接,实际上是一种纯软件层面的概念,在物理层并没有“连接”这种概念。如果出现一些意外导致某端出现异常而另一端无法感知,一直维护着这个连接,长时间会导致非常多的半连接状态的 TCP 连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用 TCP 的 KeepAlive 机制来避免。

TCP KeepAlive 的基本原理:隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接。

TCP KeepAlive 的局限:首先 TCP KeepAlive 检测的方式是发送一个 probe 包,会给网络带来额外的流量,另外 TCP KeepAlive 只能再内核层级检测连接的存活与否,而连接的存活不一定代表服务可用,例如当一个服务器 CPU 占用 100% 已经卡死不能响应请求了,此时 TCP KeepAlive 依然会认为连接是存活的。因此 TCP KeepAlive 对于应用层程序的价值是相对较小的。

Part.4 – UDP 协议

UDP 是一个简单的传输层协议,和 TCP 相比, UDP 有如下几个显著的特性:

  • UDP 缺乏可靠性。 UDP 本身不提供确认序列号、超时重传等机制。 UDP 数据报可能在网络中被复制,被重新排序。即 UDP 不保证数据报会到达其最终目的地,也不保证各个数据报的先后顺序,也不保证每个数据报只到达一次。
  • UDP 数据报是有长度的。每个 UDP 数据报都有长度,如果一个数据报正确地到达目的地,那么该数据报的长度将随数据一起传递给接收方。而 TCP 是一个字节流协议,没有任何协议上的记录边界。
  • UDP 是无连接的。 UDP 客户端和服务器之间不存在长期的关系, UDP 发送数据报之前也不需要经过握手创建连接的过程
  • UDP 支持多播和广播。

Part.5 – IP 协议

IP 协议位于 TCP/IP 协议的第三层 – 网络层。与传输层协议相比,网络层的责任是提供点到点的服务,而传输层(TCP/UDP)则提供端到端的服务。

1. 网络 OSI 的七层协议

7 应用层
6 表示层
5 会话层
4 传输层
3 网络层
2 数据链路层
1 物理层

2. IP 地址的分类

  • A 类地址
  • B 类地址
  • C 类地址
  • D 类地址

3. 广播与多播

广播与多播仅用于 UDP(TCP 是面向连接的)

广播

一共有四种广播地址:

255.255.255.255

多播

又称组播,使用 D 类地址,D类地址分配的 28bit 均用作多播组号而不再表示其他

BGP

边界网关协议(BGP)是运行于 TCP 上的一种自治系统的路由协议

Part.6 – Socket 编程

1. Socket 基本概念

Socket 是对 TCP/IP 协议族的一种封装,是应用层与 TCP/IP 协议族通信的中间软件抽象层。从设计模式的角度看,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面,对用户来说,一组简单的接口就是全部,让 Socket 去组织数据,以符合指定的协议。

Socket 还可以认为是一种网络间不同计算机上的进程通信的一种方法,利用三元组(IP 地址、协议、端口)就可以唯一标识网络中的进程,网络中的进程通信可以利用这个标志与其他进程进行交互。

Socket 起源于 Unix,Unix/Linux 基本哲学之一就是:一切皆文件,都可以用“打开(open)-> 读写(write/read)-> 关闭(close)”模式来进行操作,因此 Socket 也被处理为一种特殊的文件。

2. 写一个简单的 WebServer

一个简单的 Server 的流程包括:

  1. 建立连接,接受一个客户端连接。
  2. 接受请求,从网络中读取一条 HTTP 请求报文。
  3. 处理请求,访问资源。
  4. 构建响应,创建带有 header 的 HTTP 响应报文。
  5. 发送响应,传给客户端。

大体的程序与调用的函数逻辑:

socket()
bind()
listen()
accept()
read()/write()
close()