背景
前段时间工作时,安全同学特意提醒要注意安全问题,如:
- 高并发的安全问题 (高并发产生的重复抽奖,重复领积分,重复参加活动,重复领取 xxx 的问题)。
- 短信接收的风控安全问题 (损失短信费用是一个问题,但最担心的是短信被刷完导致影响业务使用)。
- 留资内容在后端显示的 xss 攻击(被从留资入口攻击进内网后台)。
- 各系统的
个人中心
里面的越权漏洞 (越权查看个人地址,越权领奖,越权订单,越权发票等数据)
坦白说,安全这块关注比较少,于是跟安全同学交流学习,推荐一本叫白帽子讲 web 安全书籍,本文的内容是基于该书籍的介绍进行修改调整。
书籍信息
本书全名:白帽子讲 Web 安全
作者:吴翰清
出版社:电子工业出版社
版次:2015 年 1 月
安全的三要素
- 机密性,要求保护数据内容不能泄露,加密是实现机密性要求的常见手段。
- 完整性,要求保护数据内容是完整、没有被篡改,常见的保证一致性的技术手段是数字签名。
-
可用性,要求保护资源是
随需而得
。
安全评估过程
简单分为 4 个阶段:
- 资产等级划分。
- 威胁分析。
- 风险分析。
- 确认解决方案。
资产等级划分
- 互联网的核心是由用户数据驱动的,用户产生业务,业务产生数据,所以互联网安全的核心问题,是数据安全的问题。
- 不同业务的侧重点会不一样,所以做资产划分时,需要跟各业务部分沟通,了解公司最重要的资产以及数据是什么。
- 了解要保护的目标后,需要划分信任域和信任边界,比如两个应用传输数据,对于各自应用是否可信?是否应该划分一个边界,对数据进行安全检查。
威胁分析
威胁分析就是把所有的威胁都找出来,一般是采用头脑风暴法。
但大部分会采用威胁建模,把尽可能多的风险列出,好处是避免遗漏,常用的方法叫STRIDE模型,如下:
主要包括伪装、篡改、抵赖、信息泄露、拒绝服务、提升权限。
风险分析
风险由以下因素组成:
Risk = Probability * Damage Potential
影响风险高低的因素,除了造成损失的大小外,权衡事件发生的可能性,才能正确地判断出风险。
微软提出的DREAD模型可以更科学的判断威胁的风险程度:
安全方案的特点
优秀的安全方案应该具备以下特点:
- 能够有效解决问题。
- 用户体验好。
- 高性能。
- 低耦合。
- 易于扩展与升级。
设计方案时的方法
- 黑白名单,比如只开放某一个端口。
- 最小权限原则,只授予主体必要的权限,这样能减少出错的机会。
- 纵深防御原则,从不同的层面、不同的角度对系统做出整体的解决方案。
- 数据与代码分离原则,防止因注入引发安全问题。
-
不可预测性原则,禁止使用有规则特性的方式,如
id=1、2、3
递增的方式,建议使用加密算法、随机数算法、哈希算法来提供不可预测性。
浏览器安全
同源策略
指A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同"
。
定义如下:
协议相同
域名相同
端口相同
举例来说,http://www.jb.com/dir/page.html
这个网址,协议是http://
,域名是www.jb.com
,端口是80
,它的同源情况如下。
http://www.jb.com/dir2/other.html:同源
http://www.jb.com/dir/inner/another.html:同源
http://jb.com/dir/other.html:不同源(域名不同)
http://v2.www.jb.com/dir/other.html:不同源(域名不同)
http://www.jb.com:81/dir/other.html:不同源(端口不同)
设想这样一种情况:
用户登录 A 网站以后,又去浏览网站 B。如果网站 B 可以读取 A 网站的 Cookie
,会发生什么?
很显然,如果Cookie
包含隐私(比如金额、个人信息等),这些信息就会泄漏。
由此可见,同源政策的目的是为了保证用户信息的安全,防止恶意的网站窃取数据。
浏览器沙箱
沙箱模型技术是浏览器和其他应用程序中保护安全的一种组件关系设计模式,设计目的一般是为了让不可信任的代码运行在一定的环境中,限制不可信任的代码访问隔离区之外的资源。
恶意网址拦截
工作原理一般是,浏览器周期性地从服务器端获取一份最新的恶意网址黑名单,如果用户上网时访问的网址存在于此黑名单中,浏览器就会弹出一个警告页面。
跨站脚本攻击(XSS)
XSS
攻击通常指黑客通过HTML注入
篡改了网页,插入了恶意的脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击。
例子
某天,公司需要一个搜索页面,根据 URL
参数决定关键词的内容。jb 很快把页面写好并且上线,代码如下:
<input type="text" value="<%= getParameter("keyword") %>">
<button>搜索</button>
<div>
您搜索的关键词是:<%= getParameter("keyword") %>
</div>
然而,在上线后不久,就接到了安全组发来的一个神秘链接:
带着一种不祥的预感点开了这个链接,页面中弹出了写着” XSS” 的对话框。
当浏览器请求 http://xxx/search?keyword="><script>alert('XSS');</script>
时,服务端会解析出请求参数 keyword
,得到 "><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>
是恶意代码,因而将其执行。
这里不仅仅 div
的内容被注入了,而且 input
的 value
属性也被注入, alert
会弹出两次。
本质
恶意代码未经过滤,与网站正常的代码混在一起;
浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。
分类
存储型
存储型XSS
会把用户输入的数据存储在服务器端,具有很强的稳定性。
攻击步骤:
- 攻击者将恶意代码提交到目标网站的数据库中。
- 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
- 这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。
反射型
把用户输入的数据反射给浏览器,黑客往往需要诱使用户点击一个恶意链接,才能攻击成功。
攻击步骤:
- 攻击者构造出特殊的 URL,其中包含恶意代码。
- 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
反射型跟存储型的区别
- 存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。
- 反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。
DOM 型
通过修改页面的DOM
节点形成的XSS
,称之为DOM Based XSS
。
攻击步骤:
- 攻击者构造出特殊的
URL
,其中包含恶意代码。 - 用户打开带有恶意代码的
URL
。 - 用户浏览器接收到响应后解析执行,前端
JavaScript
取出URL
中的恶意代码并执行。 - 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
跟前两种的区别:
-
DOM
型XSS
攻击中,取出和执行恶意代码由浏览器端完成,DOM 属于前端 JavaScript 自身的安全漏洞,而其他两种XSS
都属于服务端的安全漏洞。
预防
XSS
攻击有两大要素:
- 攻击者提交恶意代码。
- 浏览器执行恶意代码。
输入过滤
常见的Web
漏洞如XSS、SQL Injection
等,都要求攻击者构造一些特殊字符,这些特殊字符可能是正常用户不会用到的,所以输入检查就有存在的必要了。
输入检查的逻辑,必须放在服务器端代码中实现。如果只是在客户端使用JavaScript
进行输入检查,是很容易被攻击者绕过的。
目前Web
开发的普遍做法,是同时在客户端 JavaScript 中和服务器端代码中实现相同的输入检查。客户端JavaScript
的输入检查,可以阻挡大部分误操作的正常用户,从而节约服务器资源。
输出过滤
在变量输出到 HTML 页面时,可以使用编码或转义的方式来防御XSS
攻击。
跨站点请求伪造(CSRF)
CSRF(Cross-site request forgery)跨站请求伪造:
攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
攻击流程
- 受害者登录
a.com
,并保留了登录凭证(Cookie
)。 - 攻击者引诱受害者访问了
b.com
。 -
b.com
向a.com
发送了一个请求:a.com/act=xx
。浏览器会默认携带a.com
的Cookie
。 -
a.com
接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。 -
a.com
以受害者的名义执行了act=xx
。 - 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让
a.com
执行了自己定义的操作。
攻击类型
-
get
类型,直接请求一个url
即可。 -
post
类型,访问链接后自动提交表单,模拟完成一个post
操作。 - 链接类型,相对上面两种,成本更高,需要用户进行点击链接才能触发攻击,这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击。
特点
- 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
- 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
- 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是冒用。
- 跨站请求可以用各种方式:图片
URL
、超链接、CORS
、Form
提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。
防护策略
- 验证码,强制用户必须跟应用完成交互,能遏制攻击,但只是辅助形式。
-
Referer Check
,用于检查请求是否来自合法的源,但目前大部分都可以自定义请求头,所以也只能是辅助形式。 -
Token
,这是页面目前公认的做法,下面详细说明。
Token
上面提及到CSRF
的特性,攻击者无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用 Cookie 中的信息。
而CSRF
攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么可以要求所有的用户请求都携带一个CSRF
攻击者无法获取到的Token
。服务器通过校验请求是否携带正确的Token
,来把正常的请求和攻击的请求区分开,也可以防范CSRF
的攻击。
- 将
CSRF Token
输出到页面中。 - 页面提交的请求携带这个
Token
。 - 服务器验证
Token
是否正确。
Token 的值必须是随机生成的。
点击劫持
点击劫持是一种视觉上的欺骗手段。
攻击者使用一个透明的、不可见的iframe
,覆盖在一个网页上,然后诱使用户在该网页上进行操作,此时用户将在不知情的情况下点击透明的iframe
页面。
一眼看这个按钮没啥问题,但实际会跳到jb.html
,还可以模拟一些博客的点赞、关注功能,但因为需要诱使用户与页面进行交互,实施的攻击成本更高,一般来说都比较少见。
用户以为点击的是下层页面内容,但真正操作的是上面的透明的一层。而因为它是透明的,真正的意图是由攻击者在控制。
注入安全
介绍
注入安全应该是安全领域中最常见的攻击方式,一般测试同学也是比较关注这类,前面提交到的xss
本质也是对HTML
的注入攻击。
注入攻击的本质,是把用户输入的数据当做代码执行。
关键的条件:
- 第一个是用户能够控制输入。
- 第二个是原本程序要执行的代码,拼接了用户输入的数据。
SQL 注入
例子
有这么一段逻辑:
var Shipcity;
ShipCity = Request.form ("ShipCity");
var sql = "select * from OrdersTable where
ShipCity = '" + ShipCity + "'";
变量ShipCity
的值由用户提交,在正常情况下,假如用户输入Beijing
,那么SQL
语句会执行:
SELECT * FROM OrdersTable WHERE ShipCity = 'Beijing'
但假如用户输入一段有语义的SQL
语句,比如:
Beijing'; drop table OrdersTable--
那么,SQL
语句在实际执行时就会如下:
SELECT * FROM OrdersTable WHERE ShipCity =
'Beijing';drop table OrdersTable--'
这里看到,原本正常执行的查询语句,现在变成了查询完后,再执行一个 drop 表的操作,而这个操作,是用户构造了恶意数据的结果。
攻击行为
1. 猜测数据库名,备份数据库
2. 猜解字段名称
3. 遍历系统的目录结构,分析结构并发现WEB虚拟目录,植入木马
4. 查询当前用户的数据库权限
5. 设置新的数据库帐户提权得到数据库管理员账户权限
6. 利用存储过程获取操作系统管理员账户
7. 客户端脚本攻击:通过正常的输入提交方式将恶意脚本提交到数据库中,当其他用户浏览此内容时就会受到恶意脚本的攻击。
8. 客户端脚本攻击:通过SQL注入方式将恶意脚本提交到数据库中,直接使用SQL语法UP
如何防御
采用 sql 语句预编译和绑定变量
String sql = "select id, no from user where id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ps.executeQuery();
如上所示,就是典型的采用 sql
语句预编译和绑定变量 。为什么这样就可以防止sql
注入呢?
因为采用了PreparedStatement
,就会将sql
语句:"select id, no from user where id=?"
预先编译好,也就是SQL
引擎会预先进行语法分析,产生语法树,生成执行计划.
后面输入的参数,无论输入的是什么,都不会影响该sql
语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析sql
命令,比如 select ,from ,where ,and, or ,order by
等等。
所以即使后面输入了这些sql
命令,也不会被当成sql
命令来执行了,因为这些 sql 命令的执行, 必须先的通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为sql
命令来执行的,只会被当做字符串字面值参数。所以sql
语句预编译可以防御sql
注入。
使用安全函数
有一些场景必须的采用字符串拼接的方式,此时需要严格检查参数的数据类型,还有可以使用一些安全函数,来防止 sql 注入。
安全函数的使用,比如:
MySQLCodec codec = new MySQLCodec(Mode.STANDARD);
name = ESAPI.encoder().encodeForSQL(codec, name);
String sql = "select id,no from user where name=" + name;
ESAPI.encoder().encodeForSQL(codec, name)
该函数会将 name
中包含的一些特殊字符进行编码,这样 sql
引擎就不会将name
中的字符串当成sql
命令来进行语法分析了。
检查数据类型
比如 String sql = "select id,no from user where id=" + id;
在接收到用户输入的参数时,我们就严格检查 id
,只能是int
型。复杂情况可以使用正则表达式来判断。这样也是可以防止sql
注入的。
XML 注入
XML
是一种常用的标记语言,通过标签对数据进行结构化表示,进行攻击需要满足两大条件:
- 用户能控制数据的输入。
- 程序成功拼凑数据。
解决方案跟上面类似,对用户输入数据中包含的保留字符进行转义即可。
代码注入
代码注入与命令注入往往都是由一些不安全的函数或者方法引起的。
对抗代码注入、命令注入时,需要禁用eval()、system()
等可以执行命令的函数。如果一定要使用这些函数,则需要对用户的输入数据进行处理。
代码注入往往是由于不安全的编程习惯所造成的,危险函数应该尽量避免在开发中使用,可以在开发规范中明确指出哪些函数是禁止使用的。
小结
了解安全评估流程,xss、csrf、sql
注入攻击的原理及常用的预防措施,点击劫持常用的手段信息。