计算机网络
在我们日常的开发中,除了对技术要有一定的专研性,我们也很有必要去学习一下网络方面的知识以及原理,这样能让我们更好的理解其通信,也让我们在网络编程中跟游刃有余。
HTTP协议
特性:
- HTTP协议是构建与TCP/IP协议之上,是一个应用层协议,默认端口号是80
- HTTP是无连接状态的
HTTP报文
请求报文
HTTP协议是以ASCII码传输,是构建于TCP/IP协议之上的应用层规范。
我们可以把一个HTTP请求分为三部分:
- 状态行
- 请求头
- 信息报文
HTTP定义了与服务器交互的不同的方法,最常见的方法有4种:GET
,POST
,’DELETE’以及’PUT’。这4种方法对应着对某个资源的查,改,删和增4个操作。
GET
与POST
的区别
GET
操作用于信息的获取,而且应该是安全的和幂等的
所谓安全就是意味着改操作只是用于获取信息而非修改信息。就比如说查询数据库一样,它只是将数据从数据库中查询出来而非修改其数据。
所谓幂等就是意味着对同一个URL的多个请求只应该返回同样的结果。
HTTP的GET
请求报文示例:
GET /books/?sex=man&name=Professional HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive
POST
操作表示可能会修改服务器上某个资源
其请求报文示例如下:
POST / HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive
sex=man&name=Professional
需要注意的是:
GET
可提交的数据量受到了URL长度的限制,HTTP协议中没有规范对URL数据长度的限制,这个限制是特定浏览器以及服务器对它的限制。- 理论上讲,
POST
是没有大小限制的,HTTP协议规范中也没有进行大小的限制,只是在服务钱软件实现的时候可能会对其有所限制。 - 通过上面两种数据报文的示例,我们可以发现其数据内容是完全一样的,只是数据内容的位置不一样,一个在URL中,一个在HTTP的包体里面。
- GET产生一个TCP数据包;POST产生两个TCP数据包。对于get请求方式,浏览器会把httpheader和data一并发送出去,服务器响应200,而对于post请求,浏览器先发送header,服务器响应100之后,浏览器再发送data,服务器响应200。
响应报文
HTTP响应与HTTP请求相似,HTTP响应也分为三部分:
- 状态行
- 响应头
- 响应体
状态行由协议版本、数字形式的状态代码以及相应的状态描述组成,各元素之间以空格隔开。
常见的状态码有如下几种:
- 200 ok,客户端请求成功
- 203 请求的文档被正常返回,但是由于正在使用的是文档副本所以某些响应头信息可能不正确,这是在http1.1中加入的新特性
- 204 在并没有新文档的情况下,204确保浏览器继续显示先前的文档。
- 301 请求永久重定向
- 302 请求临时重定向
- 304 文件未修改,可以继续使用先前的文档
- 400 客户端请求有语法错误,不能被服务器解析
- 401 请求未经授权
- 403 服务器收到请求,但是拒绝提供服务。服务器一般会在响应正文中显示拒绝的原因
- 404 请求的资源不存在
- 500 服务器发生不可预期的错误,导致客户端无法完成请求
- 503 服务器当前不能处理客户端的请求,在一段时间之后服务器可能会恢复正常。
下面是一个HTTP响应的例子:
HTTP/1.1 200 OK
Server:Apache Tomcat/5.0.12
Date:Mon,6Oct2003 13:23:42 GMT
Content-Length:112
<html>...
条件GET
HTTP条件GET是HTTP协议为了减少不必要的带宽浪费而提出的一种方案。
- 条件get的使用时机
客户端之前已经访问过该网站,并打算再次访问该网站。
- HTTP条件get的使用方法
客户端向服务器发送一个包询问是否在上一次访问网站的时间后更改了页面,如果服务器没有更改页面,显然是不需要把所有页面都传送给客户端,客户端只需要使用本地缓存即可,如果服务器对照客户端给出的时间已经更新了页面,则发送更新后的页面给客户端。
下面是一个具体的发送接收报文的示例:
客户端发送请求:
GET / HTTP/1.1
Host: www.sina.com.cn:80
If-Modified-Since:Thu, 4 Feb 2010 20:39:13 GMT
Connection: Close
第一次请求时,服务端返回请求数据,之后的数据,服务器根据请求中的If-Modified-Since
字段判断响应文件是和更新,如果没有更新,服务器会返回一个304状态码,告诉浏览器文档未修改,可以继续使用缓存中的文档。
HTTP/1.0 304 Not Modified
Date: Thu, 04 Feb 2010 12:38:41 GMT
Content-Type: text/html
Expires: Thu, 04 Feb 2010 12:39:41 GMT
Last-Modified: Thu, 04 Feb 2010 12:29:04 GMT
Age: 28
X-Cache: HIT from sy32-21.sina.com.cn
Connection: close
如果文档有更新,那么就返回更新后的文档。
持久连接
HTTP协议采用”请求-应答”模式,当使用普通模式的时候,即非Keep-Alive模式时候,每个“请求-应答”都要新建一次连接,完成之后又立即断开连接(因为HTTP协议是无连接协议)当使用Keep-Alive模式之后,Keep-Alive功能会使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive避免了连接的建立。
在 HTTP 1.0 版本中,并没有官方的标准来规定 Keep-Alive 如何工作,因此实际上它是被附加到 HTTP 1.0协议上,如果客户端浏览器支持 Keep-Alive ,那么就在HTTP请求头中添加一个字段 Connection: Keep-Alive,当服务器收到附带有 Connection: Keep-Alive 的请求时,它也会在响应头中添加一个同样的字段来使用 Keep-Alive 。这样一来,客户端和服务器之间的HTTP连接就会被保持,不会断开(超过 Keep-Alive 规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接。
在 HTTP 1.1 版本中,默认情况下所有连接都被保持,如果加入 “Connection: close” 才关闭。目前大部分浏览器都使用 HTTP 1.1 协议,也就是说默认都会发起 Keep-Alive 的连接请求了,所以是否能完成一个完整的 Keep-Alive 连接就看服务器设置情况。
需要注意的是:
-
HTTP Keep-Alive 简单说就是保持当前的TCP连接,避免了重新建立连接。
-
HTTP 长连接不可能一直保持,例如 Keep-Alive: timeout=5, max=100,表示这个TCP通道可以保持5秒,max=100,表示这个长连接最多接收100次请求就断开。
-
HTTP 是一个无状态协议,这意味着每个请求都是独立的,Keep-Alive 没能改变这个结果。另外,Keep-Alive也不能保证客户端和服务器之间的连接一定是活跃的,在 HTTP1.1 版本中也如此。唯一能保证的就是当连接被关闭时你能得到一个通知,所以不应该让程序依赖于 Keep-Alive 的保持连接特性,否则会有意想不到的后果。
-
使用长连接之后,客户端、服务端怎么知道本次传输结束呢?两部分:1. 判断传输数据是否达到了Content-Length 指示的大小;2. 动态生成的文件没有 Content-Length ,它是分块传输(chunked),这时候就要根据 chunked 编码来判断,chunked 编码的数据在最后有一个空 chunked 块,表明本次传输数据结束,详见这里。什么是 chunked 分块传输呢?下面我们就来介绍一下。
会话跟踪
- 什么是会话
会话是指客户端打开与服务端的连接并发出请求到服务端响应客户端的请求的全过程称之为会话。
- 什么是会话跟踪
会话跟踪指的是对同一个用户对服务器所发出的连续的请求和接受响应的监视。
- 为什么需要会话跟踪
浏览器与服务器之间是通过HTTP协议进行通信的,而HTTP协议是无连接状态的,不能保证客户的信息,因为每一次通信都需要建立一次连接状态,通信完成后就需要断开,下一次通信又需要重新建立连接。这样就需要判断每次的请求是否为同一个用户,所以会话跟踪技术才开始出现。
- 会话跟踪的实现方式
URL重写
: URL是网页上特定资源的地址,而URL重写的技术就是在URL后面添加一个附加数据用来标示该会话,把会话ID通过URL的信息传过去,以便在服务端判断不同的用户。
表单隐藏域
: 将会话ID添加到表单元素中提交到服务器,此表单元素并不在客户端显示。
cookie
: cookie是Web服务器发送给客户端的一小段信息,客户端请求时可以读取该信息并发送到服务端,从而对用户进行识别。对于客户端的每次请求,服务器都会将cookie发送到客户端让客户端进行保存,以便下次使用。
客户端可以采用两种方式保存cookie对象,一种方式是保存在客户端内存中,称为临时cookie,浏览器关闭之后这个cooke=ie对象消失。另外一种方式是保存在客户端的磁盘上,这样的保存称为永久cookie。以后客户端只要访问该网站,那么就会将这个cookie发送到服务端,前提是这个cookie在有效期内,这样就实现是了对客户端的跟踪。
cookie是可以被客户端禁用的。
session
: 每一个用户都有一个session,各个用户之间是不能共享session的,是每个用户所独享的,在session中可以存放信息。
在服务器端会创建一个session对象,产生一个session id用来标示这个session对象,然后将这个session id放入到cookie中发送到客户端,下一次访问时,session id会发送到服务端,在服务端进行识别不同的用户。
session的实现依赖于cookie,如果cookie被禁用,那么session也将会失效。
跨站攻击
- CSRF(Cross-site request forgery,跨站请求伪造)
伪造请求,冒充用户在站内正常工作。
例如,一论坛网站的发贴是通过 GET 请求访问,点击发贴之后 JS 把发贴内容拼接成目标 URL 并访问:
http://example.com/bbs/create_post.php?title=标题&content=内容
那么,我们只需要在论坛中发一帖,包含一链接:
http://example.com/bbs/create_post.php?title=我是脑残&content=哈哈
只要有用户点击了这个链接,那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧,但是既然发贴的请求可以伪造,那么删帖、转帐、改密码、发邮件全都可以伪造。
如何防范 CSRF 攻击? 可以注意以下几点:
-
关键操作只接受POST请求
-
验证码 CSRF 攻击的过程,往往是在用户不知情的情况下构造网络请求。所以如果使用验证码,那么每次操作都需要用户进行互动,从而简单有效的防御了CSRF攻击。但是如果你在一个网站作出任何举动都要输入验证码会严重影响用户体验,所以验证码一般只出现在特殊操作里面,或者在注册时候使用。
-
Token
目前主流的做法是使用 Token 抵御 CSRF 攻击。下面通过分析 CSRF 攻击来理解为什么 Token 能够有效
CSRF 攻击要成功的条件在于攻击者能够预测所有的参数从而构造出合法的请求。所以根据不可预测性原则,我们可以对参数进行加密从而防止 CSRF 攻击。
另一个更通用的做法是保持原有参数不变,另外添加一个参数 Token,其值是随机的。这样攻击者因为不知道 Token 而无法构造出合法的请求进行攻击。
Token 使用原则:
-
Token 要足够随机————只有这样才算不可预测
-
Token 是一次性的,即每次请求成功后要更新Token————这样可以增加攻击难度,增加预测难度
-
Token 要注意保密性————敏感操作使用 post,防止 Token 出现在 URL 中
注意:过滤用户输入的内容不能阻挡 csrf,我们需要做的是过滤请求的来源。
- 检测referer
常见的互联网页面与页面之间是存在联系的,比如你在 www.baidu.com 应该是找不到通往 www.google.com 的链接的,再比如你在论坛留言,那么不管你留言后重定向到哪里去了,之前的那个网址一定会包含留言的输入框,这个之前的网址就会保留在新页面头文件的 Referer 中,通过检查 Referer 的值,我们就可以判断这个请求是合法的还是非法的,但是问题出在服务器不是任何时候都能接受到 Referer 的值,所以 Referer Check 一般用于监控 CSRF 攻击的发生,而不用来抵御攻击。
- XSS(Cross Site Scripting)
XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。
XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF。
如何防御 XSS 攻击?
理论上,所有可输入的地方没有对输入数据进行处理的话,都会存在 XSS 漏洞,漏洞的危害取决于攻击代码的威力,攻击代码也不局限于 script。防御 XSS 攻击最简单直接的方法,就是过滤用户的输入。
如果不需要用户输入 HTML,可以直接对用户的输入进行 HTML escape 。下面一小段脚本:
<script>window.location.href=”http://www.baidu.com”;</script>
经过 escape 之后就成了:
<script>window.location.href="http://www.baidu.com"</script>
它现在会像普通文本一样显示出来,变得无毒无害,不能执行了。
当我们需要用户输入 HTML 的时候,需要对用户输入的内容做更加小心细致的处理。仅仅粗暴地去掉 script 标签是没有用的,任何一个合法 HTML 标签都可以添加 onclick 一类的事件属性来执行 JavaScript。更好的方法可能是,将用户的输入使用 HTML 解析库进行解析,获取其中的数据。然后根据用户原有的标签属性,重新构建 HTML 元素树。构建的过程中,所有的标签、属性都只从白名单中拿取。
HTTPS协议
HTTPS协议即http over TLS,是一种在加密信道中进行HTTP内容传输的协议。
TLS基本过程如下:
- 客户端发送一个
ClientHello
消息到服务端,消息中同时包含了它的(Transport Layer Security)TLS版本,可用的加密算法以及压缩算法。 - 服务端接受到客户端发送的消息之后,也会返回一个
ServerHello
的消息,消息中包含了服务端的TLS版本,服务器所选择的加密算法以及压缩算法,以及数字证书认证机构(Certificate Authority,缩写 CA)签发的服务器公开证书,证书中包含了公钥。客户端会使用这个公钥加密后面的握手过程,直到协商生成了一个新的对称密钥。证书中还包含了该证书所应用的域名范围(Common Name,简称 CN),用于客户端验证身份。 - 客户端根据自己的信任CA列表,验证服务器端的证书是否可信,如果认为可行,客户端会生成一段伪随机数,然后利用服务器的公钥加密它。这串随机数会被用于生成新的对称密钥。
- 服务器端使用自己的私钥解密上面提到的随机数,然后使用这段随机数生成自己的对称主密钥。
- 客户端发送一个
finished
消息给服务端,使用对称对称密钥加密这次通讯的一个散列值。 - 服务器端生成自己的hash值,然后解密客户端发送来信息,检查这两个值是否相等,如果相等,就发送一个
finished
消息,也使用协商好的对称密钥进行加密。 - 从现在开始,接下来整个 TLS 会话都使用对称秘钥进行加密,传输应用层(HTTP)内容。
从上面的过程我们可以看出,TLS的完成过程需要三个算法(协议),密钥交互算法,对称加密算法和消息验证算法。
TLS 证书机制
HTTPS 过程中很重要的一个步骤,是服务器需要有 CA 颁发的证书,客户端根据自己的信任 CA 列表验证服务器的身份。现代浏览器中,证书验证的过程依赖于证书信任链。
所谓证书信任链,即一个证书要依靠上一级证书来证明自己是可信的,最顶层的证书被称为根证书,拥有根证书的机构被称为根 CA。
以 Github 为例,在浏览器中我们可以看到它的证书信任链如下:
DigiCert High Assurance EV Root CA -> DigiCert SHA2 Extended Validation Server CA -> Github.com
从上到下即 Root CA -> 二级 CA -> 网站。
前面提到,证书当中包括 CN(Common Name),浏览器在验证证书的同时,也会验证 CN 的正确性。即不光需要验证“这是一个合法的证书”,还需要验证“这是一个用于 Github.com 的证书”。
既然所有的信任,最终要落到根 CA 上,根证书本身又是怎么获得的呢?答案也很简单,根证书一般是操作系统自带的。不管是桌面系统 Windows,macOS 还是移动端系统 Android, iOS 都会内置一系列根证书。随着操作系统本身的升级,根证书也会随着升级进行更新。
对浏览器而已,浏览器当然也有选择信任某个根证书的权利。Chrome 浏览器一般是跟随系统根证书信任的。Firefox 浏览器通常是使用自带的一套证书信任机制,不受系统证书的影响。
在使用 curl 等工具时,我们还可以自行选择证书进行信任。
中间人攻击
HTTPS 的过程并不是密不透风的,HTTPS 有若干漏洞,给中间人攻击(Man In The Middle Attack,简称 MITM)提供了可能。所谓中间人攻击,指攻击者与通讯的两端分别建立独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。
SSL 剥离
SSL 剥离即阻止用户使用 HTTPS 访问网站。由于并不是所有网站都只支持 HTTPS,大部分网站会同时支持 HTTP 和 HTTPS 两种协议。用户在访问网站时,也可能会在地址栏中输入 http:// 的地址,第一次的访问完全是明文的,这就给了攻击者可乘之机。通过攻击 DNS 响应,攻击者可以将自己变成中间人。
DNS 作为基于 UDP 的协议是相当不安全的,为了保证 DNS 的安全可以使用 DNS over TCP 等机制,这里不赘述了.
HSTS
为了防止上面说的这种情况,一种叫做 HSTS 的技术被引入了。HSTS(HTTP Strict Transport Security)是用于强制浏览器使用 HTTPS 访问网站的一种机制。它的基本机制是在服务器返回的响应中,加上一个特殊的头部,指示浏览器对于此网站,强制使用 HTTPS 进行访问:
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
可以看到如果这个过期时间非常长,就是导致在很长一段时间内,浏览器都会强制使用 HTTPS 访问该网站。
HSTS 有一个很明显的缺点,是需要等待第一个服务器的影响中的头部才能生效,但如果第一次访问该网站就被攻击呢?为了解决这个问题,浏览器中会带上一些网站的域名,被称为 HSTS preload list。对于在这个 list 的网站来说,直接强制使用 HTTPS。
伪造证书攻击
HSTS 只解决了 SSL 剥离的问题,然而即使在全程使用 HTTPS 的情况下,我们仍然有可能被监听。
假设我们想访问 www.google.com, 但我们的 DNS 服务器被攻击了,指向的 IP 地址并非 Google 的服务器,而是攻击者的 IP。当攻击者的服务器也有合法的证书的时候,我们的浏览器就会认为对方是 Google 服务器,从而信任对方。这样,攻击者便可以监听我们和谷歌之前的所有通信了。
可以看到攻击者有两步需要操作,第一步是需要攻击 DNS 服务器。第二步是攻击者自己的证书需要被用户信任,这一步对于用户来说是很难控制的,需要证书颁发机构能够控制自己不滥发证书。
HPKP
HPKP 技术是为了解决伪造证书攻击而诞生的。
HPKP(Public Key Pinning Extension for HTTP)在 HSTS 上更进一步,HPKP 直接在返回头中存储服务器的公钥指纹信息,一旦发现指纹和实际接受到的公钥有差异,浏览器就可以认为正在被攻击:
Public-Key-Pins: pin-sha256="base64=="; max-age=expireTime [; includeSubDomains][; report-uri="reportURI"]
和 HSTS 类似,HPKP 也依赖于服务器的头部返回,不能解决第一次访问的问题,浏览器本身也会内置一些 HPKP 列表。
TCP 协议
特性:
- TCP提供面向连接的、可靠的字节流服务。
- 在一个TCP连接里面,仅有两方进行通信。不能用于多播或者广播。
- TCP使用校验,确认和重传机制来保证数据的可靠性。
- TCP给数据分节进行排序,并使用累计确认保证数据的顺序性和不变性。
- TCP使用滑动窗口机制来实现流量控制,通过动态窗口的大小进行拥塞控制。
注意 :TCP 并不能保证数据一定会被对方接收到,因为这是不可能的。TCP 能够做到的是,如果有可能,就把数据递送到接收方,否则就(通过放弃重传并且中断连接这一手段)通知用户。因此准确说 TCP 也不是 100% 可靠的协议,它所能提供的是数据的可靠递送或故障的可靠通知。
三次握手与四次挥手
所谓TCP的三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务端总共发3次包。
三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。在socket编程中,当客户端执行connect()
时。将促发三次握手
- 第一次握手(syn=1,seq=x):
客户端发送一个TCP的syn标志位置为1的包,并指明客户端打算连接的服务器端口以及初始序列号x,保存在包头的序列号(Sequence Number)字段里面。发送完毕后,客户端进入syn_send
状态。
- 第二次握手(syn=1,ack=1,seq=y,ackNum=x+1):
服务端会返回确认包(ack)应答。即syn标志位以及ack标志位均为1。服务器端会选择自己的isn序列号,放到seq域里面,同时将确认序号(Acknowledgement Number)设置为客户端isn序列号+1,即x+1,发送完毕后,服务器端进入syc_rcvd
状态。
- 第三次握手(ack=1,ackNum=y+1):
客户端再次发送确认包,syc的标志位为0,ack的标志位为1,并且把服务器发来的ack的isn序列号+1,放在确认字段中发送给对方。发送完毕后,客户端进入established
状态,当服务器端也接受到这个包之后,也进入established
状态,TCP握手结束。
三次握手的示意图如下:
TCP的连接拆除是指需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端和服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()
操作即可产生挥手操作。
- 第一次挥手(fin=1,seq=x):
假设客户端想要关闭连接,客户端会发送一个fin标志位置为1的包,表示自己已经没用任何数据可以发送,但是仍然可以接受数据。发送完毕后,客户端进入fin_wait_1
状态。
- 第二次握手(ack=1,ackNum=x+1):
服务器端确认客户端的fin包,并返回一个确认包,表明自己已经接受到了客户端关闭连接的请求,但还没有准备好关闭连接。发送完毕后,服务端进入close_wait
状态,客户端接受到这个确认包之后,进入fin_wait_2
状态,等待服务端关闭连接。
- 第三次挥手(fin=1,seq=y):
服务器端准备好关闭连接时,向客户端发送关闭连接请求,fin标志位置为1。发送完毕后,服务器端进入last_act
状态,等待来自客户端的最后一个ack。
- 第四次挥手(ack=1,ackNum=y+1):
客户端接受来自服务器端的连接关闭请求,发送一个确认包,并进入time_wait
状态,等待可能出现的要求重传的ack包。
服务器端接受到这个确认包之后,关闭连接,进入closed
状态。
客户端等待了某个固定的时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ack ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入closed
状态。
四次挥挥的示意图如下:
SYN攻击
什么是 SYN 攻击(SYN Flood)?
在三次握手过程中,服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后,服务器才能转入 ESTABLISHED 状态.
SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。 SYN 攻击是一种典型的 DoS/DDoS 攻击。
如何检测 SYN 攻击?
检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。
如何防御 SYN 攻击?
SYN攻击不能完全被阻止,除非将TCP协议重新设计。我们所做的是尽可能的减轻SYN攻击的危害,常见的防御 SYN 攻击的方法有如下几种:
- 缩短超时(SYN Timeout)时间
- 增加最大半连接数
- 过滤网关防护
- SYN cookies技术
TCP KeepAlive
TCP 的连接,实际上是一种纯软件层面的概念,在物理层面并没有“连接”这种概念。TCP 通信双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会。在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启等各种意外,当这些意外发生之后,这些 TCP 连接并未来得及正常释放,在软件层面上,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用 TCP 的 KeepAlive 机制实现来实现。主流的操作系统基本都在内核里支持了这个特性。
TCP KeepAlive 的基本原理是,隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接。
TCP-Keepalive-HOWTO 有对 TCP KeepAlive 特性的详细介绍,有兴趣的同学可以参考。这里主要说一下,TCP KeepAlive 的局限。首先 TCP KeepAlive 监测的方式是发送一个 probe 包,会给网络带来额外的流量,另外 TCP KeepAlive 只能在内核层级监测连接的存活与否,而连接的存活不一定代表服务的可用。例如当一个服务器 CPU 进程服务器占用达到 100%,已经卡死不能响应请求了,此时 TCP KeepAlive 依然会认为连接是存活的。因此 TCP KeepAlive 对于应用层程序的价值是相对较小的。需要做连接保活的应用层程序,例如 QQ,往往会在应用层实现自己的心跳功能。
UDP协议
UDP 是一个简单的传输层协议。和 TCP 相比,UDP 有下面几个显著特性:
- UDP 缺乏可靠性。UDP本身不提供确认,序列号,超时重传等机制。UDP数据报可能在网络中被复制,被重新排序。即UDP不保证数据报会到达其最终目的地,也不保证各个数据报的先后顺序,也不保证每个数据报只到达一次
- UDP 数据报是有长度的。每个UDP数据报都有长度,如果一个数据报正确地到达目的地,那么该数据报的长度将随数据一起传递给接收方。而 TCP 是一个字节流协议,没有任何(协议上的)记录边界。
- UDP 是无连接的。UDP 客户和服务器之前不必存在长期的关系。UDP 发送数据报之前也不需要经过握手创建连接的过程。
- UDP 支持多播和广播。
IP协议
IP协议位于TCP/IP协议的第三层——网络层。与传输层协议相比,网络层的责任是提供点到点(hop by hop)的服务,而传输层(TCP/UDP)则提供端到端(end to end)的服务。