常见攻击手段
安全的Web服务/接口/应用设计原则:
- 默认安全 Secure by Default: 白名单, 黑名单, 最小权限原则
- 纵深防御 Defense in Depth: 从不同层面, 不同角度, 构筑多层次的安全防御体系. 纵深防御不是简单的重复, 是通过各种角度审视系统, 从各个方面选择最合适的防御手段, 最终形成稳定的整体防御解决方案. 例如: 系统的安全防护包括Web应用安全, OS安全, 数据库安全, 网络环境安全等, 针对每个具体的场景设计最合适的防御手段, 而不是只在系统的门户加一把大锁, 每个模块, 每个功能, 每个组件都要进行安全防御.
- 数据与代码分离: 尽可能地压缩用户能够实际操作的内容, 只暴露最基本的功能接口
- 不可预测: 微软通过ASLR技术让进程的栈地址随机初始化, 这样攻击者就很难预测某个进程所处的内存区域, 从而提升了攻击门槛. 此外, 不可预测原则也有利于Web安全防护, BiliBili之前使用连续的AV号作为视频资源的标识, 目前换成了加密后的BV号, 虽然防君子不防小人, 但是这显著提升了批量攻击的成本
SQL注入, CRLF注入, X-Path注入, JavaScript注入
跨站脚本攻击(XSS)
跨站脚本攻击(Cross Site Scripting, XSS)
反射型XSS: 链接参数触发, DOM触发
存储型XSS: 所访问的网页本身就就存在恶意代码, 访问后直接就执行, 这种XSS是持久化的
危害: Cookie劫持(自动登录), 模拟GET/POST操作(读取邮件列表), CSS历史钩子(访问过的网站的超链接是红色的), XSS Worm(蠕虫)
理论上, js无法控制浏览器发出的HTTP请求中的字段, 但flash却可以, 因此大部分漏洞都是通过flash触发的.
XSS的防御:
- HttpOnly选项: 用于防御XSS对Cookie的劫持. 设置了HttpOnly选项的Cookie无法被js读取, 只能作为HTTP传输时的数据使用. 当然, 这只是浏览器级别的访问控制, 最早被IE浏览器所实现. 在实际使用时, 需要为每个Cookie都开启该选项(可能同时存在多个Cookie), 但在业务场景过于复杂导致使用大量Cookie的情况下, 仍然可能出现漏标记的情况. HttpOnly可以被绕过, 例如针对Apache服务器可以使用HTTP的TRACE方法返回请求头, 尽管这是用于调试的方法, 但是攻击者可以让返回的请求头承载Cookie数据, 绕开了HttpOnly-Cookie对js的访问控制.
- 输入检查: 限制用户输入, 不允许输入可能造成注入攻击的特殊字符, 或者对用户的输入进行语义分析. 输入检查的筛选范围过大会影响正常的用户输入, 而力度过小又会产生漏网之鱼, 因此一般只被当作初级防御手段.
- 输出检查: 攻击者试探出输入限制之后, 总会有办法实现对服务器的攻击. 或者说, 输入检查是防君子不防小人, 攻击者通过不断尝试, 总会有办法找到漏洞. 因此需要将用户输入的变量中的特殊字符(
&,<,>等)进行转码后再嵌入HTML页面. 常用的安全编码标准为ISO-8859-1. 此外, 浏览器默认会为URL编码, 但却不会为URL的协议类型与主机名编码, 这就导致了攻击者可以对整个URL进行替换(如: 将javascript伪造成协议类型执行脚本), 更安全的做法是检测URL是否以HTTP开头, 直接过滤掉其他协议. - 多次编码: 针对DOM Based XSS需要更复杂的防御逻辑. 首先, 用户输入的变量在嵌入
<script>标签时需要执行一次jsEncode, 然后根据DOM嵌入的位置决定下一次编码的内容. 如果DOM是事件或脚本, 则需要对完整的DOM再做一次jsEncode; 如果DOM是拼接的HTML内容, 则需要做一次HTMLEncoder.
跨站点请求伪造(CSRF)
攻击者诱导用户访问能够自动执行恶意请求的页面, 从而伪造成访问者实现跨站点的攻击:
- 诱导带有权限的用户访问恶意站点
- 页面中包含一张无法显示的图片
<img src="需要用户权限的恶意代码"> - 攻击者以该用户的身份在第三方站点中执行了一次操作
解决方法:
- 验证码
- Referer Check
- Anti-CSRF Token
点击劫持
点击劫持是一种视觉上的欺骗手段. 攻击者使用透明的iframe覆盖在网页或某些控件上, 并诱导用户在网页上操作, 让用户在不知情的情况下点击iframe.
其中最出名的是Flash点击劫持, 通过一系列诱导操作最终能够打开本地用户的摄像头, 窃取个人隐私.
点击劫持的变种:
- 覆盖攻击. 直接在网页上覆盖原始资源, 例如: 图片, 文字, 视频等. 用于显示恶意信息或散布广告.
- 拖拽劫持.
- 触屏劫持.
点击劫持的防御:
- 禁止跨域iframe: 一刀切, 有些正常的页面, 如第三方嵌入视频, 可能会受到影响
- 禁止嵌套iframe: 用js判断是否有嵌套iframe, 但是js本身就容易被篡改
- X-Frame-Options选项: HTTP头部字段, 专门用于解决点击劫持问题, 在HTTP层面控制浏览器对iframe的加载. 当值为DENY时会拒绝一切frame的加载; 当值为SAMEORIGIN时, 只会允许加载同源的frame页面; 当值为ALLOW0-FROM时, 则可以自定义允许frame加载的页面地址.
HTML5安全
新标签
跨窗口传递消息(postMessage)
web storage: 浏览器关闭就失效的Session storage 与具有最大生存时间的Local storage
DoS与
reboot
注入攻击
SQL注入是最经典的注入攻击手段, 服务器开启错误回显是很危险的, 攻击者可能根据会显的信息得知数据库信息.
即使关闭错误回显, 攻击者也能通过盲注(Blind Injection)实现对数据库的攻击
Timing Attack
防御方法:
- 预编译SQL: 让用户无法篡改SQL语义, 只能提供字段信息, 即使输入
tom' or 1=1也只会被当作一个字段来处理, 因为语句的主体已经被编译完成. - 使用存储过程: 存储过程可以传入参数, 能够实现简单的输入过滤, 但是安全性不如预编译SQL. MySQL中提供了安全函数, 能够对输入的参数中的特殊字符进行编码, 这样存储过程的安全性就得以保证.
- 检查数据类型: 用户的输入都是由业务所控制的, 因此可以在业务层面限制并过滤用户的输入格式, 例如: 数据类型, 字符格式等. 尽管静态类型检查能够最大程度地过滤注入攻击, 但在某些无规律的字符串输入环境下, 仍然有发生SQL注入的风险.
- 最小权限原则: 根据业务的应用场景最小化所使用的查询用户的权限, 不要为了方便而全部使用root用户
除了SQL注入之外, 在Web环境中还可能发生:
- XML注入, 所有带格式的存储都可以被注入.
- CRLF注入. CR代表
\r, LF代表\n. 一般在日志文件中, 如果不对这两种字符进行特殊编码则容易让攻击者伪造日志记录. - 代码注入. JS中提供的
eval函数就非常危险, 在常规业务中不建议使用.
文件上传漏洞
尽量避免黑名单, 使用白名单
但白名单也并不安全, 通过截断字符串同样可以绕过文件上传的后缀检查. 例如: 上传xxx.php[\0].jpg, 服务器在处理时会自动截断[\0]后的字符, 最终会变为php文件.
早期的Apache服务器采用从后向前遍历后缀, 直到可识别的后缀为止. 如果在业务层面定义了rar为合法的后缀, 但没有修改Apache配置文件, 那么攻击者构造并上传xxx.php.rar.rar就能够让服务器执行这个脚本.
防御手段:
- 将文件上传的目录设置为不可执行任何文件
- 对接收的文件类型进行二次判断
- 随机修改上传的文件名与路径. 攻击者上传文件之后需要执行, 通过随机数扰乱文件名将显著提升攻击成本.
- 使用单独的服务器存储上传的文件, 让恶意脚本与主服务器实现物理隔离. 也可以给存储文件的服务器提供单独的域名, 因为执行跨域脚本的成本将更高.
浏览器安全
同源策略: host(或IP), 子域名, 端口, 协议都相同的资源才被浏览器认为同源. 来自不同源的对象无法互相干扰, 也就是说, 只有当前域加载的资源才可以修改当前页的表现形式.
- 在
a.com/index_a.html中加载b.com/b,js可以修改index_a.html中的内容, 因为b,js被a.com/加载, 与index_a.html同源 - 在
b.com/index_b.html中加载b.com/b,js显然不可以直接修改属于a.com源的index_a.html中的内容, 这就是跨域
<script> <img> <iframe> <link>等标签都可以跨域加载资源, 因此不受同源策略的限制. 而使用XMLHttpRequest或AJAX加载资源时就需要在HTTP头中设置Access-Control-Allow-Origin
恶意网址拦截: 浏览器周期性地从可信服务器中获取恶意网址黑名单, 但是有时效性, 只能作为简单的过滤手段.
浏览器沙箱: 多进程模型, 包括浏览器内核进程, 渲染进程, 插件进程, 扩展进程. 其中, 渲染进程中包含实际的web内容, 默认是不可信的. 一旦恶意网站拦截失效, 浏览器的渲染进程可能就会执行<script>或<iframe>标签中包含的攻击脚本. 现代浏览器都提供了隔离web内容(主要针对js和flash)的sandbox. 能够与操作系统运行时隔离, 与浏览器内核通信时也会校验信息的安全性. 多进程模型的好处是安全, 并且网页崩溃只会导致一个tab页退出, 而不会导致整个浏览器会话被强制终止; 但问题在于IPC代价较高, 且进程维护与切换的代价较高, 因此对宿主机的资源占用相对其他浏览器也要更高.
加密技术
| 层级 | 加密技术 |
|---|---|
| 应用层 | SSH, SSL/Telnet, PET等远程登录; PGP, S/MIME等加密邮件 |
| 传输层 | SSL/TLS(跨越应用层与传输层), SOCKS V5 加密 |
| 网路层 | IPsec |
| 链路层 | Ethernet, WAN加密, PPTP(PPP) |
通过因特网传输的数据分组可能会被攻击者窃听, 甚至直接修改数据分组的内容后再发给接收方. 这种中间人攻击可能发生在WEB网络通信的任何阶段, 例如: 对DNS或HTTP的劫持. 安全通信(secure communication)需要具备以下特性:
- 机密性(confidentiality). 仅有发送方和预期的接收方能够理解传输报文的内容. 只要加密手段足够复杂, 即使窃听者截获了报文, 也无法在有生之年暴力破解该报文.
- 报文完整性(message integrity). 预期的接收方能够完整无误地接收到发送方传输的报文. 窃听者截获报文之后, 即使无法破解, 也可以篡改其中的比特位或新增数据, 需要借助校验和等技术来保证传输数据的完整性.
- 端点鉴别(end-point authentication). 发送方和接收方需要具备能够确定对方是预期终端的能力. 不仅需要保证数据被传递到正确的终端, 还需要防止大量恶意终端占用服务器资源(DDoS).
- 运行安全性(operational security). 通信链路中的主机具备自主防护能力. 通过防火墙等设备反制网络攻击.
密码学领域的做法是, 通过加密算法(encryption algorithm)将明文(plaintext)报文转换为密文(ciphertext). 现代密码系统中一般采用通用的加密算法, 这些算法对发送方, 接收方甚至是攻击者都是已知的.
加密的关键在于被称为密钥(key)的隐藏信息. 以$A$和$B$代表通信双方, 令它们拥有的密钥分别为$K_{A}$和$K_{B}$. 假设使用SHA-256作为加密算法, 加密函数为${S}_{256}$, 解密函数为$\hat{S}_{256}$.以主机$A$向主机$B$发送分组$m$的过程为例:
- 主机$A$构造加密报文: $\hat{m} = {S}_{256}(m, K_{A})$
- 主机$B$使用对应的解密算法解密报文: $m = \hat{S}_{256}(\hat{m}, K_{B})$
在对称密钥系统中, $K_{A}$与$K_{B}$相同且理论上只有通信双方可知. 对称加密算法一般是分块加密的, 常见的有DES, 3DES, AES. 而在非对称密钥系统中, 每个通信方分别持有由公钥和私钥构成的密钥对, 其中公钥$K_P$是公开的, 而私钥则理论上只有对应的主机可知. 非对称加密的一般流程如下:
- 主机$A$随机生成私钥$K_A$, 并向主机$B$请求公钥$K_P$
- 主机$B$返回公钥$K_P$,
- 主机$A$根据公钥$K_P$加密自己的私钥$K_A$, 并将加密结果发送给主机$B$
- 主机$B$根据私钥$K_B$解密报文, 得到主机$A$的私钥$K_A$

