共计 4930 个字符,预计需要花费 13 分钟才能阅读完成。
以前获取前端安全方面的知识非常零碎且大多停留在看,又或者自己在实际项目中用到了其实也不太清楚。通过这次 egg 项目实践能更加深刻的理解。egg 在框架中内置了安全插件 egg-security,提供了默认的安全实践。
HSTS
HSTS 指的是 Http Strict Transport Security,是响应头的信息,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是 HTTP。
Http 的请求明文传输过程中,信息都可以被中间人(通信运营商,代理,路由器厂商等)获取,可能造成数据泄漏,请求劫持,内容篡改等,
场景:用户直接输入域名 www.baidu.com,不输入 http 或者 https,默认是 http 访问,http 的访问会给用户返回一个 302 重定向到 https 的地址,后续的访问都是 https 传输。那么在这个 http 到 302 重定向的过程可能会被劫持篡改。
开启 hsts
在站点的响应头中设置 Strict-Transport-Security, 浏览器会将这个域名加入 Hsts 列表,下次用户早使用 http 访问这个网站,浏览器会自动发送 https 请求(但第一次访问还是 http),而不是先发送 http 再重定向到 https,避免 302 重定向 url 被篡改,进一步提高通信的安全性。
以访问 www.baidu.com 为例子测试分析
- 打开 chrome://net-internals/#hsts,现在 delete domain 中输入 www.baidu.com 删除,然后在 query hsts 中输入 www.baidu.com 出现 not found 证明缓存已经清除。
- 在浏览器直接输入 www.baidu.com,会看到先发送 http 请求然后 302 的 https 重定向
- 打开 https 的详情信息,可以看到 Strict Transport Security 响应头,max-age 表示 hsts 有效期
- 再次在浏览器中输入 www.baidu.com 会看到与第一次请求不同,这次是 307 重定向,表示浏览器做内部转换,将 http 转换为 https。
- 回到 chrome://net-internals/#hsts,query www.baidu.com 可以看到浏览器已经缓存了百度的 hsts。
egg-security 的防范
文档上说的是默认开启,但是看了下源码默认是 false,需要手动开启。
// default
hsts: {
enable: false,
maxAge: 365 * 24 * 3600,
includeSubdomains: false, // 可以添加子域名,保证所有子域名都使用 HTTPS 访问。}
CSRF
Cross Site Request Forgery,跨站域请求伪造,重点在伪造的请求,CSRF 攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点。
最经典的例子
受害者 Bob 在银行有一笔存款,通过对银行的网站发送请求
http://bank.example/withdraw?account=bob&amount=1000000&for=bob2
可以使 Bob 把 1000000 的存款转到 bob2 的账号下。通常情况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的 session,并且该 session 的用户 Bob 已经成功登陆。黑客想到使用 CSRF 的攻击方式,他先自己做一个网站,在网站中放入如下代码:src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory”
,并且通过广告等诱使 Bob 来访问他的网站。当 Bob 访问该网站时,上述 url 就会从 Bob 的浏览器发向银行,而这个请求会附带 Bob 浏览器中的 cookie 一起发向银行服务器。大多数情况下,该请求会失败,因为他要求 Bob 的认证信息。但是,如果 Bob 当时恰巧刚访问他的银行后不久,他的浏览器与银行网站之间的 session 尚未过期,浏览器的 cookie 之中含有 Bob 的认证信息,操作成功。
这个操作看起来很玄乎,其实我们经常点击一些外部信息的时候也会有这个过程,比如说我在 juejin 中点击一个原文链接跳转到 sf:
点击访问时就会带上 sf 站点的 cookie,假如攻击者将这个链接改为对用户有安全隐患的操作,sf 又没有做防范的话,csrf 攻击就成功了。(另外从图中的 referer 可以看到请求的来源。)
攻击原理
黑客通过借助受害者的 cookie 骗取服务器的信任,但是本身是没有办法获取 cookie 值的。
防范方法:
-
验证 referer 字段。
- 从上述图中可以看到服务器中是能获取到 referer 的,将所有有安全敏感的请求都加一个 referer 的过滤拦截。
- 问题:referer 是由浏览器提供的,虽然 http 有协议规定,但各个浏览器对 referer 的实现可能会有偏差,不能保证浏览器本身没有安全漏洞。把安全性依赖于第三方浏览器来保障,理论上并不可靠。旧的浏览器有些可以被篡改 referer,即使无法被篡改,用户如果担心 referer 留下用户的访问来源也可以在浏览器设置不记录 referer 信息。
-
通过 token 值校验
- token 的校验有很多方式,实际上就是拿到服务端给的一个特定值,在发送请求时把这个值带上,服务端确认是自己签发的 token。
egg-security 中 csrf 的防范:
- 在默认配置下,egg-security 会在 cookie 中设置 token 值,因为 csrf 攻击这是伪造请求,并不能实际获取 cookie 值,因此通过校验的请求才能实际发生。
- egg-security 只用 csrf 策略不保护 get, head, options and trace 四个方法,因为这四个方法被认为是安全的方法,不需要受到 CSRF 的保护,因为它们不会对应用程序进行更改,即使它们返回敏感信息,也会受到浏览器中的同源策略的保护。
- 在发送请求时 egg-security 会通过 headerName/queryName/bodyName 的字段去获取 token 值做校验,在 AJAX 请求的时候,可以从 Cookie 中取到 csrfToken(插件会把 httponly 设置为 false 让 Js 可以操作),放置到 query、body 或者 header 中发送给服务端。如:
xhr.setRequestHeader('x-csrf-token', csrftoken);
// config/config.default.js
module.exports = {
security: {
csrf: {
headerName: 'x-csrf-token', // 通过 header 传递 CSRF token 的默认字段为 x-csrf-token
queryName: '_csrf', // 通过 query 传递 CSRF token 的默认字段为 _csrf
bodyName: '_csrf', // 通过 body 传递 CSRF token 的默认字段为 _csrf
},
},
};
// egg-security csrf.js
module.exports = options => {return function csrf(ctx, next) {if (utils.checkIfIgnore(options, ctx)) {return next();
}
// 在 cookie 中设置 token 值
ctx.ensureCsrfSecret();
// ignore requests: get, head, options and trace
const method = ctx.method;
if (method === 'GET' ||
method === 'HEAD' ||
method === 'OPTIONS' ||
method === 'TRACE') {return next();
}
if (options.ignoreJSON && typeis.is(ctx.get('content-type'), 'json')) {return next();
}
const body = ctx.request.body || {};
debug('%s %s, got %j', ctx.method, ctx.url, body);
// 判断 token 是否为预期
ctx.assertCsrf();
return next();};
};
刷新 token
cookie 在设置时如果没有明确 expires 默认是 session,这个 session 的意思跟 sessionStorage 的作用时间不一样,sessionStorage 只要关闭 tab 就不会保留数据,但 cookie 默认 expires 值 session 只有关闭浏览器 token 才会失效重新赋值。所以在用户登录的时候需要刷新 token,egg 提供了 ctx.rotateCsrfSecret();
重新设置 csrfToken。
CSP
CSP(Content Security Policy)指定资源可信任来源 (脚本、图片、iframe、fton、style 等等可能的远程的资源),减少(注意这里是减少而不是消灭) 跨站脚本攻击。
egg-security 中 csp 默认是不开启的,需要熟悉 csp 的 policy 配置 Content Security Policy (CSP) 是什么?为什么它能抵御 XSS 攻击
// 只允许本站资源
csp: {
enable: true,
policy: {'default-src':'self'},
}
XST
Cross-Site Tracing 客户端通过 http trace 请求到服务器,服务器如果按照处理了 trace 请求会在 response body 中返回所有的请求头信息,包括 httponly 的 cookie。
比如说这里的 cookie test 是 httponly,正常来说 js 是不能操作的,但是通过 trace 请求能够直接将这些信息返回。
Trace 请求:
客户端发起一个请求可能要经过多个代理,网关等,每个节点都可能修改原始的 http 请求,trace 方法可以看到发到服务端是请求头最终的样子,TRACE 方法主要用于诊断。
TRACE 请求会在目的服务器端发起一个 环回 诊断。行程最后一站的服务器会弹回一条 TRACE 响应,并在响应主体中携带它收到的原始请求报文。这样客户端就可以查看在所有中间 HTTP 应用程序组成的请求 / 响应链上,原始报文是否,以及如何被毁坏或修改过。
egg-security 的防范:判断方法为 trace 或者 track 时会返回 405(Method Not Allowed)错误
// default
methodnoallow: {enable: true}
钓鱼攻击
URL 钓鱼
网站中可能需要通过参数拼接成一个新的 url 让用户点击访问,如果参数是由用户输入的,可能会导致构造成一个恶意的网站链接,诱导用户跳转欺骗用户输入用户名密码,因为是信任的网站跳转的,用户比较信任。
防范
egg 中由两种服务端跳转的方法,ctx.redirect(url)
会经过配置白名单的校验后再进行跳转,如果 domainWhiteList 为空则与 ctx.unsafeRedirect(url)
跳转一样不进行校验。
// default
exports.security = {domainWhiteList: []
}
iframe 钓鱼
通过内嵌 iframe 到被攻击的网页中可以诱导用户点击危险网站,遮盖影响网站的正常功能。
防范
X-Frame-Options HTTP 响应头是用来给浏览器 指示允许一个页面 可否在 <frame>, <iframe>, <embed> 或者 <object> 中展现的标记。
egg-security 提供了 xframe 的选项,默认是 SAMEORIGIN, 只允许同域把本页面当作 iframe 嵌入。
// default
xframe: {
enable: true,
// 'SAMEORIGIN', 'DENY' or 'ALLOW-FROM http://example.jp'
value: 'SAMEORIGIN',
}
参考链接
https://www.ibm.com/developer…
https://eggjs.org/zh-cn/core/…
https://segmentfault.com/a/11…
https://www.freebuf.com/artic…