网络安全是用于保护关键系统和敏感信息免遭数字攻击的实践。本文例举了常见的网络安全问题及在前后端防范的方法。
XSS(跨站脚本攻击)
Cross-Site Scripting
(跨站脚本攻击)简称XSS
,是一种代码注入攻击。攻击者通过在目标网站上注入脚本,让它运行在用户浏览器上,利用这些脚本可以获取用户的信息例如cookie
。XSS
的本质就是恶意代码没有经过处理,跟网站正常代码混在一起。下面说说常见的XSS
类别以及如何防护。
存储型 XSS
存储型是指攻击者将恶意代码提交到服务器的数据库中,当普通用户正常打开网站浏览时,服务器从数据库中取出并拼接到HTML
中返回给浏览器,这时就加载到了恶意代码。这类攻击主要是由于未转译用户保存数据而直接显示引起的。
用户提交信息
<img src="xxx" onerror="alert('XSS Attack')">
服务端使用模板
<div id="comment"><%= comment.body %></div>
或前端使用数据
const $comment = document.querySelector("#comment");
$comment.innerHTML = comment.body;
存储型防护
首先我们需要明确是否需要 html 片段,如果可能的话将他们当做文本来使用。比如在模板中使用<%-
而不是<%=
(示例说明,上方是 ejs,根据不同模板而定) ;在客户端中尽可能使用textContent
而不是innerHTML
。
如果确实需要 html 片段来显示则需要特别注意,因为富文本是最容易受到攻击的。我们可以为这个富文本建立一个白名单,滤出允许出现的标签和属性然后再使用它。可以借助xss
库来完成这件事、
注意:在 HTML5 之后,innerHTML 不会执行 script 脚本。
npm install xss
import filterXSS from "xss";
const config = {
whiteList: {
span: [],
p: [],
div: [],
h1: [],
h2: [],
h3: [],
img: ['alt', 'src'],
a: ['title', 'href'],
},
// 过滤所有非白名单的标签保留内容
stripIgnoreTag: true
// 过滤script标签并丢弃内容
stripIgnoreTagBody: ["script"]
}
// 客户端处理
$comment.innerHTML = filterXSS(comment.body, config);
// 服务端处理
ejs.renderFile(
filename,
{ ...comment, body: filterXSS(comment.body) },
function(err, str){
if (err) {
console.log(err);
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.write(str);
res.end();
}
}
);
反射型 XSS
反射型是指攻击者创建出特出的URL
,而开发人员未对URL
进行处理,直接将URL
参数进行拼接HTML
使用,当用户打开这个URL
时就加载到了恶意代码。这类攻击主要是由于未转译URL
数据而直接使用引起的。
用户提交信息
<img src="xxx" onerror="alert('XSS Attack')">
服务端使用模板
<input placeholder="搜索" id="search">
<div class="result">
<h2>
搜索关键词:
<span id="keyword"><%= getParameter("keyword") %></span>
</h2>
</div>
或客户端使用数据
const params = Object.fromEntries(new URLSearchParams(location.search));
const $keyword = document.querySelector("#keyword");
$search.innerHTML = params.keyword;
这里处理方式与存储型一样不在赘述。
DOM 型 XSS
DOM
型是指前端JavaScript
代码不严谨,将数据直接设置为DOM
属性,而有些属性字符串是能作为代码运行。比如比较常见的就是a
标签的href
属性,还有一些内联事件处理器如onload
、onerror
、onclick
等等。
用户提交信息
javascript:alert('XSS Attack')
客户端使用数据
const $recom = document.querySelect("a#recom");
$recom.setAttribute("href", data.recom);
DOM 型防护
这类攻击我们需要将属性转译,或者忽略掉恶意代码,可以通过检测是否是正确资源链接,如果不是则删除。为了防止漏掉某项,还是建议找第三方成熟库来做这件事,比如我们可以通过xss
库的内置方法safeAttrValue
来做这件事。
import filterXSS from "xss";
const link = filterXSS.safeAttrValue("a", "href", data.recom);
$recom.setAttribute("href", link);
CSP 策略
"网页安全政策"(Content Security Policy,缩写 CSP)是厂商们推出的防护XSS
的策略。本质上就是白名单制度,告诉浏览器哪些资源可加载,可执行,不过这个白名单的实现和执行是在浏览器完成的。我们只需要在http
响应头中配置Content-Security-Policy
字段,在这个字段中配置。
CSRF(跨站请求伪造)
Cross-site request forgery
(跨站请求伪造)简称CSRF
,是一种利用用户凭证,进而伪装成用户执行非本意的操作的攻击方法。典型的CSRF
攻击就是用户登录了 A 网站,然后打开了 B 网站,B 网站往 A 网站发送请求,由于 A 网站未做防护,接受了这个请求通过凭证认证用户并处理。
CSRF
有攻击在第三方网站执行,攻击利用用户的凭证数据,无法直接窃取凭证而是伪装等特点。我们可以针对这个特点指定防护。
阻止外域访问
既然CSRF
是来自第三方网站的,那我们在在请求中拦截不安全域名不是就可以了。在http
协议中可以使用origin和referer请求头可以获取请求来源域名。
origin | referer | |
---|---|---|
描述 | 指示了请求来自于哪个站点 | 当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面(如搜索引擎)里的链接进入的 |
携带值 | 服务器名称,并不包含任何路径信息。 | 服务器名称,并不包含任何路径信息。 |
发送 | CORS (跨域请求) 请求或非GET 和HEAD 请求。 | 非http 跳转https |
特殊 | 由于同源定义不同IE11 跨站不会携带;302重定向时也不会携带 | 在html 中对于资源可以指定是否发送referer ,参见阮一峰博文 |
我们在后端拦截器中可以获取来源,如果是可信地址就放行,不可信来源则档回。
但是有些情况我们还是需要特殊处理,如果两个值都获取不到时如果没做二次确认(如下方确认处理)最好拦截认为是不可信的。
如果不是可信来源全部拦截还会造成一个问题,比如从google
一类搜索引擎进入的,referer
值就是www.google.com
也会被当成CSRF
拦截下来,那我们的页面就打不开了,我们需要为GET
请求且Accept
为text/html
一类的请求放行。但是这又造成另一个问题,如果一些接口是通过GET
携带参数来实现功能的话,这类接口也防御不了CSRF
,这也是GET为什么安全性比POST弱的其中一个原因。
阻止外域访问携带cookie
在chrome51
之后浏览器对cookie
新增了一个会影响跨站访问携带凭证的属性 SameSite,下面是这个属性的值:
Strict
,严格的,完全禁止第三方cookie
,只有当前网页URL与请求目标一致才会携带。可能造成不好的用户体验,比如从一个github
连接点击进去不会携带cookie
会被判断为未登录Lax
,稍微宽松,大多数不允许第三方cookie
,从顶级导航到目标地址的Get
(链接,预加载请求,GET表单) 除外。None
允许第三方跨站访问携带cookie
,该属性有一个要求,必须同时为cookie
设置Secure
(该值指明cookie只能在https请求中被携带) ,第三方必须是https
协议的源。
下图是这个属性的兼容性
将cookie
的SameSite
设置为Strict
就很安全了,任何跨域都不能携带cookie
,但是体验下非常不好。一般我们会设置为Lax
,但是安全性也就降低了,从导航到目标地址的Get
也会携带cookie
。所以这个方法也没解决通过GET
携带参数来实现功能的CSRF
的防御。
另外在网上绝大多数都说这个属性是不支持子域的,其实错的。这个属性是描述跨站时cookie
的携带方式,而不是跨域。也就是说只要设置好了domain
,子域也能共享cookie
,对于ajax
需要设置withCredentials
。
双重 cookie
既然GET
请求来做功能和SameSite
都无法防御,那我们索性将他们放行。然后加多一层验证,我们在服务端生成一段唯一的随机数并设置到cookie
中,前端在请求前从cookie
中获取到这个随机数,然后加入到请求体或请求path
中。后端收到请求时第二层验证验证此参数与cookie
中的是否一致。
这个防御主要是针对CSRF是利用用户的凭证数据,无法直接窃取凭证来实施的,这样我们就解决了GET
写单参数实现功能的CSRF
防御了,但是这个方式也有一定的弊端。
如果有多个子域,我们需要将这个cookie
种在主域,这样每个子域才能访问得到它,难以做到子域隔离。如果子域有一个站点存在XSS
漏洞被攻击,那攻击者就能拿到这个随机数,所有站点都有可能被CSRF
攻击。
使用 token
这种方式也是类似双重cookie
的原理,加多一层随机数验证。token
是后端通过一段唯一的字符串 (一般是随机数与时间戳组合) 加密后生成,然后将token
存储起来,并通过模板渲染嵌入到html
,或者开放更新并返回token
接口 (一般指登录接口) ,然后客户端在请求时携带上token
的参数,后端在拦截器中验证token
是否有效与是否过期,如果有效则放行,无效则拒绝。
由于CSRF
一般是在第三方进行,无法获取到token
的,只要页面没有XSS
漏洞泄露Token
,那么CSRF
攻击就无法成功。
另外在前后端分离使用token
认证来代替cookie
认证用户身份最主流的方式。原理就在于服务端可以通过token
关联用户信息,只要携带上token
就可以知道是哪个用户,而前端获取到token
后缓存到localStorage
或sessionStorage
中,并将token
设置到全局请求头中,这样一来就可以代替cookie
了,并且Storage
的数据并不会跟随请求而被携带,能有效防范CSRF
。
二次确认
在敏感接口比如转账,更改密码,注销账号等,还可以使用二次确认,比如再次输入密码或手机验证码也能防止CSRF
攻击,而且比上面几种方式更安全。
中间人攻击
在http
数据提交给TCP
层之后,会经过用户电脑、路由器、运营商、服务器,这中间每一个环节,都不是安全的。因为http
采用的是明文传输,攻击者可以通过中间环节对数据窃取、伪造、篡改这就是中间人攻击。
为了防范中间人攻击,我们需要对传输的数据进行加密,确保私密性和完整性 (防止修改) 。而https
就是干这件事的,在说明https
的工作方式之前我们先了解一些相关词汇。
相关词汇
对称加密:对称算法是内容加密的一类算法。它有一个秘钥,通过秘钥解开加密内容。
非对称加密:非对称算法是内容加密的一类算法。它有两个秘钥,公钥与私钥。公钥是公开给所有人的,私钥是私密的,只有持有者知道。通过公钥加密后的内容只有通过私钥才能解密出来。非对称算法的安全性很高,但是因为计算量庞大,比较消耗性能。非对称加密算法可逆(可解密)。
认证:认证是指不在意传输内容被看到,只需要确保内容是完整的,传输方式正确的。
数字签名:数字签名是指发送方在发送源内容的同时也发送一份对源内容进行不可逆算法加密(签名)。接收方也通过相同算法对发送内容进行加密,查看加密后的内容是否与发送方一致,如果一致则内容可信。
证书中心,CA 认证,数字证书:在一些情况下,接收方的公钥可能被偷偷替换成其他劫持者的公钥,此时劫持者就可以与接收方通话,所以就需要 CA 认证。CA 会用自己的私钥给要认证的公钥及其他一些信息进行签名,然后发送给持有者,持有者在发送数据时将 CA 认证后的数字签名发送给接收方,接收方通过 CA 给的公钥验签,验签通过认为是安全的公钥。
https
通讯的过程
- 浏览器发送起往服务器的
443
端口请求,并携带了浏览器支持的加密算法和 hash 算法 - 服务器收到请求,选择浏览器支持的加密算法和哈希算法
- 服务器将数字证书返回给浏览器,这个证书可以是可靠机构申请的,也可以是自制的
- 浏览器进入数字证书认证环境,这一部分是由浏览器的 TLS 完成的
- 首先浏览器会从内置的证书列表中找到数字证书的机构,如果找不到则会提示用户该证书不是权威机构颁发,是不可信任的,如果找到证书机构,则取出机构颁发的公钥
- 浏览器使用机构公钥对数字证书验签验签通过则拿出内容,内容包括网站的公钥、网址、证书的有效期等。浏览器会先验证证书签名的合法性(数字签名)。签名通过后在查看证书记录的网址是否与当前网址一致,查看证书是否过期。
- 浏览器生成随机数 R,并使用网站的公钥对 R 进行加密
- 浏览器将加密后的 R 发送给服务器
- 服务器用私钥解密得到 R
- 服务器以 R 为秘钥使用对称加密算法加密网页内容并传输给浏览器
- 浏览器以 R 为秘钥使用之前约定好的解密算法获取网页内容
前 5 步其实就是https
握手过程,主要是认证服务端证书的合法性。因为非对称计算量比较大, 整个通讯过程只会用到一次非对称加密算法(主要是用来保护传输客户端生成用于对称加密随机数秘钥)。后续内容的加解密都是用一开始约定好的对称加密算法进行的。
通过https
的混合加密方式 (非对称加密和对称加密结合) 大大增加了中间人的攻击成本。但是https
不是绝对安全的,下面例举一些情况
- 一些软件会提示用户本地计算机安装根证书,这时这个软件就可以使用自签证书发起中间人攻击,大致流程如下
- 在中间环节将服务器发送过来的数字证书替换成自签的证书,并缓存数字证书;
- 由于自签证书在根证书中,浏览器认证通过;
- 浏览器使用自签公钥机密内容,软件使用自签私钥解密,获取请求内容,使用服务器公钥加密发送到服务器;
- 服务器返回内容使用自签私钥进行签名,获取返回内容
- CA私钥泄漏
- 生成随机数R泄漏
虽然不是绝对安全,但是已经是在现有架构下最好的解决方案了。
完结撒花