前端安全
几种前端安全相关的
XSS,Cross Site Script,跨站脚本攻击。
原理是攻击者通过某种方式在网页上嵌入脚本代码,这样,正当用户在浏览网页或执行操作的时候,脚本被执行,就会触发攻击者预期的“不正当”行为。举个例子。在 [第 29 讲] 中我介绍了会话的原理,服务端给用户分配了一个标识身份的字符串,整个会话生命周期内有效,保存在浏览器的 Cookie 中(如果你忘记了,请回看)。
知道了原理,我们就可以针对 XSS 的特点来进行防御,比如有这样两个思路: 第一个,做好字符转义和过滤,让用户上传的文本在展示的时候永远只是文本,而不可能变成 HTML 和脚本。 第二个,控制好 Cookie 的作用范围。比如服务器在返回 Set-Cookie 头的时候,设置 HttpOnly 这个标识,这样浏览器
当然上述内容只是 XSS 原理的一个简单示意,实际的跨站脚本攻击会比这个复杂且隐蔽得多。而且,这个跨站脚本可不只是能偷偷摸摸地把用户的 Cookie 传给攻击者,还能做其它的事情,比如我下面将要介绍的 CSRF。
CSRF
CSRF,Cross-Site Request Forgery,跨站请求伪造,它指的是攻击者让用户进行非其本意的操作。CSRF 和 XSS 的最大区别在于,在 CSRF 的情况下,用户的“非其本意”的行为全部都是从受害用户的浏览器上发生的,而不是从攻击者的浏览器上挟持用户会话以后发起的。
针对 CSRF 的策略 第一种,使用 HTTP 的 Referer 头,因为 Referer 头可以携带请求的来源页面地址,这样可以根据 Referer 头鉴别出伪造的请求。 第二种,使用 token,原理上也很简单,服务端给每个表单都生成一个随机值,这个值叫做 token。Token 和我们前面讲到的用来标识用户身份的 Cookie 所不同的是,前者是对每个页面或每个表单就会生成一个新的值,而后者则是只有会话重新生成的时候才会生成。当用户正常操作的时候,这个 token 会被带上,从而证明用户操作的合法性,而如果是 CSRF 的情形,这个请求来自于一个非预期的位置,那么就不可能带有这个正确的 token。
值得注意的是,CSRF 和 XSS 不是地位均等的,具体说,在防范 CSRF 的情况下,必须首先确保没有 XSS 的问题,否则 CSRF 就会失去意义。
因为一旦用户的会话以 XSS 的方式被劫持,攻击者就可以在他自己的浏览器中假装被劫持用户而进行操作。由于攻击者在他自己的浏览器中遵循着正确的操作流程,因而这种情况下无论是 Referer 头还是 token,从服务端的角度来看都是没有问题的,也就是说,当 XSS 被攻破,所有的 CSRF 的防御就失去了意义。
SQL 注入
SQL 注入,指的是攻击者利用网站漏洞,通过构造特殊的嵌入了 SQL 命令的网站请求以欺骗服务器,并执行该恶意 SQL 命令。
知道了原理,那么我们就可以采取相应的措施来应对了: 第一种,对于参数进行转义和过滤,这和我们前面介绍的 XSS 的应对是一样的。如果参数的格式明确,我们应当较为严格地校验参数,比如上面的例子,如果 id 实际是一个数值,那么用户输入非数值就应当报错。 第二种,SQL 的语句执行尽可能采用参数化查询的接口,而不是单纯地当做字符串来拼接。当然,一般在使用持久化框架的时候,这样的事情框架一般都替程序员考虑到了。 第三种,严格的权限控制,这和 Linux 下面权限控制的原则是一样的,保持“最小授权”原则,即尽可能只授予能实现功能的最小权限。
HTTP 劫持
HTTP 劫持的原理很简单,但是却非常常见。就是说,由于 HTTP 协议是明文通信的,它就可以被任意篡改。而干这事儿干得最多的,不是什么传统意义上的“黑客”,而是那些无良的网络服务提供商和运营商们,他们利用对网络控制之便利,通过这种方式强行给用户塞广告。
对于 HTTP 劫持,由于攻击者利用了 HTTP 明文传输的特性,因此解决方案很简单,就是将网站切换为 HTTPS。至于其它的方法,相对都比较特例化,并不一般和通用,只有将传输加密才是最理想的解决方案。
DNS 劫持
用户的浏览器在通过 DNS 查询目标域名对应的 IP 地址的时候,会被攻击者引导到一个恶意网站的地址。这个假的网站也可以有相似的页面布局,也可能有“正规”方式申请的 HTTPS 证书,换言之,HTTPS 加密通信并不能防范 DNS 劫持,因此用户很可能被欺骗而不察觉。
DNS 假如被劫持了,浏览器都去和一个假冒的网站通信了,HTTPS 加密做的也只是保证你和这个假冒网站通信的完整性、保密性,那还有何用?就好比要去药店买药,可去了家假的药店,那么保证整个交易过程的安全性就失去了它原本的意义了
安全防范的各个环节就像一个木桶的各个木板,网络公共服务的安全性,经常决定了用户网上冲浪安全性的上限
事实上,安全防范的各个环节就像一个木桶的各个木板,网络公共服务的安全性,经常决定了用户网上冲浪安全性的上限。2010 年的百度被黑事件,就是遭遇了 DNS 劫持。由于 DNS 解析的过程比较长,劫持可能发生在网络,也可以发生在本机(别忘了本机有 hosts 文件),还可能发生在某一个子网的路由。对于 DNS 网络明文通信带来的隐患,有一个安全的域名解析方案,叫做 DNS over HTTPS,目前还在实验阶段,仅有部分 DNS 服务支持。
DDoS 攻击
最后我来简单介绍一下 DDoS,Distributed Denial-of-Service,分布式拒绝服务,这种攻击方式从理论上说,最难以防范,被称为互联网的“癌症”。
为什么呢?因为它的原理是,攻击者使用若干被“攻陷”的电脑(比如被病毒占领和控制的“肉鸡”),向网络应用和服务同一时间发起请求,通过一瞬间的请求洪峰,将服务冲垮。
DDoS 攻击的目的不是偷窃用户数据,也不是为了仿冒用户身份,而是“无差别”阻塞网络,引发“拒绝服务”,让正常使用网站和应用的用户难以继续使用,这个“无差别”最要命,简单、粗暴,但却有效。
因此对于 DDoS 的攻击,我们需要整个网络链路配合,包括路由器、交换机、防火墙等等组件,采取入侵检测和流量过滤等多种方式来联合防范。这部分的内容涉及比较多,我在扩展阅读放了一点材料,感兴趣的话可以阅读。
XSS 和 CSRF 都和身份有关系,前者是劫持了某个身份,后者是假冒了某个身份
什么是 CSRF 攻击
CSRF(Cross-site request forgery 跨站请求伪造,也被称为“One Click Attack”或者 Session Riding,通常缩写为 CSRF 或者 XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与 XSS 非常不同,并且攻击方式几乎相左。XSS 利用站点内的信任用户,而 CSRF 则通过伪装来自受信任用户的请求来利用受信任的网站。与 XSS 攻击相比,CSRF 攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比 XSS 更具危险性。
为什么会出现 CSRF 攻击?举例说明比如说有两个网站 A 和 B。你是 A 网站的管理员,你在 A 网站有一个权限是删除用户,比如说这个过程只需用你的身份登陆并且 POST 数据到 【http://a.com/delUser 】, 就可以实现删除操作。好现在说 B 网站,B 网站被攻击了,别人种下了恶意代码,你点开的时候就会模拟跨域请求,如果是针对你,那么就可以模拟对 A 站的跨域请求,恰好这个时候你已经在 A 站登陆了。那么攻击者 在 B 站内通过脚本,模拟一个用户删除操作是很简单的。面对这种问题,有从浏览器解决,但个人认为最好是从网站端解决,检测每次 POST 过来数据时的 Refer,添加 AccessToken 等都是好方法。
一个典型的 CSRF 攻击有着如下的流程:
受害者登录 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 类型的 CSRF
POST 类型的 CSRF
链接类型的 CSRF
防范 CSRF 攻击可以遵循以下几种规则:
Get 请求不对数据进行修改
不让第三方网站访问到用户 Cookie
阻止第三方网站请求接口 referer
请求时附带验证信息,比如验证码或者 Token
上文中讲了 CSRF 的两个特点:
CSRF(通常)发生在第三方域名。
CSRF 攻击者不能获取到 Cookie 等信息,只是使用。
针对这两点,我们可以专门制定防护策略,如下:
阻止不明外域的访问 同源检测 Samesite Cookie 提交时要求附加本域才能获取的信息 CSRF Token 双重 Cookie 验证 以下我们对各种防护方法做详细说明
在 HTTP 协议中,每一个异步请求都会携带两个 Header,用于标记来源域名:
Origin Header Referer Header 这两个 Header 在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。 服务器可以通过解析这两个 Header 中的域名,确定请求的来源域。
使用 Origin Header 确定来源域名 在部分与 CSRF 有关的请求中,请求的 Header 中会携带 Origin 字段。字段内包含请求的域名(不包含 path 及 query)。
如果 Origin 存在,那么直接使用 Origin 中的字段确认来源域名就可以。
设置 Referrer-Policy 的方法有三种:
在 CSP 设置
页面头部增加 meta 标签
a 标签增加 referrer-policy 属性
令牌同步模式 令牌同步模式(英語:Synchronizer token pattern,简称 STP)。原理是:当用户发送请求时,服务器端应用将令牌(英語:token,一个保密且唯一的值)嵌入 HTML 表格,并发送给客户端。客户端提交 HTML 表格时候,会将令牌发送到服务端,令牌的验证是由服务端实行的。令牌可以通过任何方式生成,只要确保随机性和唯一性(如:使用随机种子【英語:random seed】的哈希链 )。这样确保攻击者发送请求时候,由于没有该令牌而无法通过验证。
檢查 Referer 字段 HTTP 頭中有一個 Referer 字段,這個字段用以標明請求來源於哪個地址。在處理敏感數據請求時,通常來說,Referer 字段應和請求的地址位於同一域名下。以上文銀行操作為例,Referer 字段地址通常應該是轉帳按鈕所在的網頁地址,應該也位於 bank.example.com 之下。而如果是 CSRF 攻擊傳來的請求,Referer 字段會是包含惡意網址的地址,不會位於 bank.example.com 之下,這時候伺服器就能識別出惡意的訪問。
這種辦法簡單易行,工作量低,僅需要在關鍵訪問處增加一步校驗。但這種辦法也有其局限性,因其完全依賴瀏覽器發送正確的 Referer 字段。雖然 http 協議對此字段的內容有明確的規定,但並無法保證來訪的瀏覽器的具體實現,亦無法保證瀏覽器沒有安全漏洞影響到此字段。并且也存在攻擊者攻擊某些瀏覽器,篡改其 Referer 字段的可能。
添加校驗 token 由於 CSRF 的本質在於攻擊者欺騙用戶去訪問自己設置的地址,所以如果要求在訪問敏感數據請求時,要求用戶瀏覽器提供不保存在 cookie 中,并且攻擊者無法偽造的數據作為校驗,那麼攻擊者就無法再執行 CSRF 攻擊。這種數據通常是表單中的一個數據項。伺服器將其生成並附加在表單中,其內容是一個偽亂數。當客戶端通過表單提交請求時,這個偽亂數也一並提交上去以供校驗。正常的訪問時,客戶端瀏覽器能夠正確得到並傳回這個偽亂數,而通過 CSRF 傳來的欺騙性攻擊中,攻擊者無從事先得知這個偽亂數的值,伺服器端就會因為校驗 token 的值為空或者錯誤,拒絕這個可疑請求。
在使用 .innerHTML、.outerHTML、document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等。
CSP 是为了解决 XSS 攻击
Content Security Policy 严格的 CSP 在 XSS 的防范中可以起到以下的作用:
禁止加载外域代码,防止复杂的攻击逻辑。 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。 合理使用上报可以及时发现 XSS,利于尽快修复问题。
输入内容长度控制 对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。
其他安全措施 HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。 验证码:防止脚本冒充用户提交危险操作。
做了 HTML 转义,并不等于高枕无忧。 对于链接跳转,如 <a href="xxx" 或 location.href="xxx",要检验其内容,禁止以 javascript: 开头的链接,和其他非法的 scheme。
Samesite Cookie 属性
Samesite=Strict 这种称为严格模式,表明这个 Cookie 在任何情况下都不可能作为第三方 Cookie,绝无例外。
Samesite=Lax 这种称为宽松模式,比 Strict 放宽了点限制:假如这个请求是这种请求(改变了当前页面或者打开了新页面)且同时是个 GET 请求,则这个 Cookie 可以作为第三方 Cookie。比如说 b.com 设置了如下 Cookie:
我们应该如何使用 SamesiteCookie 如果 SamesiteCookie 被设置为 Strict,浏览器在任何跨域请求中都不会携带 Cookie,新标签重新打开也不携带,所以说 CSRF 攻击基本没有机会。
但是跳转子域名或者是新标签重新打开刚登陆的网站,之前的 Cookie 都不会存在。尤其是有登录的网站,那么我们新打开一个标签进入,或者跳转到子域名的网站,都需要重新登录。对于用户来讲,可能体验不会很好。
如果 SamesiteCookie 被设置为 Lax,那么其他网站通过页面跳转过来的时候可以使用 Cookie,可以保障外域连接打开页面时用户的登录状态。但相应的,其安全性也比较低。
另外一个问题是 Samesite 的兼容性不是很好,现阶段除了从新版 Chrome 和 Firefox 支持以外,Safari 以及 iOS Safari 都还不支持,现阶段看来暂时还不能普及。
而且,SamesiteCookie 目前有一个致命的缺陷:不支持子域。例如,种在 topic.a.com 下的 Cookie,并不能使用 a.com 下种植的 SamesiteCookie。这就导致了当我们网站有多个子域名时,不能使用 SamesiteCookie 在主域名存储用户登录信息。每个子域名都需要用户重新登录一次。
总结 简单总结一下上文的防护策略:
CSRF 自动防御策略:同源检测(Origin 和 Referer 验证)。 CSRF 主动防御措施:Token 验证 或者 双重 Cookie 验证 以及配合 Samesite Cookie。 保证页面的幂等性,后端接口不要在 GET 页面中做用户操作。
什么是 XSS 攻击
Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。XSS 可以分为多种类型,但是总体上我认为分为两类:持久型、非持久型和 DOM-Based 型 XSS.
XSS 的本质是:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。
而由于直接在用户的终端执行,恶意代码能够直接获取用户的信息,或者利用这些信息冒充用户向网站发起攻击者定义的请求。
在部分情况下,由于输入的限制,注入的恶意脚本比较短。但可以通过引入外部的脚本,并由浏览器执行,来完成比较复杂的攻击策略。
这里有一个问题:用户是通过哪种方法“注入”恶意脚本的呢?
不仅仅是业务上的“用户的 UGC 内容”可以进行注入,包括 URL 上的参数等都可以是攻击的来源。在处理输入时,以下内容都不可信:
来自用户的 UGC 信息
来自第三方的链接
URL 参数
POST 参数
Referer (可能来自不可信的来源)
Cookie (可能来自其他子域注入)
XSS 分类
存储型、反射型和 DOM 型三种。
持久型也就是攻击的代码被服务端写入进数据库中,这种攻击危害性很大,因为如果网站访问量很大的话,就会导致大量正常访问页面的用户都受到攻击。
一次性(非持久性)
通过用户点击链接引起
反射型 XSS,也可称为非持久型 XSS。
其攻击方式往往是通过诱导用户去点击一些带有恶意脚本参数的 URL 而发起的。
事实上由于反射型 XSS 因为 URL 特征导致很容易被防御。很多浏览器如 Chrome 都内置了 相应的 XSS 过滤器,来防范用户点击了反射型 XSS 的恶意链接
反射型 XSS 归根到底就是由于不可信的用户输入被服务器在没有安全防范处理,然后就直接使用到响应页面中,然后反射给用户而导致代码在浏览器执行的一种 XSS 漏洞。
DOM 型 XSS
DOM 型 XSS 的攻击步骤:
攻击者构造出特殊的 URL,其中包含恶意代码。
用户打开带有恶意代码的 URL。
用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。
XSS 攻击的预防
XSS 攻击有两大要素:
攻击者提交恶意代码。 浏览器执行恶意代码。
XSS 主要做什么事:
窃取用户 Cookie
伪造请求
XSS 钓鱼
防范措施: 针对 URL 编码, HTML 编码, JS 编码。 URL 只能使用英文字母(a-zA-Z)、数字(0-9)、-_.~4 个特殊字符以及所有(; , /?:@&=+$#)保留字符。 例如:
判断输入格式: 过滤特殊字符:<、 > 、&、 过滤危险字符: 去除<"script"> 、javascript、onclik
但是输入侧过滤一旦攻击者绕过前端过滤,直接构造请求,就可以提交恶意代码了。
当然,对于明确的输入类型,例如数字、URL、电话号码、邮件地址等等内容,进行输入过滤还是必要的。
既然输入过滤并非完全可靠,我们就要通过“防止浏览器执行恶意代码”来防范 XSS。这部分分为两类: 预防存储型和反射型 XSS 攻击
防止 HTML 中出现注入。 防止 JavaScript 执行时,执行恶意代码。
预防这两种漏洞,有两种常见做法:
改成纯前端渲染,把代码和数据分隔开。
对 HTML 做充分转义。
XSS 的防御方式
httpOnly 原理:浏览器禁止页面的 Javascript 访问带有 HttpOnly 属性的 cookie。(实质解决的是:XSS 后的 cookie 劫持攻击)如今已成为一种“标准”的做法
解决方案: JavaEE 给 Cookie 添加 HttpOnly 的方式为:
输入检查(XSS Filter)
原理:让一些基于特殊字符的攻击失效。(常见的 Web 漏洞如 XSS、SQLInjection 等,都要求攻击者构造一些特殊字符)
输入检查的逻辑,必须在服务端实现,因为客户端的检查也是很容易被攻击者绕过,现有的普遍做法是两端都做同样的检查,客户端的检查可以阻挡大部分误操作的正常用户,从而节约服务器的资源。
解决方案: 检查是否包含"JavaScript",""等敏感字符。以及对字符串中的<>:"&/'等特殊字符做处理.
输入检查
原理:一般来说除了富文本输出之外,在变量输出到 HTML 页面时,使用编码或转义的方式来防御 XSS 攻击
解决方案:
针对 HTML 代码的编码方式:HtmlEncode
PHP:htmlentities()和 htmlspecialchars()两个函数
Javascript:JavascriptEncode(需要使用""对特殊字符进行转义,同时要求输出的变量必须在引号内部)
在 URL 的 path(路径)或者 search(参数)中输出,使用 URLEncode
CSRF 的攻击方式
浏览器所持有的策略一般分为两种: Session Cookie,临时 Cookie。保存在浏览器进程的内存中,浏览器关闭了即失效。 Third-party Cookie,本地 Cookie。服务器在 Set-Cookie 时指定了 Expire Time。过期了本地 Cookie 失效,则网站会要求用户重新登录。
GET, POST 请求
这里有个误区 大多数 CSRF 攻击,都是通过 、 、 <script> 等带 src 属性的标签,这类标签只能发送一次 GET 请求,而不能发送 POST 请求,由此也有了认为 CSRF 攻击只能由 GET 请求发起的错误观点。
构造一个 POST 请求,只需要在一个不可见的 iframe 窗口中,构造一个 form 表单,然后使用 JavaScript 自动提交这个表单。那么整个自动提交表单的过程,对于用户来说就是不可见的。
CSRF 的防御方式
1.验证码 原理: CSRF 攻击过程中,用户在不知情的情况下构造了网络请求,添加验证码后,强制用户必须与应用进行交互
优点:简洁而有效
缺点:网站不能给所有的操作都加上验证码
2.Referer Check
原理:
利用 HTTP 头中的 Referer 判断请求来源是否合法
Referer 首部包含了当前请求页面的来源页面的地址,一般情况下 Referer 的来源页就是发起请求的那个页面,如果是在 iframe 中发起的请求,那么对应的页面 URL 就是 iframe 的 src
优点:简单易操作(只需要在最后给所有安全敏感的请求统一添加一个拦截器来检查 Referer 的值就行)
缺点:服务器并非什么时候都能取到 Referer 1.很多出于保护用户隐私的考虑,限制了 Referer 的发送。 2.比如从 HTTPS 跳转到 HTTP,出于安全的考虑,浏览器不会发送 Referer
使用 Anti CSRF Token
原理:把参数加密,或者使用一些随机数,从而让攻击者无法猜测到参数值,也就无法构造请求的 URL,也就无法发起 CSRF 攻击。
例子(增加 token):
比如一个删除操作的 URL 是:
http://host/path/delete?uesrname=abc&item=123
保持原参数不变,新增一个参数 Token,Token 值是随机的,不可预测
http://host/path/delete?username=abc&item=123&token=[random(seed)]
优点:比检查 Referrer 方法更安全,并且不涉及用户隐私
缺点: 加密 1. 加密后的 URL 非常难读,对用户非常不友好 2. 加密的参数每次都在改变,导致用户无法对页面进行搜索 3. 普通参数也会被加密或哈希,将会给 DBA 工作带来很大的困扰,因为数据分析常常需要用到参数的明文 token 1. 对所有的请求都添加 Token 比较困难
其他安全问题
4.1 跨域问题处理
当服务端设置 'Access-Control-Allow-Origin' 时使用了通配符 "*",允许来自任意域的跨域请求,这是极其危险的
4.2 postMessage 跨窗口传递信息
postMessage 允许每一个 window(包括当前窗口、弹出窗口、iframes 等)对象往其他的窗口发送文本消息,从而实现跨窗口的消息传递。并且这个功能不受同源策略限制。
必要时,在接受窗口验证 Domain,甚至验证 URL,以防止来自非法页面的消息。实际上是在代码上实现一次同源策略的验证过程。接受窗口对接口的信息进行安全检查。
4.3 web Storage
Web Storage 分为 Session Storage 和 Local Storage。
虽然受同源策略的约束,但当存有敏感信息时,也可能会成为攻击的目标。
最后更新于
这有帮助吗?