上述过程成立的前提是: 使用主机$B$提供的公钥$K_P$加密后的数据只能被主机$B$的私钥$K_B$解密, 这样主机$A$的私钥才可以安全地传输到主机$B$. 在之后的通信过程中, 主机$A$和主机$B$都使用私钥$K_A$加密或解密数据. RSA是最经典的非对称加密算法, 但是速度较慢.
在机密性已经被保证的前提下, 需要验证报文的完整性以确保数据的发送源是期望用户, 以及数据在传输过程中没有被篡改. 通过使用密码散列函数, 报文鉴别码, 数字签名等手段可以最大程度地保证报文完整性.
公钥认证 CA 证书 端点鉴别
TCP协议在设计之初并没有考虑安全性, 而随着因特网环境的日渐恶化, 被称为安全套接字层(Secure Socket Layer, SSL)的TCP安全强化技术逐渐成为了实际应用中的标准, 并最终以传输层安全性(Transport Layer Security, TLS)的形式收录于RFC 4346.
尽管SSL完全在应用层中实现, 但从逻辑角度看, 仍然可以将SSL视作位于应用层与传输层之间的子层(sub-layer). 应用层与SSL之间通过安全套接字(Secure Socket, SS)传递数据, SSL与运输层之间通过普通套接字传递数据. SSL的作用在于, 向应用层提供了SS接口, 能够加密应用要传递的数据; 向下保留了原始的socket传递方式, 将加密后的数据以二进制的形式放入socket交给TCP去运输, 保证了兼容性.
在通过三次握手建立TCP连接之后, 还需要再经过三次握手建立SSL连接, 在连接过程中协商所采用的加密算法以及得到主密钥(MS).
- 客户发送它支持的加密算法列表, 以及一个客户端不重数
- 服务器将选择的对称加密算法, 非对称加密算法, MAC算法, CA证书以及服务端不重数返回给客户.
- 客户端验证该证书的合法性, 并从中提取服务器的公钥, 然后生成前主密钥(Pre-Master Secret, PMS). 接下来使用公钥加密PMS, 并将加密后的PMS发送给服务器.
- 服务器解密报文得到PMS, 这时客户端和服务器都已经持有了PMS, 因此其升级为主密钥(Master Secret, MS)
其中, 不重数用于防止连接重放攻击. 由于握手的前两步是以明文方式传递数据, 因此为了避免中间人攻击, 除了发送不重数之外, 客户端与服务端还需要额外交换并确认握手过程中所有报文的MAC地址. 在SSL断开时, 如果直接将TCP的FIN报文断开视为连接结束的标志则容易引发截断攻击(truncation attack), 即攻击者向服务器发送明文FIN报文让TCP连接提前断开. SSL标准中给出的方案是在SSL记录的类型字段中设置连接关闭标签.
身份认证技术
认证(Authentication): Who am I? 认出用户是谁, 认证的过程就是验证凭证的过程
授权(Authorization): What can I do? 决定用户能做什么
去酒店前台凭借身份证进行旅客身份认证, 成功认证后会获得某个房间的房卡, 房卡决定了你能打开哪间房门, 也就是前台通过房卡给已经通过认证的用户授权.
认证与会话管理
密码应具有适当的复杂度. 密码必须以不可逆的加密算法, 或者是单向散列函数算法, 加密后存储在数据库中. 这样即使数据库信息泄露, 攻击者也不能拿到明文的密码. 但是密文存储的数据通过”撞库”依然能够获取明文, 这就要求密码加密时最好加些特殊的混淆(加盐), 甚至是采用自定义的特殊加密算法. 此外, 破解简单的加密算法, 例如: MD5, 可以通过构建彩虹表的方式完成, 本质就是暴力构建密码字典, 并通过比对加密结果得到明文, 彩虹表破解也可以通过加盐来应对.
重要的数据应该使用多重认证. 除密码外还可以使用设备编号, 数字证书, 动态口令等.
用户 Session 管理
授权与访问控制
垂直权限管理: 又称基于角色的访问控制(Role-Based Access Control, RBAC), 这是最基本的访问控制模型, 每个用户被赋予不同等级的角色, 因此拥有对应级别的权限. Spring MVC中的Spring Security就是典型的RBAC管理器, 在系统验证权限时, 只需要验证用户所属的角色即可. 具体实现包括基于URL的访问控制以及基于Method的访问控制, Spring Security能够控制每种角色所能访问的URL或方法. 在使用RBAC框架时要注意满足最小权限原则以及默认拒绝策略.
水平权限管理: 同一类角色能够访问相同层面的数据, 但是数据可能归属于不同用户, 因此需要水平权限控制. 例如: A和B都是普通用户, 都能访问私信数据, 但理论上A只能访问A的私信, B只能访问B的私信, 仅仅采用RBAC是无法对同一层面的数据进行水平细分的, 即A能够访问到B的私信, 这显然存在安全隐患. 解决办法是通过用户组, 规则引擎等手段额外实现权限的水平细分.
OAuth协议是授权协议, 1.0版本被定义在RFC 5849中: 传统的第三方授权需要向网站提供登录凭证(例如: 用户名和密码), 这显然存在极大的风险. 而OAuth协议则通过Token转移而免除了向服务提供者提供登录凭证的危险做法, 并让用户能够严格限制服务提供者的访问权限, 同时由于传输基于HTTPs, 也几乎不存在Token泄漏的风险.
然而, 最初的OAuth 1.0是由Google的工程师设计的, 但在实际使用中发现存在session fixation漏洞, 即session可能被重复使用, 因此推出了OAuth 1.0a版本修复了该问题. 后来发现OAuth的验证流程过于复杂, 因此推出了更安全且简化了授权流程的OAuth 2.0版本.
2.0版本提供了针对四类应用场景的不同验证流程,
其授权流程如下:
- 服务提供者向用户索取权限, 例如: 访问谷歌相册(Resource Server, RS)
- 用户同意授予该权限, 并向服务提供者发送同意授权证明(proof)
- 服务提供者拿着用户给予的proof向谷歌请求授权
- 谷歌验证proof的合法性, 通过后就仅仅将用户谷歌相册的访问权授予服务提供者, 即返回Token

