08月10, 2017

web安全笔记(1)

记一些笔记。该笔记是我对慕课网收费课程的一些记录与总结,如果有侵权,请联系我!

我们自己可以简单搭一下koa项目来跑一个安全方面的项目,前阵子刚好知乎上有一篇浅谈 Node.js 安全

这里我之前碰到一个问题,我电脑用的node版本是6.9,然后要跑node的async await必须要是7.x。

所以我可以用n包管理器来安装node7就行,但是nodemon要怎么用呢?查了蛮久的资料,终于让我找到了这个页面:sample-nodemon.md

我只需要在当前项目下新建一个nodemon.json,让其内容为:

{
  "execMap": {
    "js": "n use 7.6.0 --harmony-async-await"
  }
}

搞定。

Scripting能干啥?

  • 获取页面数据
  • 获取Cookies
  • 劫持前端逻辑
  • 发送请求
  • ...

从而偷取网站任意数据数据、偷取用户资料、偷取用户密码和登录态、欺骗用户。。。

XSS

跨站脚本攻击:在目标页面,执行第三方的脚本。

攻击注入点:

  • HTML节点内容
  • HTML属性
  • javascript代码
  • 富文本

a. HTML节点内容

<div>
     #{content}
</div>

防御代码:

var escapeHtml = function(str) {
     if(!str)  return '';
     str = str.replace(/&/g, '&amp;');  // 这条可有可无
     str = str.replace(/</g, '&lt;');
     str = str.replace(/>/g, '&gt;');
    return str;
}

b. HTML属性

<img src="#{image}" />
<img src="1" onerror="alert(1)" />

防御代码:

var escapeHtmlProperty = function(str) {
    if(!str)  return '';
    str = str.replace(/"/g, '&quto;');   // 处理“
    str = str.replace(/'/g, '&#39');   // 处理'
    str = str.replace(/ /g, '&#32');   // 处理空格,HTML属性不一定要有引号!!这个巨坑,所以写属性一定要加引号
    return str;
}

另外这个函数,其实可以和上面的防御HTML节点内容的代码进行合并。

c. javascript代码

var data = "#{data}";
var data = "hello";alert(1)"";

防御代码:

var escapeForJs = function(){
    if(!str) return '';
    str = str.replace(/\\/g, '\\\\');
    str = str.replace(/"/g, '\\"');
    return str;
}

但其实上面的写法可能依旧存在漏洞,所以推荐用:

JSON.stringify(str);

d. 富文本

简单过滤:

var xssFilter = function(html) {
   if (!html)  return '';
   html = html.replace(/<\s*\/?script\s*/g, '');  // 过滤script标签
   html = html.replace(/javascript:[^'"]*/g, '');  // 过滤javascript:这种
   html = html.replace(/onerror\s*=\s*['"]?[^'"]*['"]?/g, '');  // 过滤onerror
   return html;
}

但上面显然没有考虑像onmouseover等其他事件之类的。

所以这里靠黑名单肯定不行,得通过白名单来保留部分标签和属性。


浏览器自带防御:

alt

但这个防御比较有限,它不会对js的代码做防御。

注:这里可以通过设置X-XSS-Protection来关闭浏览器的防御:

// ctx为koa的上下文
ctx.set("X-XSS-Protection", 0);

XSS的危害可不仅仅是alert(1),既然可以alert了,那就可以引入第三方的js,然后就可以在js中得到当前网页的cookie了。

CSP防御:

Content Security Policy:内容安全策略,用于指定哪些内容可执行。

ctx.set('Content-Security-Policy', `default-src 'self'`);

加了这句话,所有的第三方注入都无法执行了。

更多资料请参见:CSP

CSRF

Cross Site Request Forgy: 跨站请求伪造。

这货要比XSS牛逼了。它就相当于攻击者盗用了你的身份,以你的名义发送恶意请求,可谓是一点就爆。

攻击原理:

a. 用户登录A网站 b. A网站确认身份 c. B网站页面向A网站发起请求(带A网站身份)

攻击危害:

  • 利用用户登录态
  • 用户不知情
  • 完成业务请求
  • ...

从而盗取用户资金(转账、消费)、冒充用户发帖背锅、损坏网站名誉。。。

攻击防御:

它的特征:B网站向A网站请求带A网站Cookies、不访问A网站前端、referer为B网站。

针对特征一:B网站向A网站请求带A网站Cookies,可以使用sameSite(但有些浏览器不支持):

ctx.cookies.set('userId', user.id, {
     httpOnly: false,
     sameSite: 'strict'
})

针对特征二:不访问A网站前端,考虑的方案是在前端页面加入验证信息:

  • 验证码

验证码可以使用ccap

var captcha = {};

var cache = {};

captcha.captcha = async function(ctx, next) {
    var ccap = require("ccap");
    var capt = ccap();
    var data = capt.get();

    captcha.setCache(ctx.cookies.get('userId'), data[0]);

    ctx.body = data[1];
}

captcha.setCache = function(uid, data) {
    cache[uid] = data;
}

captcha.validCache = function(uid, data) {
    return cache[uid] === data;
}

module.exports = captcha;

同时在提交时,后台注意要判断是否有验证码(即验证码是否为空)。

  • token

后端往前端render的时候带上一个随机的token值,且种到cookie中去。

当form提交时,将给前端的token值传回后端,后端将这个值与cookie中的值进行比较。

if (data.csrfToken != ctx.cookies.get('csrfToken')) {
     throw new Error('CSRF token为空');
}

这里其实有一个问题,当打开多个form表单时,必须要用最后一个页面来提交数据,如果回到之前的页面去提交,会失败(因为每打开一个页面都会生成最新的token)。

针对特征三:referer为B网站,考虑的方案是验证referer,禁止来自第三方网站的请求

代码譬如:

var referer = ctx.request.headers.referer;
if (/^https?:\/\/localhost/.test(referer)) {
    throw new Error("非法请求");
}

Cookies

登录用户凭证

  • 用户ID
  • 用户ID + 签名

光用户ID,如果不设置httpOnly,那么可以通过修改document.cookie来改变用户ID。

所以我们需要一个签名:

var crypt = {};

const KEY = "#dsqre!d45#56$"; //这个key越复杂越好

crypt.cryptUserId = function(userId) {
    var crypto = require('crypto');
    var sign = crypto.createHmac("sha256", KEY);
    sign.update(userId + "");
    return sign.digest('hex');
}

module.exports = crypt;

将ID和加密后的ID,种到cookie中。在提交时,得到的ID加个密和cookie中的签名对比,不相等的话,说明有人用document.cookie修改了ID。

  • sessionId
var session = {};

var cache = {};

session.set = function(userId, obj) {
    var sessionId = Math.random();
    if (!cache[sessionId]) {
        cache[sessionId] = {}; 
    }
    cache[sessionId].content = obj;
    return sessionId;
}

session.get = function(userId) {
    return cache[sessionId] && cache[sessionId].content;
}

module.exports = session;

使用:

var sessionId = session.set(user.id, {
        userId: user.id
})

这样就可以将sessionId和userId做一个映射关系了。

当然实际代码中,不可能将session保存在内存中,因为如果一旦分布式,数据就会丢失了。


Cookies和XSS的关系

  • xss可能偷取Cookies
  • http-only的Cookies不会被偷

Cookies和CSRF的关系

  • CSRF利用了用户Cookies
  • 攻击站点无法读写Cookies
  • 最好能阻止第三方使用Cookies

Cookies安全策略

  • 签名防篡改
  • 私有变换(对称加密,如des)
  • http-only(防止XSS)
  • secure (当设置为true时,表示创建的 Cookie 会被以安全的形式向服务器传输,也就是只能在 HTTPS 连接中被浏览器传递到服务器端进行会话验证,如果是 HTTP 连接则不会传递该信息,所以不会被窃取到Cookie 的具体内容。)
  • same-site

des加减密代码:

var crypto = require('crypto');

const KEY = "#dsqre!d45#56$"; //这个key越复杂越好

var cipher = crypto.createCipher("des", KEY);
var text = cipher.update('hello word', 'utf8', 'hex');

text += cipher.final('hex');

console.log(text)

var decipher = crypto.createDecipher('des', KEY);
var originalText = decipher.update(text, 'hex', 'utf8');
originalText += decipher.final('utf8');

console.log(originalText);

本文链接:www.my-fe.pub/post/web-safety-note-1.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。