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

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

XSS

1)XSS的定义

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

2)XSS的分类

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

1、反射型:通过url参数直接注入

2、存储型:存储到数据库,用户读取时注入

3)整体演示代码结构

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

下面我们看一下,本次分享所使用到的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中提供的两个页面:



Document

 
{{}}} 三个中括号为handlebars模板引擎的语法,会将render渲染页面的第二个参数中的数据注入到页面中-->

Document
欢迎来自{{{address}}}的用户,欢迎评论评论列表{{#each commentList}}{{{comment}}}{{/each}}

 

以上是常规应用程序的代码,接下来我们看一下攻击程序的代码,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反射性攻击的做法。

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

可以看到

下面我们看一下演示

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

3)CSRF的特点

1、攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。

2、攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。

3、跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。

4)CSRF的防范手段

1、验证referer

2、携带token

3、使用验证码

验证referer

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

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

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

携带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=北京';
} 

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

使用验证码

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

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

点击劫持

1)点击劫持的定义

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

2)点击劫持演示

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

// hack/click.html
Document

 

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

3)点击劫持的防范

X-FRAME-OPTIONS

1、DENY: 表示页面不允许通过 iframe 的方式展示

2、SAMEORIGIN: 表示页面可以在相同域名下通过 iframe 的方式展示

3、ALLOW-FROM: 表示页面可以在指定来源的 iframe 中展示

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)),});
}); 

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

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


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部