web安全

浏览器端防御

xss

什么是 XSS

Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。

为了和 CSS 区分,这里把攻击的第一个字母改成了 X,于是叫做 XSS。

XSS 的本质是:用户提交的数据未经过滤,渲染到了HTML上,结果被浏览器当成了代码,浏览器无法分辨哪些代码是可信的,导致恶意代码被执行。

在部分情况下,由于输入的限制,注入的恶意代码比较短。但可以通过引入外部的js文件,并由浏览器执行,来完成比较复杂的攻击策略。

用户是通过哪些方法“注入”恶意代码的呢?

所有用户输入的信息,都有可能成为注入的入口。在处理输入时,以下内容都不可信:

  • 来自用户的 UGC 信息
  • URL 参数
  • POST 参数
  • Referer
  • Cookie
  • UA

等等…

总结就是,当我们需要把某些数据渲染到HTML上,但是这些数据是由用户决定的,这时候就有可能受到XSS攻击

XSS的危害

攻击者能够在用户的页面上运行恶意 JS ,就相当于取得了整个页面的浏览器上的控制权,攻击者通过XSS干的事情包括但不限于以下:

  1. 获取用户敏感信息。监听表单输入,获取cookie等

  2. 冒充用户发起操作请求。在恶意脚本中利用用户的登录状态进行货币、物品等转账,更改权限管理等

  3. 钓鱼。弹窗,或改造页面外观,引导用户输入其他网站的敏感信息

  4. 挖矿。占用CPU,消耗资源

  5. 发起DOS攻击。所有被XSS的用户对某个目标发起资源请求。

  6. XSS 蠕虫。如果是社交平台的XSS,在恶意脚本中利用用户的登录状态进行关注、发状态、发私信等操作,发出的状态和私信可再带上攻击 URL,诱导更多人点击,不断放大攻击范围。

    等等

XSS的分类

根据攻击的来源,XSS 攻击可分为存储型、反射型和 DOM 型三种。

存储型 XSS

存储型 XSS 的攻击步骤:

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应,恶意代码也被执行。

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

反射型 XSS

反射型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后,恶意代码被执行。

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。

反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。

由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。

DOM 型 XSS

DOM 型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL。
  3. 用户浏览器接收到响应后,前端代码将URL中的数据插入到了dom中,或者在js中进行了eval等操作,恶意代码被执行。

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。

XSS的防御

将介绍防御之前,首先探讨一下过滤输入的可行性。

Q :在用户提交时,由前端过滤输入,例如把 > 转换为 &gt,然后提交到后端。这样做是否可行呢?

A :答案是不可行。一旦攻击者绕过前端过滤,直接构造请求,就可以提交恶意代码了。

Q:那么,换一个过滤时机:后端在写入数据库前,对输入进行过滤,然后把“安全的”内容,返回给前端。这样是否可行呢?

A:也不可行。第一:数据库的数据有可能渲染到除了WEB的其他客户端,其他客户端就会显示&gt而不是 >。数据还有可能被前端框架(比如VUE),使用createTextNode的方式拼接到HTML,此时也是显示的&gt而不是>,数据内容的长度也会不能直接用来计算,等等

因此,直接过滤用户的输入是不可行的。防御XSS应该从“防止恶意代码被浏览器执行”来着手,分为两方面:

  • 防止 HTML 中出现注入(存储型和反射型 )。
  • 防止 JavaScript 执行时,执行恶意代码(DOM型)。
预防存储型和反射型 XSS 攻击

存储型和反射型 XSS 都是在服务端取出恶意代码后,插入到响应 HTML 里的,攻击者刻意编写的“数据”被内嵌到“代码”中,被浏览器所执行。

预防这两种漏洞,有两种常见做法:

  • 改成纯前端渲染,把代码和数据分隔开。
  • 对 HTML 做充分转义。

纯前端渲染就是现在流行的前端框架配合前后端分离的开发模式,所有的数据通过ajax请求返回,交给前端框架来渲染数据,由于框架里通过模板语法,会明确的告诉浏览器,数据是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等,大大降低了xss的风险。

但是纯前端渲染的页面面临SEO的问题。如果对HTML进行拼接是必要的,那么就需要对用户输入的内容进行转义以后再渲染在HTML上

常用的模板引擎,如 ejs 等,对于 HTML 转义通常只有一个规则,就是把 & < > " ' / 这几个字符转义掉,

字符 转义后的字符
& &amp;
< &lt;
> &gt;
" &quot;
' &#x27;
/ &#x2F;

确实能起到一定的 XSS 防护作用,但并不完善:

XSS 安全漏洞 简单转义是否有防护作用
HTML 标签文字内容
HTML 属性值
CSS 内联样式
内联 JavaScript
内联 JSON
跳转链接

