写在前面:小时候听到黑客,总感觉很神秘的感觉,多年以后自己做了开发,或多或少的接触一些,但都不太系统,因此这里将。
参考资料
《白帽子讲 web 安全》、chrome 开发者文档
XSS
xss 防御
- HttpOnly,浏览器将禁止页面的 JavaScript 访问带有 HttpOnly 属性的 Cookie
- 输入检查,遇到特殊字符转义
- 输出检查,当变量输出到 html 页面,可以使用编码或转义的方式来防御 XSS 攻击。
HttpOnly 解决的是 XSS 后的 Cookie 劫持攻击,XSS 的本质还是一种“HTML 注入”,用户的数据被当成了 HTML 代码一部分来执行,从而混淆了原本的语义,产生了新的语义。
而 secure 属性,当设置为 true 时,表示创建的 Cookie 会被以安全的形式向服务器传输,也就是只能在 HTTPS 连接中被浏览器传递到服务器端进行会话验证,如果是 HTTP 连接则不会传递该信息,所以不会被窃取到 Cookie 的具体内容。
首先,secure 属性是防止信息在传递的过程中被监听捕获后信息泄漏,HttpOnly 属性的目的是防止程序获取 cookie 后进行攻击。
csp 的实质就是白名单制度,开发者明确告诉客户,哪些外部资源可以加载和执行,等同于提供白名单,它的实现和执行全部由浏览器完成,开发者只需提供配置。CSP 大大增加了网页的安全性,减少(并不是消灭)跨站脚本攻击。
跨站脚本漏洞测试流程:
- 在目标站点上找到输入点,比如查询接口,留言板等;
- 输入一组”特殊字符+唯一识别字符” ,点击提交后,查
- 通过搜索定位到唯一字符,结合唯一字符前后语法确认是
- 提交构造的脚本代码(以及各种绕过姿势),看是否可以成功
TIPS :
- 一般查询接口容易出现反射型 XSS ,留言板容易出现存储型
- 由于后台可能存在过滤措施,构造的 script 可能会被过滤掉
- 通过变化不同的 script,尝试绕过后台过滤机制;
xss 绕过手段
- 前端限制绕过(比如长度等),直接抓包重放,或者修改 html 代码即可绕过
- 大小写,比如
<SCRIPT>aLeRT(11)</sCriPt>
,正则没过滤掉 - 拼凑:
<scri<script>pt>alert(11)</scri</script>pt>
- 注释干扰:
<scri<!--test-->pt>alert(2)</sc<!--test-->ript>
- 编码,后台过滤了特殊字符,比如
script
标签,但该标签可以被各种编码,后台不一定能过滤,当浏览器对该编码进行识别时,会翻译成正常的标签,从而执行
注意:将特殊字符进行url 编码可能绕过后台的过滤手段,但显式到前端,依然无法被正确解析,因此其实也没有意义。。。还有就是html 编码,html 编码可以绕过后台,同时可以被前台正常解析。另外就是事件标签里面并不会执行<script></script>
标签里的代码。
把预定义的字符转义:
- &,&
- “,"
- ‘,'
- <,<
-
,>
xss 防范措施
总的原则:输入做过滤,输出做转义;
&#**;
格式的字符串是 html 的转义字符,\
是 JS 的转义符,转义的目的就是告诉解析器该符号为字符,而不是代码,防止代码出现歧义。
参考:js 转义与 html 转义 浏览器解析原则:
- 若果存在 html 转义字符串,HTML 解析引擎会先把转义字符解析为字符串
- HTML 解析引擎按照从上到下,从外向里解析 html 标签
- 遇到 html 标签浏览器会让 html 解析引擎解析,遇到
<script>
标签,浏览器会让 JS 解析引擎对标签内容进行解析。
- 过滤:根据业务需求进行过滤,比如输入点要求手机号,则只允许输入手机号
- 转义:所有输出到前端的数据都根据输出点转义,比如输出到 html 中进行 html 实体转义,输出到 js 中进行 js 转义
注意:a 标签的 href 属性,可以使用 javascript 协议来执行 js(比如:javascript:alert(1)
),因此如果要将内容输出到 href 属性里,除了基本的 html 转义,则还需要限定协议为http或https
,而不能是javascript
协议。
如果输出到 js 中的内容,因为 js 不会对 tag 或字符实体进行解释的,虽然用 html 编码可以避免 xss 攻击,但实体编码后的内容,在 js 里不会被翻译,这样的话 js 获取到的内容就没有意义了。。。
跨站点请求伪造(CSRF)
CSRF 简介
CSRF 的全名是Cross Site Request Forgery
,翻译成中文就是跨站点请求伪造。注意要和Cross Origin Resource Share
CORS(跨域解决方案之一)区分开。
CSRF 是借助用户的权限(并没有获取到),而 xss 一般是获取了用户的登陆信息,二者纬度不同……如何检测是否有 csrf 漏洞,比如说修改密码是否验证旧密码?请求里是否有防止伪造的 token?还有就是用户登录以后的认证信息,过期时间太长,关闭浏览器后依然有效,黑客也会利用起来……get 请求只需要伪造一个请求地址就可以发起攻击,而 post 则需要借助隐藏的 form 表单进行提交
防范措施:
// 增加token验证(常用的做法)
// 1. 对关键操作增加token参数,token值必须随机,每次都不一样
// 关于安全的会话管理(避免会话被利用)
// 1. 不要在客户端保存敏感信息(比如身份认证信息)
// 2. 测试直接关闭,退出时的会话过期机制
// 3. 设置会话过期机制,比如15分钟内无操作,则自动登录超时
// 访问控制安全管理
// 1. 敏感信息的修改需要对身份证进行二次认证,比如修改账号时,需要判断旧密码
// 2. 敏感信息的修改使用post,而不是get
// 3. 通过http头部的referer来限制原页面
// 增加验证码
// 1. 一般用在登录(防暴力破解),也可以用在其他重要信息操作的表单中(需要考虑体验)
攻击过程
浏览器所持有的 Cookie 有两种,会话型 Cookie 和持久型 Cookie,前者没有设置过期时间,浏览器关闭即销毁。
CSRF 的防御
一般可以从三种方面入手:
- 验证码
Referer Check
Anti CSRF Token
验证码:CSRF 攻击的过程,往往是在用户不知情的情况下构造了网络请求。而验证码,则强制用户必须与应用进行交互,才能完成最终请求。因此在通常情况下,验证码能够很好地遏制 CSRF 攻击。
Referer Check
:常被用于检查请求是否来自合法的“源”。常见的互联网应用,页面与页面之间都具有一定的逻辑关系,这就使得每个正常请求的 Referer 具有一定的规律。
比如一个“论坛发帖”的操作,在正常情况下需要先登录到用户后台,或者访问有发帖功能的页面。在提交“发帖”的表单时,Referer 的值必然是发帖表单所在的页面。如果 Referer 的值不是这个页面,甚至不是发帖网站的域,则极有可能是 CSRF 攻击。
即使我们能够通过检查 Referer 是否合法来判断用户是否被 CSRF 攻击,也仅仅是满足了防御的充分条件。Referer Check 的缺陷在于,服务器并非什么时候都能取到 Referer。很多用户出于隐私保护的考虑,限制了 Referer 的发送。在某些情况下,浏览器也不会发送 Referer,比如从 HTTPS 跳转到 HTTP,出于安全的考虑,浏览器也不会发送 Referer。
Anti CSRF Token
,一个解决方法是,把参数加密,或者使用一些随机数,从而让攻击者无法猜测到参数值。但是这个方法也存在一些问题。首先,加密或混淆后的 URL 将变得非常难读,对用户非常不友好。其次,如果加密的参数每次都改变,则某些 URL 将无法再被用户收藏。最后,普通的参数如果也被加密或哈希,将会给数据分析工作带来很大的困扰,因为数据分析工作常常需要用到参数的明文。
因此可以用一种更加通用的方案:Anti CSRF Token
,也就是不修改 URL 而是额外增加一个token
,token
需要足够随机。Token 应该作为一个“秘密”,为用户与服务器所共同持有,不能被第三者知晓。在实际应用时,Token 可以放在用户的 Session 中,或者浏览器的 Cookie 中。
Anti CSRF Token
使用原则
防御 CSRF 的 Token,是根据“不可预测性原则”设计的方案,所以 Token 的生成一定要足够随机,需要使用安全的随机数生成器生成 Token。
此外,这个 Token 的目的不是为了防止重复提交。所以为了使用方便,可以允许在一个用户的有效生命周期内,在 Token 消耗掉前都使用同一个 Token。但是如果用户已经提交了表单,则这个 Token 已经消耗掉,应该再次重新生成一个新的 Token。
如果 Token 保存在 Cookie 中,而不是服务器端的 Session 中,则会带来一个新的问题。如果一个用户打开几个相同的页面同时操作,当某个页面消耗掉 Token 后,其他页面的表单内保存的还是被消耗掉的那个 Token,因此其他页面的表单再次提交时,会出现 Token 错误。在这种情况下,可以考虑生成多个有效的 Token,以解决多页面共存的场景。
最后 token 使用,尽量不要放在 URL 里面,因为有可能通过referer
泄露。
CSRF 的本质
CSRF 为什么能够攻击成功?其本质原因是重要操作的所有参数都是可以被攻击者猜测到的。攻击者只有预测出 URL 的所有参数与参数值,才能成功地构造一个伪造的请求;反之,攻击者将无法攻击成功。
防重复提交
参考:防重复提交(简书)、防止表单重复提交的思路和方法(腾讯云)、防止表单重复提交的八种简单有效的策略
重复提交的场景一般三种:
- 在网络延迟的情况下用户连续多次点击提交按钮
- 刷新页面后点击提交
- 前进后退后再次点击提交
第一种情况,只需前端做限制即可(按钮置灰等)。
第二种和第三种情况,需要服务端配合,一般流程为:
- 进入页面后首先向后台发送一次请求,单独请求随机标识号,这时后台会生成唯一的随机标识号,专业术语称为 Token(令牌)。
- 请求接口时带上 token
- 服务端校验提交的 token 与服务端的 token(一般存在 session 中)是否一致。不一致则为重复提交。一致则处理请求,并销毁 token
但是在多服务器多用户的场景下,以上方法也都会失效,在多服务器场景下,session
存在于每台服务器中,请求是通过负载均衡机制分配到各台服务器上的,要通过session
防止重复提交,必须有一套定向分派请求或者session
共享的机制,就算你实现了,如何处理多用户请求的情况呢,比如在一个母帐号下,有多个子帐号,每个子帐号都有权限操作某一块业务,当多人同时登录操作这一块业务时,一定会出现类似于多线程并发访问共享资源的问题,那该如何解决呢?
处理并发问题最常用的一个方法就是加锁,当一个请求发出,服务器正在处理时,待处理的资源就处于锁定状态,后续的相关请求被抛弃或者进入阻塞队列等待,待处理完毕资源解锁。
具体实现,我们可以使用 redis 缓存,相比于 session,它并不仅仅针对于特定用户会话,也就是说它可以处理多个用户同时提交同一类请求的情况。
每个请求都将带有表示某块资源的唯一标识KEY_NAME
,当第一次请求时,redis 会执行INCR KEY_NAME
命令,这是个原子递增操作,值变为 1,于是后续同类请求会将它依次递增为 2,3,4……..,当值大于 1 时,表示资源已在处理中,后续请求被抛弃或处于等待状态,待处理完毕,将值重新设为 0,表示资源已解锁可用。这是借助 redis 缓存实现的类加锁机制,解决多服务器多用户场景下请求重复提交的情况。
认证与会话管理
who am i
认证
是最容易理解的一种安全。如果一个系统缺乏认证手段,明眼人都能看出来这是“不安全”的。最常见的认证方式就是用户名与密码,但认证的手段却远远不止于此。
很多时候,人们会把“认证”和“授权”两个概念搞混,甚至有些安全工程师也是如此。实际上“认证”和“授权”是两件事情,
- 认证的英文是
Authentication
,授权则是Authorization
。 - 认证的目的是为了认出用户是谁,而授权的目的是为了决定用户能够做什么。
形象地说,假设系统是一间屋子,持有钥匙的人可以开门进入屋子,那么屋子就是通过“锁和钥匙的匹配”来进行认证的,认证的过程就是开锁的过程。
钥匙在认证过程中,被称为“凭证”(Credential)
,开门的过程,在互联网里对应的是登录(Login)。可是开门之后,什么事情能做,什么事情不能做,就是“授权”的管辖范围了。
但是有钥匙的人就一定是真正的主人吗?比如钥匙让被人复制了。。。这就出现了安全问题,那如何解决呢?
如何才能准确地判断一个人是谁呢?这是一个哲学问题,在被哲学家们搞清楚之前,我们只能够依据人的不同“凭证”来确定一个人的身份。钥匙仅仅是一个很脆弱的凭证,其他诸如指纹、虹膜、人脸、声音等生物特征也能够作为识别一个人的凭证。认证实际上就是一个验证凭证的过程。
如果只有一个凭证被用于认证,则称为“单因素认证”;如果有两个或多个凭证被用于认证,则称为“双因素(Two Factors)
认证”或“多因素认证”。一般来说,多因素认证的强度要高于单因素认证,但是在用户体验上,多因素认证或多或少都会带来一些不方便的地方。
密码的那些事儿
密码认证方式简单易行,但安全等级较低。而密码强度在密码认证中也是一环,可以参考OWASP
(开放式 Web 应用程序安全项目)推荐的最佳实践:
密码长度方面:
- 普通应用要求长度为 6 位以上;
- 重要应用要求长度为 8 位以上,并考虑双因素认证。
密码复杂度方面:
- 密码区分大小写字母;
- 密码为大写字母、小写字母、数字、特殊符号中两种以上的组合;
- 不要有连续性的字符,比如 1234abcd,这种字符顺着人的思路,所以很容易猜解;
- 尽量避免出现重复的字符,比如 1111。
除了 OWASP 推荐的策略外,还需要注意,不要使用用户的公开数据,或者是与个人隐私相关的数据作为密码。比如不要使用 QQ 号、身份证号码、昵称、电话号码(含手机号码)、生日、英文名、公司名等作为密码,这些资料往往可以从互联网上获得,并不是那么保密。
目前黑客们常用的一种暴力破解手段,不是破解密码,而是选择一些弱口令,比如 123456,然后猜解用户名,直到发现一个使用弱口令的账户为止。由于用户名往往是公开的信息,攻击者可以收集一份用户名的字典,使得这种攻击的成本非常低,而效果却比暴力破解密码要好很多。
密码的保存也有一些需要注意的地方。一般来说,密码必须以不可逆的加密算法,或者是单向散列函数算法,加密后存储在数据库中。这样做是为了尽最大可能地保证密码的私密性。即使是网站的管理人员,也不能够看到用户的密码。登录验证密码的过程仅仅是验证用户提交的密码哈希值,与保存在数据库中的是否一致。
目前黑客们广泛使用的一种破解 MD5 后密码的方法是“彩虹表(Rainbow Table)
”。
彩虹表的思路是收集尽可能多的密码明文和明文对应的 MD5 值。这样只需要查询 MD5 值,就能找到该 MD5 值对应的明文。一个好的彩虹表,可能会非常庞大,但这种方法确实有效。彩虹表的建立,还可以周期性地计算一些数据的 MD5 值,以扩充彩虹表的内容。
但为了避免密码泄露后,黑客通过彩虹表就查出密码明文,在计算密码明文的哈希值时,增加一个salt
(常说盐),“Salt”
是一个字符串,它的作用是为了增加明文的复杂度,并能使得彩虹表一类的攻击失效。
Salt 的使用一般为MD5(Username+Password+Salt)
,其中,Salt = abcddcba……
(随机字符串)。Salt 应该保存在服务器端的配置文件中,并妥善保管。
多因素认证
对于很多重要的系统来说,如果只有密码作为唯一的认证手段,从安全上看会略显不足。因此为了增强安全性,大多数网上银行和网上支付平台都会采用双因素认证或多因素认证。
比如支付宝的多因素认证方式,除了支付密码外,手机动态口令、数字证书、宝令、支付盾、第三方证书等都可用于用户认证。这些不同的认证手段可以互相结合,使得认证的过程更加安全。密码不再是唯一的认证手段,在用户密码丢失的情况下,也有可能有效地保护用户账户的安全。
Session 与认证
密码与证书等认证手段,一般仅仅用于登录(Login)的过程。当登录完成后,用户访问网站的页面,不可能每次浏览器请求页面时都再使用密码认证一次。因此,当认证成功后,就需要替换一个对用户透明的凭证。这个凭证,就是 SessionID。
在 Web 应用中,用户登录之后,服务器端通常会建立一个新的 Session 以跟踪用户的状态。每个 Session 对应一个标识符 SessionID,SessionID 用来标识用户身份,一般是加密保存在 Cookie 中。有的网站也会将 Session 保存在 Cookie 中,以减轻服务器端维护 Session 的压力(此时需要服务端对数据做一定处理,类似 token)。
最常见的做法就是把 SessionID 加密后保存在 Cookie中,因为 Cookie 会随着 HTTP 请求头发送,且受到浏览器同源策略的保护。
但是如果SessionID
在生命周期内别窃取,则等同于账户失窃。此时黑客也不用再攻击登录过程了,因为已经登录了。。。
如果 SessionID 是保存在 Cookie 中的,则这种攻击可以称为 Cookie 劫持。
Cookie 泄露的途径有很多,最常见的有XSS攻击
、网络Sniff
,以及本地木马窃取。对于通过XSS漏洞窃取Cookie的攻击,通过给Cookie标记httponly
,可以有效地缓解 XSS 窃取 Cookie 的问题。但是其他的泄露途径,比如网络被嗅探,或者 Cookie 文件被窃取,则会涉及客户端的环境安全,需要从客户端着手解决。
SessionID 除了可以保存在 Cookie 中外,还可以保存在 URL 中,作为请求的一个参数。但是这种方式的安全性难以经受考验。因为有时候可以通过referer
泄露 URL 中的 SessionID。
在生成SessionID 时,需要保证足够的随机性,比如采用足够强的伪随机数生成算法。现在的网站开发中,都有很多成熟的开发框架可以使用。这些成熟的开发框架一般都会提供 Cookie 管理、Session 管理的函数,可以善用这些函数和功能。
会话期 Cookie 和持久性 Cookie
会话期Cookie
是最简单的 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。会话期 Cookie 不需要指定过期时间(Expires)或者有效期(Max-Age)。需要注意的是,有些浏览器提供了会话恢复功能,这种情况下即使关闭了浏览器,会话期 Cookie 也会被保留下来,就好像浏览器从来没有关闭一样。
针对会话级Cookie
,当浏览器关闭后,是没有通知服务端的,服务端一般是在 30 分钟会销毁 session。当然如果前端页面一直活着,则后端也会自动更新 时间。
和关闭浏览器便失效的会话期 Cookie 不同,持久性Cookie
可以指定一个特定的过期时间(Expires)
或有效期(Max-Age)
/* 注意过期时间是相对于客户端 */
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
第三方 Cookie
每个 Cookie 都会有与之关联的域(Domain),如果 Cookie 的域和页面的域相同,那么我们称这个 Cookie 为第一方Cookie(first-party cookie)
,如果 Cookie 的域和页面的域不同,则称之为第三方Cookie(third-party cookie.)
。一个页面包含图片或存放在其他域上的资源(如图片广告)时,第一方的 Cookie 也只会发送给设置它们的服务器。通过第三方组件发送的第三方 Cookie 主要用于广告和网络追踪。
Session Fixation 攻击
什么是Session Fixation
呢?举一个形象的例子,假设 A 有一辆汽车,A 把汽车卖给了 B,但是 A 并没有把所有的车钥匙交给 B,还自己藏下了一把。这时候如果 B 没有给车换锁的话,A 仍然是可以用藏下的钥匙使用汽车的。
这个没有换“锁”而导致的安全问题,就是Session Fixation
问题。在用户登录网站的过程中,如果登录前后用户的 SessionID 没有发生变化,则会存在 SSession Fixation
问题。
具体攻击的过程是,用户 X(攻击者)先获取到一个未经认证的 SessionID,然后将这个 SessionID 交给用户 Y 去认证,Y 完成认证后,服务器并未更新此 SessionID 的值(注意是未改变 SessionID,而不是未改变 Session),所以 X 可以直接凭借此 SessionID 登录进 Y 的账户。
X 如何才能让 Y 使用这个 SessionID 呢?如果 SessionID 保存在 Cookie 中,比较难做到这一点。但若是 SessionID 保存在 URL 中,则 X 只需要诱使 Y 打开这个 URL 即可。
因此,解决Session Fixation 的正确做法是,在登录完成后,重写 SessionID。如果使用 sid(就是SessionID
)则需要重置 sid 的值;如果使用 Cookie,则需要增加或改变用于认证的 Cookie 值。(一般 sid 都存储在 Cookie 里,这里难道没有包含关系?)
Session 保持攻击
一般来说,Session 是有生命周期的,当用户长时间未活动后,或者用户点击退出后,服务器将销毁 Session。Session 如果一直未能失效,会导致什么问题呢?前面提到 Session 劫持攻击,是攻击者窃取了用户的 SessionID,从而能够登录进用户的账户。
但如果攻击者能一直持有一个有效的 Session(比如间隔性地刷新页面,以告诉服务器这个用户仍然在活动),而服务器对于活动的 Session 也一直不销毁的话,攻击者就能通过此有效 Session 一直使用用户的账户,成为一个永久的“后门”。
但是 Cookie 有失效时间,Session 也可能会过期,攻击者能永久地持有这个 Session 吗?
一些系统会给session
设置一个失效时间,当达到失效时间时,session
就会被销毁,但有一些系统,出于用户体验的考虑,只要这个用户还“活着”,就不会让这个用户的 Session 失效。从而攻击者可以通过不停地发起访问请求,让 Session 一直“活”下去。
在 Web 开发中,网站访问量如果比较大,维护 Session 可能会给网站带来巨大的负担。因此,有一种做法,就是服务器端不维护 Session,而把 Session 放在 Cookie 中加密保存。当浏览器访问网站时,会自动带上 Cookie,服务器端只需要解密 Cookie 即可得到当前用户的 Session 了。
这样的 Session 如何使其过期呢?很多应用都是利用 Cookie 的 Expire 标签来控制 Session 的失效时间,这就给了攻击者可乘之机。因为 Cookie 的 Expire 时间是完全可以由客户端控制的,
攻击者甚至可以为 Session Cookie 增加一个 Expire 时间,使得原本浏览器关闭就会失效的 Cookie 持久化地保存在本地,变成一个第三方 Cookie(third-party cookie),这里应该变为持久性 Cookie?。
如何对抗这种 Session 保持攻击呢?
常见的做法是在一定时间后,强制销毁 Session。这个时间可以是从用户登录的时间算起,设定一个阈值,比如 3 天后就强制 Session 过期。
但强制销毁 Session 可能会影响到一些正常的用户,还可以选择的方法是当用户客户端发生变化时,要求用户重新登录。比如用户的 IP、UserAgent 等信息发生了变化,就可以强制销毁当前的 Session,并要求用户重新登录。
最后,还需要考虑的是同一用户可以同时拥有几个有效 Session。若每个用户只允许拥有一个 Session,则攻击者想要一直保持一个 Session 也是不太可能的。当用户再次登录时,攻击者所保持的 Session 将被“踢出”
访问控制
what can i do
指出“认证(Authentication)”与“授权(Authorization)”
的不同。“认证”解决了“Who am I?”的问题,而“授权”则解决了“What can I do?”的问题。
权限控制,或者说访问控制,广泛应用于各个系统中。抽象地说,都是某个主体(subject)对某个客体(object)需要实施某种操作(operation),而系统对这种操作的限制就是权限控制。
在网络中,为了保护网络资源的安全,一般是通过路由设备或者防火墙建立基于 IP 的访问控制。这种访问控制的“主体”是网络请求的发起方(比如一台 PC),“客体”是网络请求的接收方(比如一台服务器),主体对客体的“操作”是对客体的某个端口发起网络请求。这个操作能否执行成功,是受到防火墙 ACL(Access Control List,访问控制列表,是路由器和交换机接口的指令列表,用来控制端口进出的数据包。
)策略限制的。
在操作系统中,对文件的访问也有访问控制。此时“主体”是系统的用户,“客体”是被访问的文件,能否访问成功,将由操作系统给文件设置的 ACL(访问控制列表)决定。比如在 Linux 系统中,一个文件可以执行的操作分为“读”、“写”、“执行”三种,分别由 r、w、x 表示。这三种操作同时对应着三种主体:文件拥有者、文件拥有者所在的用户组、其他用户。主体、客体、操作这三者之间的对应关系,构成了访问控制列表。
在一个安全系统中,确定主体的身份是“认证”解决的问题;而客体是一种资源,是主体发起的请求的对象。在主体对客体进行操作的过程中,系统控制主体不能“无限制”地对客体进行操作,这个过程就是“访问控制”。
在 Web 应用中,根据访问客体的不同,常见的访问控制可以分为“基于 URL 的访问控制”、“基于方法(method)的访问控制”和“基于数据的访问控制”。
在正常情况下,管理后台的页面应该只有管理员才能够访问。但这些系统未对用户访问权限进行控制,导致任意用户只要构造出了正确的 URL,就能够访问到这些页面。
在正常情况下,这些管理页面是不会被链接到前台页面上的,搜索引擎的爬虫也不应该搜索到这些页面。但是把需要保护的页面“藏”起来,并不是解决问题的办法。攻击者惯用的伎俩是使用一部包含了很多后台路径的字典,把这些“藏”起来的页面扫出来。比如上面的 4 个案例中,有 3 个其管理 URL 中都包含了“admin”这样的敏感词。而“admin”这个词,必然会被收录在任何一部攻击的字典中
在这些案例的背后,其实只需要加上简单的“基于页面的访问控制”,就能解决问题了
垂直权限管理
访问控制实际上是建立用户与权限之间的对应关系,现在应用广泛的一种方法,就是“基于角色的访问控制(Role-Based Access Control
)”,简称 RBAC。
RBAC
事先会在系统中定义出不同的角色,不同的角色拥有不同的权限,一个角色实际上就是一个权限的集合。而系统的所有用户都会被分配到不同的角色中,一个用户可能拥有多个角色,角色之间有高低之分(权限高低)。在系统验证权限时,只需要验证用户所属的角色,然后就可以根据该角色所拥有的权限进行授权了。
水平权限管理
用户 A 与用户 B 可能都属于同一个角色 RoleX,但是用户 A 与用户 B 都各自拥有一些私有数据,在正常情况下,应该只有用户自己才能访问自己的私有数据。
但是在 RBAC 这种“基于角色的访问控制”模型下,系统只会验证用户A是否属于角色RoleX,而不会判断用户A是否能访问只属于用户B的数据DataB
,因此,发生了越权访问。这种问题,我们就称之为“水平权限管理问题”。
相对于垂直权限管理来说,水平权限问题出在同一个角色上。系统只验证了能访问数据的角色,既没有对角色内的用户做细分,也没有对数据的子集做细分,因此缺乏一个用户到数据之间的对应关系。由于水平权限管理是系统缺乏一个数据级的访问控制所造成的,因此水平权限管理又可以称之为“基于数据的访问控制”。
一个简单的数据级访问控制,可以考虑使用“用户组(Group)”的概念。比如一个用户组的数据只属于该组内的成员,只有同一用户组的成员才能实现对这些数据的操作。
此外,还可以考虑实现一个规则引擎,将访问控制的规则写在配置文件中,通过规则引擎对数据的访问进行控制。
水平权限管理问题,至今仍然是一个难题——它难以发现,难以在统一框架下解决,在未来也许会有新的技术用以解决此类问题。
OAuth 简介
OAuth 是一个在不提供用户名和密码的情况下,授权第三方应用访问 Web 资源的安全协议。OAuth 1.0 于 2007 年 12 月公布,并迅速成为了行业标准(可见不同网站之间互通的需求有多么的迫切)。2010 年 4 月,OAuth 1.0 正式成为了 RFC 5849。
OAuth 与 OpenID 都致力于让互联网变得更加的开放。OpenID 解决的是认证问题,OAuth 则更注重授权。认证与授权的关系其实是一脉相承的,后来人们发现,其实更多的时候真正需要的是对资源的授权。
OAuth 委员会实际上是从 OpenID 委员会中分离出来的(2006 年 12 月),OAuth 的设计原本想弥补 OpenID 中的一些缺陷或者说不够方便的地方,但后来发现需要设计一个全新的协议。
常见的应用 OAuth 的场景,一般是某个网站想要获取一个用户在第三方网站中的某些资源或服务。
比如在人人网上,想要导入用户 MSN 里的好友,在没有 OAuth 时,可能需要用户向人人网提供 MSN 用户名和密码。
应用层拒绝服务攻击
DDOS 简介
DDOS
又称分布式拒绝服务,全称是Distributed Denial of Service
。DDOS
是利用合理的请求造成资源过载,导致服务不可用。系统的资源毕竟有限,同一时间处理事情的数量也有限,当有很多没有意义的请求同时涌进来时,就会占用正常请求的处理,当占用的多了,就表现的像似服务宕机。。。
攻击是安全领域中最难解决的问题之一,迄今为止也没有一个完美的解决方案。
.网络层 DDOS
常见的DDOS
攻击有SYN flood、UDP flood、ICMP flood
等。其中SYN flood
是一种最为经典的DDOS
攻击,其发现于 1996 年,但至今仍然保持着非常强大的生命力。SYN flood
如此猖獗是因为它利用了TCP
协议设计中的缺陷,而TCP/IP
协议是整个互联网的基础,牵一发而动全身,如今想要修复这样的缺陷几乎成为不可能的事情。
正常情况下的,TCP
三次握手过程如下:
- 客户端向服务器端发送一个
SYN
包,包含客户端使用的端口号和初始序列号 x; - 服务器端收到客户端发送来的
SYN
包后,向客户端发送一个SYN
和 ACK 都置位的 TCP 报文,包含确认号 x+1 和服务器端的初始序列号 y; - 客户端收到服务器端返回的
SYN+ACK
报文后,向服务器端返回一个确认号为 y+1、序号为 x+1 的ACK
报文,一个标准的TCP
连接完成。
而 SYN flood 在攻击时,首先伪造大量的源 IP 地址(发起请求方),分别向服务器端发送大量的 SYN 包,此时服务器端会返回 SYN/ACK 包,因为源地址是伪造的,所以伪造的 IP 并不会应答,服务器端没有收到伪造 IP 的回应,会重试 3 ~ 5 次并且等待一个 SYN Time(一般为 30 秒至 2 分钟),如果超时则丢弃这个连接。攻击者大量发送这种伪造源地址的 SYN 请求,服务器端将会消耗非常多的资源(CPU 和内存)来处理这种半连接,同时还要不断地对这些 IP 进行 SYN+ACK 重试。最后的结果是服务器无暇理睬正常的连接请求,导致拒绝服务。
对抗 SYN flood 的主要措施有 SYN Cookie/SYN Proxy、safereset 等算法。SYN Cookie 的主要思想是为每一个 IP 地址分配一个“Cookie”,并统计每个 IP 地址的访问频率。如果在短时间内收到大量的来自同一个 IP 地址的数据包,则认为受到攻击,之后来自这个 IP 地址的包将被丢弃。
一般来说,大型网站之所以看起来比较能“抗”DDOS 攻击,是因为大型网站的带宽比较充足,集群内服务器的数量也比较多。但一个集群的资源毕竟是有限的,在实际的攻击中,DDOS 的流量甚至可以达到数 G 到几十 G,遇到这种情况,只能与网络运营商合作,共同完成 DDOS 攻击的响应。
注意:上面说的攻击是网络层的攻击,因为此时连接还没有建立(仍然停留在TCP
网络层)
.应用层 DDOS
应用层 DDOS,不同于网络层 DDOS,由于发生在应用层,因此 TCP 三次握手已经完成,连接已经建立,所以发起攻击的 IP 地址也都是真实的。但应用层 DDOS 有时甚至比网络层 DDOS 攻击更为可怕,因为今天几乎所有的商业 Anti-DDOS 设备,只在对抗网络层 DDOS 时效果较好,而对应用层 DDOS 攻击却缺乏有效的对抗手段。
应用层DDOS
什么意思呢?可以从CC攻击
说起:
“CC攻击”
的前身是一个叫 fatboy 的攻击程序,当时黑客为了挑战绿盟的一款反 DDOS 设备开发了它。绿盟是中国著名的安全公司之一,它有一款叫“黑洞(Collapasar)”的反 DDOS 设备,能够有效地清洗 SYN Flood 等有害流量。而黑客则挑衅式地将 fatboy 所实现的攻击方式命名为:Challenge Collapasar
(简称 CC),意指在黑洞的防御下,仍然能有效完成拒绝服务攻击。
CC 攻击的原理非常简单,就是对一些消耗资源较大的应用页面不断发起正常的请求,以达到消耗服务端资源的目的。
在 Web 应用中,查询数据库、读/写硬盘文件等操作,相对都会消耗比较多的资源。比如并发频繁触发数据库查询,查询无法立即完成,资源无法立即释放,会导致数据库请求连接过多,数据库阻塞,网站无法正常打开。
在互联网中充斥着各种搜索引擎、信息收集等系统的爬虫(spider)
,爬虫把小网站直接爬死的情况时有发生,这与应用层 DDOS 攻击的结果很像。由此看来,应用层 DDOS 攻击与正常业务的界线比较模糊。
应用层攻击还可以通过将大流量的网站分流一部分完成,比如黑客篡改大流量页面
<iframe src="http://target" height="0" width="0"></iframe>
这样,只要用户打开了大流量页面,都会对target
发起一次HTTP GET
请求,就可能导致target
拒绝服务。
许多优化服务器性能的方法,或多或少的能缓解这种攻击,比如将使用频率高的数据放在memcache
中,比查询数据库效率高出很多。但如果黑客想触发耗性能的操作,只需想法命中memcache
里没有的数据即可,这样便会触发查询数据库。
.IP&Cookie 防御
通过 IP 地址与 Cookie 定位一个客户端,如果客户端的请求在一定时间内过于频繁,则对之后来自该客户端的所有请求都重定向到一个出错页面。
从架构上看,这段代码需要放在业务逻辑之前,才能起到保护后端应用的目的,可以看做是一个“基层”的安全模块。
.道高一尺,魔高一丈
然而这种防御方法并不完美,因为它在客户端的判断依据上并不是永远可靠的。这个方案中有两个因素用以定位一个客户端:一个是 IP 地址,另一个是 Cookie。但用户的 IP 地址可能会发生改变,而 Cookie 又可能会被清空,如果 IP 地址和 Cookie 同时都发生了变化,那么就无法再定位到同一个客户端了。
如何让 IP 地址发生变化呢?使用“代理服务器”是一个常见的做法。在实际的攻击中,大量使用代理服务器或傀儡机来隐藏攻击者的真实 IP 地址,已经成为一种成熟的攻击模式。攻击者使用这些方法可不断地变换 IP 地址,就可以绕过服务器对单个 IP 地址请求频率的限制了。
.几个方面防御
- 首先,应用代码要做好性能优化。合理地使用 memcache 就是一个很好的优化方案,将数据库的压力尽可能转移到内存中。此外还需要及时地释放资源,比如及时关闭数据库连接,减少空连接等消耗。
- 其次,在网络架构上做好优化。善于利用负载均衡分流,避免用户流量集中在单台服务器上。同时可以充分利用好 CDN 和镜像站点的分流作用,缓解主站的压力。
- 最后,也是最重要的一点,实现一些对抗手段,比如限制每个 IP 地址的请求频率。
.验证码
验证码是互联网中常用的技术之一,它的英文简称是CAPTCHA(Completely Automated Public Turing Test to Tell Computers and Humans Apart,全自动区分计算机和人类的图灵测试)
。在很多时候,如果可以忽略对用户体验的影响,那么引入验证码这一手段能够有效地阻止自动化的重放行为。
CAPTCHA
设计的初衷是为了识别人和机器,过于简单和过于复杂都不太好,因此是把双刃剑。
有验证码,就会有验证码破解技术。除了直接利用图像相关算法识别验证码外,还可以利用 Web 实现上可能存在的漏洞破解验证码。
因为验证码的验证过程,是比对用户提交的明文和服务器端 Session 里保存的验证码明文是否一致。所以曾经有验证码系统出现过这样的漏洞:因为验证码消耗掉后 SessionID 未更新,导致使用原有的 SessionID 可以一直重复提交同一个验证码。(难道是登陆后 SessionID 未更新?)
还有的验证码实现方式,是提前将所有的验证码图片生成好,以哈希过的字符串作为验证码图片的文件名。在使用验证码时,则直接从图片服务器返回已经生成好的验证码,这种设计原本的想法是为了提高性能。
但这种一一对应的验证码文件名会存在一个缺陷:攻击者可以事先采用枚举的方式,遍历所有的验证码图片,并建立验证码到明文之间的一一对应关系,从而形成一张“彩虹表”,这也会导致验证码形同虚设。修补的方式是验证码的文件名需要随机化,满足“不可预测性”原则。
.防御应用层 DDOS
一种比较可靠的方法是让客户端解析一段 JavaScript,并给出正确的运行结果。因为大部分的自动化脚本都是直接构造 HTTP 包完成的,并非在一个浏览器环境中发起的请求。因此一段需要计算的 JavaScript,可以判断出客户端到底是不是浏览器。类似的,发送一个 flash 让客户端解析,也可以起到同样的作用。但需要注意的是,这种方法并不是万能的,有的自动化脚本是内嵌在浏览器中的“内挂”,就无法检测出来了。
除了人机识别外,还可以在 web server 这一层做些防御,其好处是请求尚未到达后端的应用程序里,因此可以起到一个保护的作用。
在 Apache 的配置文件中,有一些参数可以缓解 DDOS 攻击。比如调小 Timeout、KeepAliveTimeout 值,增加 MaxClients 值。但需要注意的是,这些参数的调整可能会影响到正常应用,因此需要视实际情况而定。
如果黑客使用了代理服务器、傀儡机进行攻击,该如何有效地保护网站呢?
Yahoo 为我们提供了一个解决思路。因为发起应用层 DDOS 攻击的 IP 地址都是真实的,所以在实际情况中,攻击者的 IP 地址其实也不可能无限制增长。假设攻击者有 1000 个 IP 地址发起攻击,如果请求了 10000 次,则平均每个 IP 地址请求同一页面达到 10 次,攻击如果持续下去,单个 IP 地址的请求也将变多,但无论如何变,都是在这 1000 个 IP 地址的范围内做轮询。
为此 Yahoo 实现了一套算法,根据 IP 地址和 Cookie 等信息,可以计算客户端的请求频率并进行拦截。Yahoo 设计的这套系统也是为 Web Server 开发的一个模块,但在整体架构上会有一台 master 服务器集中计算所有 IP 地址的请求频率,并同步策略到每台 WebServer 上。
.资源耗尽攻击
. Slowloris攻击
Slowloris 是在 2009 年由著名的 Web 安全专家 RSnake 提出的一种攻击方法,其原理是以极低的速度往服务器发送 HTTP 请求。由于 Web Server 对于并发的连接数都有一定的上限,因此若是恶意地占用住这些连接不释放,那么 Web Server 的所有连接都将被恶意连接占用,从而无法接受新的请求,导致拒绝服务。
在正常的 HTTP 包头中,是以两个 CLRF 表示 HTTP Headers 部分结束的。Content-Length: 42\r\n\r\n
,但恶意请求只包含一个 CLRF。。。
GET / HTTP/1.1\r\n
Host: host\r\n
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n
Content-Length: 42\r\n
由于 Web Server 只收到了一个\r\n,因此将认为 HTTP Headers 部分没有结束,并保持此连接不释放,继续等待完整的请求。此时客户端再发送任意 HTTP 头,保持住连接即可。
当构造多个连接后,服务器的连接数很快就会达到上限。此类拒绝服务攻击的本质,实际上是对有限资源的无限制滥用。上面案例中,有限资源就是web server
的连接数。
. HTTP POST DOS攻击
原理是在发送 HTTP POST 包时,指定一个非常大的 Content-Length 值,然后以很低的速度发包,比如 10 ~ 100s 发一个字节,保持住这个连接不断开。这样当客户端连接数多了以后,占用住了 Web Server 的所有可用连接,从而导致 DOS。
攻击的本质是针对MaxClients
有限制的。。。因此,我们可以想到凡是资源有限制的地方,都可能发生资源滥用,从而导致拒绝服务。因此对于不可信任的资源使用者需要额外的限制。
另外内存泄露是程序员经常需要解决的问题,而在安全领域,内存泄露则被认为是能够造成拒绝服务攻击的方式。
. Server Limit DOS攻击
Cookie 也能造成一种拒绝服务,笔者称之为 Server Limit DOS,并曾在笔者的博客文章中描述过这种攻击。
Web Server 对 HTTP 包头都有长度限制,以 Apache 举例,默认是 8192 字节。也就是说,Apache 所能接受的最大 HTTP 包头大小为 8192 字节(这里指的是 Request Header,如果是 Request Body,则默认的大小限制是 2GB)。如果客户端发送的 HTTP 包头超过这个大小,服务器就会返回一个 4xx 错误,提示信息为:
Your browser sent a request that this server could not understand.
Size of a request header field exceeds server limit.
因此如果攻击者通过XSS
攻击,恶意地向客户端写入了一个超长的 cookie,在清苦 cookie 之前,将无法访问 cookie 所在域的任何页面。(当然要解决的话,需要服务器取消对 HTTP 头大小的限制)
. 正则引发的攻击
与前面提到的资源耗尽攻击略有不同的是,ReDOS 是一种代码实现上的缺陷。我们知道正则表达式是基于NFA(Nondeterministic Finite Automaton)
的,它是一个状态机,每个状态和输入符号都可能有许多不同的下一个状态。正则解析引擎将遍历所有可能的路径直到最后。由于每个状态都有若干个“下一个状态”,因此决策算法将逐个尝试每个“下一个状态”,直到找到一个匹配的。
比如这个正则表达式,当输入只有aaaax
时,执行过程如下图:
var newReg = /^(a+)+$/;
也就是 2^4 种可能,若是 16 个 a,则是 2^16 种可能。。。这极大地增加了正则引擎解析数据时的消耗。当用户恶意构造输入时,这些有缺陷的正则表达式就会消耗大量的系统资源(比如 CPU 和内存),从而导致整台服务器的性能下降,表现的结果是系统速度很慢,有的进程或服务失去响应,与拒绝服务的后果是一样的。
web server 配置安全
简介
Web 服务器是 Web 应用的载体,如果这个载体出现安全问题,那么运行在其中的 Web 应用程序的安全也无法得到保障。因此 Web 服务器的安全不容忽视。
Web 服务器安全,考虑的是应用布署时的运行环境安全。这个运行环境包括 Web Server、脚本语言解释器、中间件等软件,这些软件所提供的一些配置参数,也可以起到安全保护的作用。
Apache 安全
尽管近年来 Nginx、LightHttpd 等 Web Server 的市场份额增长得很快,但 Apache 仍然是这个领域中独一无二的巨头,互联网上大多数的 Web 应用依然跑在 Apache Httpd 上。
Web Server 的安全我们关注两点:
- Web Server 本身是否安全;
- Web Server 是否提供了可使用的安全功能。
纵观 Apache 的漏洞史,它曾经出现过许多次高危漏洞。但这些高危漏洞,大部分是由 Apache 的 Module 造成的,Apache 核心的高危漏洞几乎没有。Apache 有很多官方与非官方的 Module,默认启动的 Module 出现过的高危漏洞非常少,大多数的高危漏洞集中在默认没有安装或 enable 的 Module 上。
需要注意的是,Apache 以 root 身份或者 admin 身份运行是一个非常糟糕的决定。这里的 admin 身份是指服务器管理员在管理机器时使用的身份。这个身份的权限也是比较高的,因为管理员有操作管理脚本、访问配置文件、读/写日志等需求。
Ngnix 安全
近年来 Nginx 发展很快,它的高性能和高并发的处理能力使得用户在 Web Server 的选择上有了更多的空间。但从安全的角度来看,Nginx 近年来出现的影响默认安装版本的高危漏洞却比 Apache 要多。
就软件安全本身来看,Nginx 与 Apache 最大的区别在于,检查 Apache 安全时更多的要关注 Module 的安全,而 Nginx 则需要注意软件本身的安全,及时升级软件版本。
- 首先,Nginx 的配置非常灵活,在对抗 DDOS 和 CC 攻击方面也能起到一定的缓解作用,
- 其次,在 Nginx 配置中还可以做一些简单的条件判断,比如客户端 User-Agent 具有什么特征,或者来自某个特定 referer、IP 等条件,响应动作可以是返回错误号,或进行重定向。