最近在写一个提供文本记录和文件断点续传的Web
程序,因为缺乏经验,在刚刚上线测试的时候被几个朋友的XSS
和伪造Cookie
给锤爆了。在修补程序的过程中对中间件有了一定的了解,并上网查询了一些关于Cookie
、Token
和Session
的知识,在这里做一些记录,但一切的一切都要记住一句话:永远不要相信用户输入。
基本安全常识
在开发的过程中要提防着一些常见的Web
安全漏洞,这样才能够对数据提供足够的保护,如果不想让服务器生活在水深火热之中的话。
跨站脚本攻击
跨站脚本攻击(Cross Site Script)
因为和层叠样式表的缩写重合,所以一般叫做XSS
攻击。它的原理主要是恶意攻击者向网页中插入可以执行的脚本语言,以达到攻击目的。
类型 | 方式 |
---|---|
非持久性XSS |
主要通过诱骗用户点击注入攻击脚本的URL 来发送请求盗取信息,不经过服务器储存所以只是单次使用 |
持久性XSS |
可持续性的XSS 攻击的危害性更大,攻击脚本会进入到后端数据库中,之后每次网页渲染的时候都会执行 |
解决办法
- 字符转义
对于XSS
攻击最简单有效的方法就是进行字符转义,这样使得脚本中的大部分符号只能算作真正的字符而失去其在脚本语言中的意义,从而让脚本失效不能执行。对于持久性XSS
攻击则让后端也进行字符转义,这是一种通用并且大部分时间有效的方法。
- 保证数据来源
保证前端数据的来源一定来自后端数据库或者不可更改,并且尽量不要使用innerHTML
和write
等对DOM
进行字符串操作的语句,这在前端中实现比较困难。
SQL 注入
这是一种伴随着SQL
数据库语言产生的攻击方法,通常在表单中提交的字符串常常作为条件拼接在后端数据库的操作语句中,但是SQL
本身也是字符串执行的语言代码。这种拼接可以让攻击者有机会破坏原有的语句来实现,破坏数据库是会产生很严重的后果的。
-- Usual sql query word.
SELECT * FROM user WHERE email = '.....@...com' AND password = '........'
-- The sql injection.
SELECT * FROM user WHERE email = '.....@...com'-- AND password = '........'
可以看到的是当遇到注入攻击的时候,正确的查询语句的语义被改变为不检查用户密码的无效语句,一个简单的用户登录查询就被这么破解掉了。
预防注入攻击
- 字符转义
这和上面XSS
的预防方法一样,通常能够影响语句语义的是一些特殊的符号比如'
、\
和-
,如果经过转义那么就失去了它的语法意义。
- 提前检查输入
这常常需要很丰富的攻防经验,需要知道那些语句不合法或者为空,可能有危害性则直接过滤。对于新手来说攻击可能防不胜防,采用这种方法防御的类型比较受编码者水平的限制。
- 不解析
比如MongoDB
这种NoSQL
数据库选择不解析结构化文本来,从来不将用户输入是做查询语句中的语法构成部分,这样避免了注入。但是矛和盾的斗争从古至今从未停止,它依然无法避免其他形式的注入攻击。
DDoS 攻击
在网络层实行的流量攻击在理论上是无法防御的,因为这是一种所谓的笨方法,但也是最普遍有效的方法。Dos
攻击能够攻击理论上所有涉及到网络连接的程序,如果攻击者使用被劫持的大量计算机进行攻击则效果更显著。面对这种比较无赖的攻击,常常有下面几种普遍处理方式。
- 对相同来源的访问进行频率控制,进行流量的清洗
- 限制请求者对本地资源访问权限,防止文件的暴露
- 均衡服务器负载,通过一些手段,减少不必要的暴露端口
- 联系警察,这也是法律保护下的无奈之举
请求鉴权
对涉及到后端数据的请求我们应该进行访问权限的鉴定,比如文件云盘的文件上传不能让所有没有账号不知来历的用户上传,这时候就需要权限的鉴定。主要就是Cookie
,Token
和Session
,这三者的概念容易弄混。
Cookie
因为HTTP
协议是一种无状态的传输协议,服务端在处理完请求之后不会保留信息。那么Cookie
作为一种存在于浏览器本地的凭证就可以保存一些上次和服务器通信的信息。
// get cookie from this request
cookie, err := c.Cookie("auth");
Cookie
一般直接包含在请求头中,或者在URL
中。这种验证方式很简单,就是一个普通的令牌。同样的它很方便使用,也很方便伪造(当时登录验证就被绕过了),所以就有了以下两种方式的验证配合使用。
Session
HTTP
协议是一种无状态的协议,下次连接时不会和上一次有关联关系,为了解决这个问题需要在服务端采取措施即Session
。它是一种保存在服务端的令牌,供下次请求的时候使用,这样就可以在两次连接之间建立联系。
它的唯一标识符是SessionID
,服务端通过这个号码对应的上次请求保存的相关的令牌数据。这里会有一个问题就是无法在分布式服务器之间共享令牌。这时候可以使用Session
缓存中间件来专门存储之后分发给各个服务器,也对它进行复制。
Token
Token
可以理解为加强版的Cookie
,它支持跨站访问并且有更强的编码规则不容易伪造。通常生成一个令牌需要绑定提交的数据,当前系统时间和加密密钥之类的东西,通过指定编码方法生成。
// generates the token
claims := Claims{
user,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
Issuer: TokenIssuer,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
从获得的Token
中获取其中加密的信息是生成过程的逆过程,封装和拆包的过程相似,一般由服务器完成这个操作。