HTML转义是非常复杂的,在不同的情况下要采用不同的转义规则,一旦有疏漏就有可能被攻击。这也是为什么XSS在WEB安全领域如此高发的原因。

预防DOM型 XSS 攻击

DOM型XSS攻击,实际上就是网站前端 JS 代码不严谨,把不可信的数据当作代码执行了。

写原生JS时,一定不要把不可信的数据通过 innerHTMLouterHTMLdocument.write渲染在页面上。尽量使用textContentsetAttribute等。如果使用Vue/React 技术栈,避免使用v-html/dangerouslySetInnerHTML。DOM的内联事件,a标签的href,JS中的eval,setTimeout,setInterval等都会把字符串作为代码运行,一定不要把不可信的字符串拼接或传递给这些API。

XSS的检测

手动检测
1
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e

攻击者注入的内容通常称为payload,上面就是一个很好用的payload,它能够检测到存在于 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等多种上下文中的 XSS 漏洞,也能检测 eval()setTimeout()setInterval()Function()innerHTMLdocument.write() 等 DOM 型 XSS 漏洞,并且能绕过一些 XSS 过滤器。

具体原理:Unleashing an Ultimate XSS Polyglot

漏扫工具

除了手动检测之外,还可以使用自动扫描工具寻找 XSS 漏洞,例如AWVSArachniw3af

CSRF

什么是CSRF

CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

一个典型的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执行了自己定义的操作。

csrf的防御

同源检测

既然CSRF大多来自第三方网站,那么我们就直接禁止外域(或者不受信任的域名)对我们发起请求。通常做法是对请求头中的Referer进行检测,这个内容前端 JS是无法修改的。

这种方法显而易见的好处就是简单方便。然而,Referer的值是浏览器提供的,虽然HTTP 协议有明确的要求,但是浏览器的实现可能有差别,还可能有漏洞,比如 IE6 和 FF2中就可能被篡改Referer值。

另外,有的浏览器中用户可以手动关闭Referer的发送,这种情况下,用户的请求是正常的请求,但是请求中没有Referer,而攻击者也可以伪造没有Referer的请求。如果把没有Referer的请求封掉就会存在误封。

最后,如果攻击者的请求本身就是从本域发起的(比如在留言,评论处引入img元素),这种依赖同源检测来防范 CSRF 的方式就会失效。

总结:针对CSRF攻击,同源检测非常方便,而且很大程度上可以起到防御的作用。但是会对没有Referer的请求存在误封,无法防御用户使用存在Referer篡改漏洞浏览器时被攻击的情况,以及对于本域发起的 CSRF 无法防范。

CSRF Token

CSRF 攻击之所以能成功,是因为浏览器向一个域名发送请求时,会自动带上这个域名下的cookie。攻击者冒用了域名下的cookie,但是攻击者本身是没法知道cookie的具体数值。

所以,对用户身份的验证不能只能验证请求的cookie,服务端规定在请求体或者请求头中,需要带上一个特殊字符串,服务端对这个特殊的字符串验证其合法性,这个字符串就叫 CSRF Token。

CSRF Token的实现有以下几种方式:

  1. 服务端针对每个用户生成一个 token 并存储在Session中,浏览器请求页面时,在cookie或者页面某处插入此token,前端拿到token后,无论是ajax,还是表单提交都需要在请求头或者请求体或者请求参数中携带此token,服务端接收请求时将请求中的 token 和Sesssion中的token比对,一致则通过。

  2. 服务端对每个用户生成一个 token,把token当作cookie设置在浏览器中,前端将此cookie取出后,每次发请求时添加到请求头或者请求体或者请求参数中,服务端接收请求时将cookie中的token和请求头或者请求体或者请求参数中的token比对,一致则通过。

点击劫持

什么是点击劫持

点击劫持又称UI-覆盖攻击,通过引诱用户进行鼠标点击操作,但结果实际的操作并不是用户本身的意愿,所以被称为点击劫持。

攻击者使用 iframe 作为目标网页载体。iframe 是 HTML 标准中的一个标签,可以创建包含另外一个页面的内联框架,在点击劫持漏洞利用中主要用来载入目标网页。点击劫持典型的攻击原理示意图如图所示:

攻击者实施攻击的一般步骤是:

  1. 攻击者创建一个有害网页利用iframe包含目标网站;
  2. 通过CSS隐藏目标网站,使用户无法察觉到目标网站存在;
  3. 构造有害网页,诱骗用户点击特定按钮;
  4. 用户在不知情的情况下点击按钮,触发执行恶意网页的命令。

如图是用户以为自己是在玩一个类似打地鼠的游戏,实际上攻击者通过点击劫持,把flash隐藏在iframe中,诱导用户点击,最后打开了用户的摄像头:

点击劫持的防御

使用 FrameBusting 代码

可以通过JS来自己的网页被第三方网站用iframe嵌套,防止iframe嵌套的条件判断:

1
2
3
4
5
6
7
8
9
10
if (top != self)
if (top.location != self.location)
if (top.location != location)
if (parent.frames.length > 0)
if (window != top)
if (window.top !== window.self)
if (window.self != window.top)
if (parent && parent != window)
if (parent && parent.frames && parent.frames.length>0)
if((self.parent&&!(self.parent===self))&&(self.parent.frames.length!=0))

检测到后的处理方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
top.location = self.location
top.location.href = document.location.href
top.location.href = self.location.href
top.location.replace(self.location)
top.location.href = window.location.href
top.location.replace(document.location)
top.location.href = window.location.href
top.location.href = "URL"
document.write('')
top.location = location
top.location.replace(document.location)
top.location.replace('URL')
top.location.href = document.location
top.location.replace(window.location.href)
top.location.href = location.href
self.parent.location = document.location
parent.location.href = self.document.location
top.location.href = self.location
top.location = window.location
top.location.replace(window.location.pathname)
window.top.location = window.self.location
setTimeout(function(){document.body.innerHTML='';},1);
window.self.onload = function(evt){document.body.innerHTML='';}
var url = window.location.href; top.location.replace(url)

原理则是检查当前页面被使用 iframe 加载时,检查顶层是不是自己的网站,不是则让顶层跳转,或者将自身内容消除。

但是此方案存在以下问题:

有的浏览器出于安全考虑,会阻止iframe中嵌套的网页对父级网页发起的跳转。

iframe 中新添加属性 sandbox="allow-forms",可以禁止iframe加载的网页中的js,但仍然允许表单提交。

使用X-FRAME-OPTIONS响应头

X-FRAME-OPTIONS是微软提出的一个http头,专门用来防御利用iframe嵌套的点击劫持攻击。并且在IE8、Firefox3.6、Chrome4以上的版本均能很好的支持。

这个头可以配置:

  • DENY // 拒绝任何域加载
  • SAMEORIGIN / / 允许同源域下加载
  • ALLOW-FROM // 可以定义允许frame加载的页面地址

当网页作为iframe在其他网页中加载时,浏览器会根据中此响应头来决定是否加载

服务器端防御

HTTP劫持

什么是HTTP劫持

HTTP协议使用明文(不加密)传输,内容可能会被窃听或者篡改

HTTP劫持是指HTTP请求从浏览器到服务端的过程中,有中间节点对传输内容进行了窃听或者篡改。使用tracert可以看到请求某个网站所通过的所有节点,这些节点会将流量进行转发,也就意味着这些节点都存在窃听或者篡改的风险。

HTTP劫持的危害是显而易见的:用户的所有隐私信息都会暴露,用户和网站之间传递的内容可以被随意篡改。

最常见的例子就是随意连接第三方的路由器,此时这个路由器就是通信过程中的一个节点,如果你访问的网站又是用HTTP协议进行通信,路由器的拥有者就可以窥探或篡改你和服务器之间通信的任何内容

为了解决明文信息传输的问题,由网景公司(Netscape)在1994年首次提出 HTTPS 协议

什么是HTTPS协议

HTTPS= HTTP + SSL

它的核心是SSL/TLS协议

SSL协议是 NetScape 公司于 1994 年提出的一个关注互联网信息安全的信息加密传输协议,其目的是为客户端(浏览器) 到服务器端之间的信息传输构建一个加密通道。后来由于应用广泛,IETF(Internet Engineering Task Force,Internet工程任务组)就把SSL进行标准化,并把标准化后的SSL改名为TLS。所以,TLS可以说是SSL的加强版,两者是同一个东西的不同阶段。

对称加密

通常,将明文数据加密得到密文需要用到一把密钥,将密文数据解密成明文数据也需要用到一把密钥,对称加密的情况下,用于加密和解密的密钥相同。常见的对称加密算法有DES3DESAES

考虑如下情况:通信双方各有一把相同的密钥,密钥既可以用来加密也可以用来解密。客户端使用密钥将数据加密后,传送给服务器,服务器再用相同的密钥解密。同理,服务器用密钥加密后传输给客户端的数据,客户端也可以用该密钥解密。

看似是解决了明文传输的问题,但是新的问题来了,上图中,用于加密的Key1双方怎么约定?一旦用明文通信,中间人就会拿到Key1,之后的加密通信就形同虚设。

非对称加密

用于加密和解密的密钥是两个不同的密钥,用其中一个密钥加密得到的密文,必须用另一个密钥才能解密。通常,其中一把密钥不会对任何其他人公开,称为私钥,另外一把会对外公开,成为公钥。常见的非对称加密算法有RSADSAECC

