当今互联网Web安全问题层出不穷,我们需要了解常见的安全问题和防范的手段,在开发的时候要注意相关的安全问题。
跨站脚本攻击(XSS)
跨站脚本攻击(Cross Site Scripting),简称XSS(因与CSS层叠样式表重名,改为XSS)。web漏洞中危害比较大排行靠前的漏洞。
原理
发生在目标网站中目标用户的浏览器层面上,当用户浏览器渲染整个HTML文档的过程中出现了不被预期的脚本指令并执行时,就发生XSS攻击。
特点及危害
- 挂马
- 盗取用户Cookie
- 钓鱼攻击、恶意篡改数据、嫁祸
- js前端挖矿
- ...
类别
反射型 XSS
- 定义:XSS代码出现在URL中,提交给服务器后响应内容出现这段XSS代码,最后由浏览器解析执行。
- 例子:
场景:从url中参数获取用户的来源,然后展现在网站头部的标题“欢迎来自xx的朋友”
预期:http://xx.com/?from=google, 从参数获取用户
服务端:ctx.render('index', {posts, comments, from:ctx.query.from || ''});
前端PUG模板:
a.page-title(href="/")
span 测试网站
if from
span - 欢迎来自!{from}的朋友
攻击者:
http://xx.com/?from=<scripit>alert("执行恶意代码")</script>google
复制代码
短链接能提升隐秘性
存储型 XSS
- 定义:提交的XSS代码会存储在服务端,下次请求目标页面时不用再提交XSS代码,其攻击是最隐秘的
- 例子:例如文章评论,攻击者提交一条包含XSS代码的评论存储到数据库,如果网站不做什么处理,直接显示,则访问这篇文章的用户都会受到XSS攻击。
DOM XSS
- 与反射性XSS、存储型XSS的差别在于DOM XSS的代码并不需要服务器解析响应的直接参与,通过浏览器的DOM解析就可以进行触发。
- 例子:
前端代码:
var hash = location.hash;
if(hash){
var url = hash.substring(1);
location.href = url;
}
攻击者:
http://xx.com/#javascript:alert(1) // 使用Javascript伪协议进行攻击
复制代码
//常见的输入点
document.URL
document.location
document.cookie
window.location
window.name
...
// 常见的输出点
document.write()
document.writeln()
document.body.innerHTML
document.create
window.location.href
...
复制代码
常见的注入点
-
HTML节点内容
- 节点内容是动态生成的,并且里面的内容包含用户的输入,可能输入脚本进行攻击
前端代码:
var hash = location.hash;
if(hash){
document.getElementById('value').innerHTML = hash;
}
攻击者:
http://localhost/#<img src="1" onerror="alert(document.cookie)"
复制代码
HTML属性
- 某个HTML的节点的某个属性是由用户输入决定的,用户的输入可能包含脚本或者跃出这个属性的范围而导致XSS攻击
前端代码:
<img src="/image/!{avatarId}"/>
攻击者:
http://localhost/?avatarId=1" onerror="alert(1)
复制代码
JavaScript代码
- JS代码中存在由后台注入的变量,里面包含用户输入的信息,可能改变JS的逻辑,从而导致XSS攻击
前端代码:
let data="#{data}"
let data="hello";alert(1);"" //转义双引号
script.
var from="!{from}"
攻击者:
http://localhost/?from=google"alert(1);"
复制代码
富文本
- 富文本带有很多的HTML代码
防治方法
- 浏览器自带防御: 防御参数出现在HTML内容或属性上的反射型XSS,但不同浏览器的支持不一样,是一种有限的手段。Chrome等浏览器默认开启,可设置X-XSS-Protection强制开启,防止用户关闭检查。
ctx.set('X-XSS-Protection',1); // 可以通过设置header进行关闭,0为关闭,1为开启
复制代码
转义和过滤
- 输入检查 用户输入的内容,过滤其中的特殊字符,如
",',<,>,&
- 输出检查 输出用户输入的内容时,对其中不含的特殊字段进行转义,如
<
转为<
,>
转为>
- 谨慎使用eval,innerHTML等js方法或属性
- 设立白名单 对于富文本,按白名单保留部分标签和属性
- CSP
跨站请求伪造(CSRF)
跨站请求伪造(Cross-site request forgery),简称CSRF,关键点是跨站点的请求与伪造请求,挟持用户在当前已登录的Web应用程序上执行非本意的操作。其与XSS的概念很容易发生混淆,其与XSS的区别,可以通过知乎的一个例子了解:
===防盗系统启动===
妈妈: 给我看着衣服呀
小孩: 好的
===小偷来了===
===正常工作===
小孩: 你是谁?
小偷: 我是张三
小孩:妈妈,有人偷衣服
妈妈: 谁?
小孩: 张三
小偷被抓
===漏洞===
小孩: 你是谁?
小偷: 我叫逗你玩
小孩: 妈妈有人偷衣服呀
妈妈: 谁?
小孩: 逗你玩
妈妈: ...
========================================
csrf是让用户在不知情的情况下,冒用其身份发起了一个请求
小偷: 你妈妈喊你去买洗衣粉
复制代码
原理
特点及危害
- 传播CSRF蠕虫
- 篡改目标网站上的用户数据
- 盗取用户隐私数据
- 冒充用户发帖背锅
- ...
类别
document.write(`
<form name="commentForm" target="csrf" method="post" action="http://localhost/post/addComment">
<input name="postId" type="hidden" value="23">
<textarea name="content">来自CSRF!</textarea>
</form>`
);
var iframe = document.createElement('iframe');
iframe.name = 'csrf';
iframe.style.display = 'none';
document.body.appendChild(iframe);
setTimeout(function(){
document.querySelector('[name=commentForm]').submit();
},1000);
复制代码
防治方法
-
Token
- 生成一个随机字符串,存到前端表单,或者header请求头
-
验证码
- 提交需要填写验证码,但此操作对用户不太友好
-
验证Referer
-
Cookie same-site
- strict:任何跨域请求中都不会携带 cookie,能够完美的阻止 CSRF 攻击
- lax:用 安全的 HTTP 方法(GET、HEAD、OPTIONS 和 TRACE)改变了当前页面或者打开了新页面,可以允许携带cookie
点击劫持(Clickjacking)
点击劫持,是指利用透明的按钮或链接做成陷阱,覆盖在 Web 页面之上。然后诱使用户在不知情的情况下,点击那个按钮或链接访问内容的一种攻击手段。
原理
- 黑客创建一个网页利用 iframe 包含目标网站
- 隐藏目标网站,使用户无法无法察觉到目标网站存在
- 构造网页,诱使用户点击特定按钮
- 用户在不知情的情况下点击按钮,触发执行恶意网页的命令
特点及危害
- 获取用户敏感信息
- 盗取用户
- ...
防治方法
-
Javascript禁止内嵌
- 可以利用top和self进行区分是否内嵌,但这种手段没法彻底防范CSRF攻击,攻击者可以使用IFrame的sanbox="allow-forms" 禁止js运行导致不起作用。
if(top !== self){
top.location.href = location.href;
}
复制代码
X-FRAME-OPTIONS
ctx.set('X-Frame-Options','DENY')
复制代码
DENY:表示页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许
- SAMEORIGIN:表示该页面可以在相同域名页面的 frame 中展示
- ALLOW-FROM url:表示该页面可以在指定来源的 frame 中展示
跨域资源共享(CORS)漏洞
CORS(Cross-Origin Resource Sharing),HTML5的一个新特性,用于解决浏览器跨域资源访问。在此之前,我们先说明一下SOP。
原理
同源策略
SOP(Same Origin Policy,同源策略),web最重要的安全策略,以同源为限制条件。同源策略,隔离了不同源网站的cookie,拦截不同源的请求,只允许同源网站的脚本运行,保证了web网站的安全。
- 同源:相同的协议、端口和域名
CORS
W3C设计CORS协议标准,为了更安全规范地支持跨域网络资源共享,其在各大浏览器已经得到了很好地支持。
类别
CORS请求可以分为两类:简单请求(simple request)和非简单请求(not-so-simple request)
满足以下两大条件为简单请求
-
请求方法是以下三种方法之一
- HEAD
- GET
- POST
-
HTTP的头信息在以下字段之中
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:限于三种值:
Application/x-www-form-urlencoded
、multipart/form-data
、text/plain
不同时满足两大条件为非简单请求。
连接过程
-
网站直接发起CORS请求,浏览器自动在每个跨域请求的头信息添加
Origin
字段,用来说明本次请求来源 -
简单请求
- 如果Origin值不在允许范围内,服务器会返回一个正常的HTTP Response,浏览器如果发现返回信息没有包含Access-Control-Access-Origin,会抛出错误
- 如果在允许范围之内,添加几个字段
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Expose-Headers
复制代码
非简单请求
- 先发出一个OPTIONS请求,判断所在的域名是否在服务器的允许范围,以及可以使用哪些HTTP方式和头信息字段。
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
复制代码
通过之后,其请求过程都跟简单请求一样
错误配置引起的攻击
场景:想要与多个域共享数据
方法:需要配置Access-Control-Allow-Origin
,但Access-Control-Allow-Origin
的值的范围只能为*、null、<origin>
,以下配置是错误:
Access-Control-Allow-Origin: http://a.com, http://b.com 错误配置
Access-Control-Allow-Origin: http://*.a.com 错误配置
复制代码
<origin>
只能配置单个域名,如果允许多个源的话,需要编写代码来动态生成访问控制策略,而在这个过程中很可能由于配置出现问题,导致网站遭受攻击。
- 反射Origin头:发射请求的Origin值,导致信任所有来源的请求
- Origin校验错误:慎用正则
前缀匹配:
预期:a.com
攻击者:a.com.danger.com
后缀匹配:
预期:a.com
攻击者:dangera.com
没有转义‘.’
预期:www.a.com
攻击者:wwwba.com
复制代码
信任null
- 开发者在网站上配置信任null,用于与本地file页面共享数据,但是除了本地file页面的跨域请求Origin头为null外,攻击者可以从任意域通过iframe sanbox构造Origin为null的跨域请求。
<iframe
sandbox="allow-scripts allow-top-navigation allow-forms"
src="data:text/html,<script>XMLHttpRequest here</script>"
>
</iframe>
复制代码
HTTPS信任HTTP域
- 中间人攻击
-
信任自身全部子域
- 所有鸡蛋放在一个篮子里。某个子域的XSS漏洞会危害其他所有子域
-
Origin:* 与 Credentials:true 共用
-
缺少Vary:Origin头
- 服务器需要在响应头中配置Vary:Origin头来指导缓存,为每个不同的Origin头缓存一份不同的内容,避免由于缓存导致CORS失效
特点及危害
- 用户隐私泄露
- 信息窃取
- ...
防治方法
- 不要盲目反射 Origin头
- 严格校验 Origin 头,避免出现权限泄露
- 不要配置 Access-Control-Allow-Origin: null
- HTTPS 网站不要信任HTTP 域
- 不要信任全部自身子域,减少攻击面
- 不要配置 Origin:*和 Credentials: true
- 增加 Vary: Origin 头
SQL注入(SQL Injection)
SQL注入攻击是通过将恶意的 SQL查询或添加语句插入到应用的输入参数中,再在数据库服务器上解析执行进行的攻击。
原理
===基本例子===
select * from table where id=${id}
传入数据:1 or 1=1
select * from table where id=1 or 1=1
===实际场景的注入===
select * from user where username='${data.username}' and password='${data.password}'
// 注入的攻击
输入http://localhost/post/23,获取id取得对应的文章
对应的sql语句:select * from post where id = "${id}"
攻击者:http://localhost/post/23" and 1=0 and ""="
结果构造的SQL语句:select * from post where id = "23" and 1=1 and ""="
=== SQL语句 ===
select * from table where id='1' and 1=0 //错误回显
select * from table where id='1' or 1=1
select * from table where id='1' and mid(version(),1,1)=5 //探测服务端信息
select 1,2,3 from table
select id,1,2,3 from table
select * from table union select 1,2,3 from table2 // 猜table2的列数
select * from table where mid(username,1,1)='t' // 猜测数据库内容
复制代码
常见的注入点:
- SELECT columns
- FROM table
- WHERE expression
- ORDER BY expression
特点及危害
- 脱库
- 读取或修改数据库中的内容
- 通过存储过程执行系统命令和操作注册表
- ...
防治方法
-
关闭错误输出
-
检查数据类型
-
对数据进行转义
- 对可能变为程序的字符进行转义
-
参数化查询
- 不管输入任何值,都将整个值作为参数的值,而不是原始 SQL 文本的一部分
安全防御
内容安全策略(CSP)
- 定义
内容安全策略(Content Security Policy),用于指定哪些内容可执行,能够将各种子资源的加载限制到开发人员允许的一组源。
-
实现方式:
- 服务器添加
Content-Security-Policy
响应头来指定规则 - HTML 中添加 标签来指定
Content-Security-Policy
规则
- 服务器添加
-
预设值
Content-Security-Policy: default-src ‘self’;
复制代码
none 不匹配任何东西
- self 匹配当前域,但不包括子域。比如 example.com 可以,api.example.com 则会匹配失败
- unsafe-inline 允许内嵌的脚本及样式。对于页面中内嵌的内容也是有相应限制规则的
- unsafe-eval 允许通过字符串动态创建的脚本执行,比如 eval,setTimeout 等
几条安全原则
-
黑名单、白名单原则
- 黑名单能够防止已知可能带来的威胁,但很难防范未知的危险
- 白名单只有在规则内才可以使用,可以防范很多未知的危险,但是要基于白名单是完全可信任的前提
-
最小权限原则
- 只授予主体必要的权限,而不要过度授权,能有效减少出错的机会
-
数据与代码分离原则
- 将用户数据当作代码执行,可能导致安全问题