Egg中的安全防范

3,848 阅读8分钟

以前获取前端安全方面的知识非常零碎且大多停留在看,又或者自己在实际项目中用到了其实也不太清楚。通过这次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值的。

防范方法:

  1. 验证referer字段。
  • 从上述图中可以看到服务器中是能获取到referer的,将所有有安全敏感的请求都加一个referer的过滤拦截。
  • 问题:referer是由浏览器提供的,虽然http有协议规定,但各个浏览器对referer的实现可能会有偏差,不能保证浏览器本身没有安全漏洞。把安全性依赖于第三方浏览器来保障,理论上并不可靠。旧的浏览器有些可以被篡改referer,即使无法被篡改,用户如果担心referer留下用户的访问来源也可以在浏览器设置不记录referer信息。
  1. 通过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 响应头是用来给浏览器 指示允许一个页面 可否在 , , 或者 中展现的标记。 egg-security提供了xframe的选项,默认是SAMEORIGIN,只允许同域把本页面当作 iframe 嵌入。

// default
xframe: {
  enable: true,
  // 'SAMEORIGIN', 'DENY' or 'ALLOW-FROM http://example.jp'
  value: 'SAMEORIGIN',
}

参考链接