为了让客户端和服务端约定后续对称加密用到的密钥,可以通过非对称加密来通信,事先服务端有一个私钥Key1和一个公钥Key2,服务端先将公钥Key2发送给客户端,客户端使用公钥Key2Key3进行加密后返回服务端,服务端再通过私钥Key1解密密码,拿到Key3

注意只要私钥Key1不泄露,使用任何使用公钥Key2加密的数据就不会泄露。注意,一般网站的私钥Key1公钥Key2是长期不变的,但是每次回话用于对称加密的Key3都是会变化的,否则攻击者完全可以以另一个合法用户的身份拿到相同的Key3。通过非对称加密,中间人在第二步不知道私钥Key1的情况下,无法得到Key3,但是,这个流程仍然存在问题:

中间人可以在第一步,拦截服务端的公钥后,把自己的公钥Key2*发给客户端,这样就可以在第二步用自己的私钥解密得到Key3,之后再用服务端真的公钥Key2Key3加密返回给服务端。

以上这个问题根源可以归结为:怎么证明这个网站的公钥一定是公钥Key2,而不是别人伪造的,例如Key2*

数字证书

证书机构中心简称CA(Certificate Authority),专门为网站提供证明“我是我”的认证的机构。通常做法是,网站所有者按CA的要求在指定目录下放一个文件,如果CA能够访问,就证明网站所有者确实拥有对该网站的控制权。这时,CA会颁发给该网站一份证书

证书关键内容包括:

网站的域名、网站的公钥等明文信息,和一个数字签名数字签名是使用CA的私钥加密后生成的:

所以第一步服务端传递给浏览器的是一个CA颁发给本网站的证书,浏览器拿到证书后使用CA的公钥进行校验。就可以证明Key2确实是这个网站的公钥

最后的问题是:CA的公钥是怎么获取的?如果CA的公钥也是网络获取,显然就进入了无解的死循环。事实上,CA的公钥是以CA给自己颁发的证书的形式内置在操作系统中的。

至此SSL协议通过对称加密 + 非对称加密+ CA证书 环环相扣的方式达到了加密通信的目的。

问题1:为什么不直接内置网站的证书?

世界上网站的数量太多,系统想要内置网站的证书是不可能的。

问题2:为什么不对对称加密的密钥颁发证书?

对称加密的密钥是根据每个会话随机生成的,如果保持不变,攻击者会通过伪装成一个合法用户获取到其他人的密钥。

问题3:为什么不直接用非对称加密通信,还要交换对称加密密钥后续用对称加密通信,这不是多此一举吗?

对称加密的性能远远优于非对称加密,所有信息都使用非对称加密会非常耗费性能。

综上,防止了HTTP劫持,作为站长出于安全考虑应该为自己的网站部署HTTPS。作为用户,应该尽量选择部署了HTTPS的网站进行访问。

回顾整个HTTPS安全通信的逻辑关系:

CA的公钥内置在系统 + CA的私钥不泄露 → CA的证书无法伪造 → 网站的公钥等信息无法伪造 + 网站的私钥不泄露→ 使用网站的公钥加密后的对称加密密钥无法解密 → 所有通过对称加密后的数据无法解密 → 中间人无法窃听或篡改信息。

上传漏洞

什么是上传漏洞

在web应用中经常会遇到用户上传文件的需求,比如用户上传头像,在文章中上传图片,或者是招聘网站中上传相关附件等等。上传的文件之后肯定也会被访问,比如用于显示在网页界面上。服务器访问用户上传的文件时,将文件当成了程序来执行,又由于文件的内容是由上传者决定的,此时就存在运行恶意程序的风险。

最常见的例如PHP这种服务端脚本语言,Web开发者通常会将一个服务端某个文件夹里所有脚本文件作为路由映射出去,浏览器通过相应的URL即可执行与之对应的服务端脚本。

如果有攻击者在此文件夹中上传恶意脚本,再通过访问来执行它,最严重的后果是整个服务器最高权限被攻击者拿到。

上传漏洞的防御

对上传文件的后缀进行校验。

此方法是最常见的防御方法,但仍然可能有漏网之鱼:

比如文件名大小写Php,aSP

比如evil.php.在windows中会自动去掉最后的.

比如Apache平台中上传.htaccess文件

1
2
3
<FilesMatch "_php.gif">
SetHandler application/x-httpd-php
</FilesMatch>

可以让文件名中包含_php.gif的文件统一按照php执行

文件重命名

防止攻击者获取执行脚本的路径

可执行与可写权限分离

包含可执行脚本的目录禁止用户写入,用户写入文件的目录禁止执行

SQL注入

什么是SQL

SQL是一种数据库查询语言