图中Client就是服务提供者, 用户就是资源拥有者(Resource Owner, RO), 谷歌提供授权服务(Authorization Server, AS), 谷歌相册提供资源(Resource Server, RS). 本质上, Client成为了用户的代理, 并且被严格限制了资源的访问权限.
虚拟专用网
虚拟专用网(Virtual Private Network, VPN)基于IPsec. IPsec在IP首部后面追加封装安全有效载荷和认证首部来实现对数据的加密. 发送报文时通过上述内容加密, 接受数据时需要解密上述内容, 因此攻击者在没有密钥的情况下很难破解分组内容, 即使篡改了数据也很快就会被发现.
IEEE802.1X
链路层终端认证技术. 只允许已经通过验证的设备在数据链路中传递数据.
加密算法
常见的加密算法包括分组加密与流密码加密两类. 分组加密算法基于分组(block)进行操作, 原始内容被拆分为长度相同的数个分组进行迭代加密. 代表算法包括DES, 3-DES, Blowfish, IDEA, AES.
流密码加密则每次只操作一个字符, 密钥独立于消息之外, 两者通过异或实现加密与解密. 代表算法包括RC4, ORYX, SEAL. 流密码的使用中, 最常见的错误是使用同一个密钥进行多次加密/解密, 这显著降低了流密码的破解成本, 这种攻击被称为Reused Key Attack.
假设存在密钥$S$, 明文$A$和$B$, 使用异或流加密方式生成的密文分别为:
\[E(A)=A \oplus S\] \[E(B)=B \oplus S\]由于密文可以轻松获得, 则由异或运算的性质可推导出如下等式:
\[E(A) \oplus E(B) = (A \oplus S) \oplus (B \oplus S) = A \oplus B\]假设明文$A$和密文$E(A)$被攻击者所持有, 而明文$B$由于是公开的也能够被轻松地获取到, 因此攻击者可以在没有密钥$S$的情况下拿到密文$E(B)$, 因为上述公式中根本不需要$S$参与运算, 这是非常严重的安全隐患.
解决该问题的办法是给每个密文添加消息验证码(Message Authentication Code, MAC), 用于校验密文的完整性. 正常计算出来的密文可以被MAC验证, 而通过异或运算推导出来的密文无法与MAC匹配. 常用的MAC是通过哈希算法生成的, 即HMAC.
密钥或salt等用于加密的数据千万不要硬编码在代码中, 防止对可执行文件反编译获得密钥, 以及防止流动的开发人员泄漏密钥. 针对Web应用的正确密钥管理方法是将密钥保存在数据库或加密的配置文件中, 只有在运行时才将数据加载进内存. 此外, 生产环境中使用的密钥也需要与测试环境和开发环境中使用的密钥相区分.
伪随机数安全:
- Debian的ssh key根据PID生成, 而PID是有限的, 最大值为32768. 因此攻击者可以很快遍历出所有的key并构造密码表. 这次事件的影响很大, 波及OpenSSL, OpenSSH, OpenVPN等
- 将时间函数的输出结果直接用于生成随机数是十分危险的, 因为服务器的时间能够被任何可访问的用户轻易获取. 此外, 将时间函数当作随机数生成的种子也是十分危险的.
- linux中提供了/dev/random和/dev/urandom虚拟设备, 用于生成安全的随机数
针对加密算法的攻击包括:
- 唯密文攻击.
- 已知明文攻击,
- 选择明文攻击.
- 选择密文攻击,
最佳实践:
- 不要使用ECB模式
- 不要使用流密码
- 使用HMAC-SHA1代替MD5以及普通SHA-1
- 不要使用相同的key做不同的事情
- salts与IV需要随机产生
- 不要自己实现加密算法, 用安全专家已经验证并实现好的库
- 不要过度依赖第三方系统提供的保密机制
最优选择:
- 使用CBC(循环分组加密)模式的AES256用于加密
- 使用HMAC-SHA512用于完整性校验
- 使用带salt的SHA-256或SHA-512用于Hashing
Web框架安全
MVC架构:
- View层负责用户视图, 页面展示
- Controller层负责应用的逻辑实现, 接收View层传入的用户请求, 并转发给对应的Model处理
- Model层负责抽象数据模型, 完成对数据处理的请求
struts2对XSS的修复: 就是简单的模式匹配, 没有达到预想效果, 多次修复后仍然存在问题