slavik 2019-09-08
CSRF
(跨站请求伪造)是一种挟制用户在当前已登录的Web
应用程序上执行非本意的操作的攻击方法。
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。这个过程可以这样来看:
sueRimn.com
,并保留了登录凭证(Cookie
)sb.com
(用户自身不知道)sb.com
向sue.com
发送请求:sue.com/act=xx
,浏览器会默认携带sue.com
的Cookie
sue.com
接收到请求后,对请求进行验证,发现是曾经用户保留的Cookie
,误以为是用户本人操作,然后允许了执行请求可见完成一次CSRF
攻击,用户必须依次存在着两个操作:
Cookie
由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
攻击者并不能通过CSRF攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义运行操作。
CSRF
特点URL
、超链接、CORS
、Form
提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪CSRF
通常是跨域的,因为外域通常更容易被攻击者掌控。但是如果本域下有容易被利用的功能,比如可以发图和链接的论坛和评论区,攻击可以直接在本域下进行,而且这种攻击更加危险。
CSRF
攻击类型GET
类型的CSRF
利用非常简单,只需要一个HTTP
请求,一般会这样利用:
<img src="http://bank.example/withdraw?amount=10000&for=hacker" >
在受害者访问含有这个img
的页面后,浏览器会自动向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker
发出一次HTTP请求。bank.example
就会收到包含受害者登录信息的一次跨域请求。
这种类型的CSRF利用起来通常使用的是一个自动提交的表单,如:
<form action="http://bank.example/withdraw" method=POST> <input type="hidden" name="account" value="xiaoming" /> <input type="hidden" name="amount" value="10000" /> <input type="hidden" name="for" value="hacker" /> </form> <script> document.forms[0].submit(); </script>
访问该页面后,表单会自动提交,相当于模拟用户完成了一次POST
操作。
POST
类型的攻击通常比GET
要求更加严格一点,但仍并不复杂。任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅允许POST
上面。
链接类型的CSRF
并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击,例如:
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank"> 重磅消息!! <a/>
由于之前用户登录了信任的网站A,并且保存登录状态,只要用户主动访问上面的这个PHP
页面,则表示攻击成功。
CSRF
通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF
的防护能力来提升安全性。
上文中讲了CSRF
的两个特点:
CSRF
(通常)发生在第三方域名CSRF
攻击者不能获取到Cookie
等信息,只是使用针对以上两个特点制定两个反向的防御措施,如下:
主要的方式有:
Samesite Cookie
既然CSRF
大多来自第三方网站,那么我们就直接禁止外域(或者不受信任的域名)对我们发起请求。
那么问题来了,我们如何判断请求是否来自外域呢?
在HTTP
协议中,每一个异步请求都会携带两个Header
,用于标记来源域名:
Origin Header
Referer Header
这两个Header
在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。 服务器可以通过解析这两个Header
中的域名,确定请求的来源域。
使用Origin Header确定来源域名
在部分与CSRF
有关的请求中,请求的Header
中会携带Origin
字段。字段内包含请求的域名(不包含path
及query
)。
如果Origin
存在,那么直接使用Origin
中的字段确认来源域名就可以。
但是Origin
在以下两种情况下并不存在:
检查Referer
字段
HTTP
头中有一个Referer
字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer
字段应和请求的地址位于同一域名下。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的Referer
字段。虽然http协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其Referer
字段的可能。
设置Referrer Policy
的方法有三种:
CSP
设置meta
标签a
标签增加referrerpolicy
属性上面说的这些比较多,但我们可以知道一个问题:攻击者可以在自己的请求中隐藏Referer。如果攻击者将自己的请求这样填写,这个请求发起的攻击将不携带Referer
:
![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2018b/ff0cdbee.example/withdraw?amount=10000&for=hacker)
另外在以下情况下Referer
没有或者不可信:
IE6、7
下使用window.location.href=url
进行界面的跳转,会丢失Referer
IE6、7
下使用window.open
,也会缺失Referer
HTTPS
页面跳转到HTTP
页面,所有浏览器Referer
都丢失Flash
上到达另外一个网站的时候,Referer
的情况就比较杂乱,不太可信综上所述:同源验证是一个相对简单的防范方法,能够防范绝大多数的CSRF
攻击。但这并不是万无一失的,对于安全性要求较高,或者有较多用户输入内容的网站,我们就要对关键的接口做额外的防护措施。
Samesite Cookie
属性为了从源头上解决这个问题,Google
起草了一份草案来改进HTTP
协议,那就是为Set-Cookie
响应头新增Samesite
属性,它用来标明这个 Cookie
是个“同站 Cookie
",同站Cookie
只能作为第一方Cookie
,不能作为第三方Cookie,Samesite
有两个属性值,分别是 Strict
和 Lax
token
由于CSRF
的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在cookie中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再运行CSRF
攻击。
CSRF Token
的防护策略分为三个步骤:
CSRF Token
输出到页面中token
token
是否正确Cookie
验证在会话中存储CSRF Token
比较繁琐,而且不能在通用的拦截上统一处理所有的接口。
那么另一种防御措施是使用双重提交Cookie
。利用CSRF
攻击不能获取到用户Cookie
的特点,我们可以要求Ajax
和表单请求携带一个Cookie
中的值。
双重Cookie
采用以下流程:
Cookie
,内容为随机字符串(例如csrfcookie=v8g9e4ksfhw
)。Cookie
,并添加到URL
的参数中(接上例POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw
)。Cookie
中的字段与URL
参数中的字段是否一致,不一致则拒绝。双重Cookie防御CSRF的优缺点:
优点:
Session
,适用面更广,易于实施token
储存于客户端中,不会给服务器带来压力token
,实施成本更低,可以在前后端统一拦截校验,而不需要一个个接口和页面添加缺点:
Cookie
中增加了额外的字段XSS
),攻击者可以注入Cookie
,那么该防御方式失效Cookie
传输安全,采用这种防御方式的最好确保用整站HTTPS
的方式,如果还没切HTTPS
的使用这种方式也会有风险CSRF
测试步骤:
CSRF
修改并伪造请求CSRF
监控CSRF攻击有着比较明显的特征:
在网站的代理层监控所有的接口请求,如果请求符合上面的特征,就可以认为请求有CSRF
攻击嫌疑。我们可以提醒对应的页面和项目负责人,检查或者 Review
其CSRF
防护策略。
CSRF
攻击简单总结CSRF
自动防御策略:同源检测(Origin
和 Referer
验证)CSRF
主动防御措施:token
验证 或者 双重Cookie
验证 以及配合Samesite Cookie
GET
页面中做用户操作XSS
(跨站脚本攻击)XSS
攻击是指在用户在访问信任的网站时注入恶意攻击脚本(主要是JavaScript
),对客户端网页进行篡改,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。
攻击者对客户端网页注入的恶意脚本一般包括 JavaScript
,有时也会包含 HTML
和 Flash
。有很多种方式进行 XSS
攻击,但它们的共同点为:获取用户隐私数据像 Cookie
、session
,进行一些恶意操作。
用户是通过哪种方法“注入”恶意脚本的呢?
不仅仅是业务上的“用户的 UGC
内容”可以进行注入,包括URL
上的参数等都可以是攻击的来源。在处理输入时,以下内容都不可信:
UGC
信息URL
参数POST
参数Referer
(可能来自不可信的来源)Cookie
(可能来自其他子域注入)XSS
注入方式 HTML
中内嵌的文本中,恶意内容以 script
标签形成注入。 JavaScript
中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。href
、src
等属性中,包含 javascript:
等可执行代码。onload
、onerror
、onclick
等事件中,注入不受控制代码。 style
属性和标签中,包含类似 background-image:url("javascript:...");
的代码(新版本浏览器已经可以防范)。expression(...)
的 CSS 表达式代码(新版本浏览器已经可以防范)。总之,如果开发者没有将用户输入的文本进行合适的过滤,就贸然插入到 HTML
中,这很容易造成注入漏洞。攻击者可以利用漏洞,构造出恶意的代码指令,进而利用恶意代码危害数据安全。
XSS
攻击的分类根据攻击的来源,XSS
攻击可分为存储型、反射型和 DOM
型三种。
XSS
存储型 XSS 的攻击步骤:
这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。
XSS
反射型 XSS
的攻击步骤:
URL
,其中包含恶意代码。 URL
时,网站服务端将恶意代码从 URL
中取出,拼接在 HTML
中返回给浏览器。反射型 XSS
跟存储型 XSS
的区别是:存储型 XSS
的恶意代码存在数据库里,反射型 XSS
的恶意代码存在 URL
里。
反射型 XSS
漏洞常见于通过 URL
传递参数的功能,如网站搜索、跳转等。
由于需要用户主动打开恶意的 URL
才能生效,攻击者往往会结合多种手段诱导用户点击。
POST
的内容也可以触发反射型 XSS
,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见。
DOM
型XSS
DOM
型 XSS
的攻击步骤:
URL
,其中包含恶意代码。 URL
。JavaScript
取出 URL
中的恶意代码并执行。DOM
型 XSS
跟前两种 XSS
的区别:DOM
型 XSS
攻击中,取出和执行恶意代码由浏览器端完成,不需要服务器解析响应的直接参与,属于前端 JavaScript
自身的安全漏洞,而其他两种 XSS
都属于服务端的安全漏洞。
对比表格
XSS 攻击类型 | 存储位置 | 插入点 |
---|---|---|
存储型XSS | 后端数据库 | HTML |
反射型XSS | URL | HTML |
DOM 型XSS | 后端数据库/前端存储/URL | 前端JavaScript |
XSS
攻击的防御措施只要有输入数据的地方,就存在XSS
攻击,XSS
攻击就有两大因素:
所以,防御措施主要从这两个方面下手:
针对用户输入过程
在用户输入过程中,前端过滤输入的恶意代码,然后再提交给后端,这是不可行的,攻击者完全可以绕过前端过滤,直接构造请求提交恶意代码。
对于明确的输入类型,例如数字、URL、电话号码、邮件地址等等内容,进行输入过滤是必要的。
所以不仅需要前端对输入的格式进行格式检查,然后后端做相关的过滤检查。
针对浏览器执行恶意代码
输入过滤不是万全之策,那就要防止浏览器执行恶意代码,这个分为两步进行:
HTML
中出现注入JavaScript
执行时,执行恶意代码XSS
攻击存储型和反射型 XSS
都是在服务端取出恶意代码后,插入到响应 HTML
里的,攻击者刻意编写的“数据”被内嵌到“代码”中,被浏览器所执行。
预防这两种漏洞,有两种常见做法:
HTML
纯前端渲染的过程:
HTML
,此 HTML
中不包含任何跟业务相关的数据HTML
中的 JavaScript
JavaScript
通过 Ajax
加载业务数据,调用 DOM API
更新到页面上在纯前端渲染中,会明确的告诉浏览器:下面要设置的内容是文本(.innerText
),还是属性(.setAttribute
),还是样式(.style
)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。
但纯前端渲染还需注意避免 DOM
型 XSS
漏洞(例如 onload
事件和 href
中的 javascript:xxx
等,请参考下文”预防 DOM
型 XSS
攻击“部分)。
在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO
需求的页面,我们仍然要面对拼接 HTML
的问题。
HTML
如果拼接 HTML
是必要的,就需要采用合适的转义库,对 HTML
模板各处插入点进行充分的转义。
常用的模板引擎,如 doT.js
、ejs
、FreeMarker
等,对于 HTML
转义通常只有一个规则,就是把 & < > " ' /
这几个字符转义掉,确实能起到一定的 XSS
防护作用,但并不完善:
|XSS 安全漏洞|简单转义是否有防护作用| |-|-| |HTML 标签文字内容|有| |HTML 属性值|有| |CSS 内联样式|无| |内联 JavaScript|无| |内联 JSON|无| |跳转链接|无|
所以要完善 XSS
防护措施,我们要使用更完善更细致的转义策略。
DOM
型XSS
攻击DOM
型 XSS
攻击,实际上就是网站前端 JavaScript
代码本身不够严谨,把不可信的数据当作代码执行了。
在使用 .innerHTML
、.outerHTML
、document.write()
时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent
、.setAttribute()
等。
如果用 Vue/React
技术栈,并且不使用 v-html
/dangerouslySetInnerHTML
功能,就在前端 render 阶段避免 innerHTML
、outerHTML
的 XSS
隐患。
DOM
中的内联事件监听器,如 location
、onclick
、onerror
、onload
、onmouseover
等,<a>
标签的 href
属性,JavaScript 的 eval()
、setTimeout()
、setInterval()
等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。
<!-- 内联事件监听器中包含恶意代码 --> ![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2018b/3e724ce0.data:image/png,) <!-- 链接内包含恶意代码 --> <a href="UNTRUSTED">1</a>
<script> // setTimeout()/setInterval() 中调用恶意代码 setTimeout("UNTRUSTED") setInterval("UNTRUSTED") // location 调用恶意代码 location.href = 'UNTRUSTED' // eval() 中调用恶意代码 eval("UNTRUSTED") </script>
虽然在渲染页面和执行 JavaScript
时,通过谨慎的转义可以防止 XSS
的发生,但完全依靠开发的谨慎仍然是不够的。以下介绍一些通用的方案,可以降低 XSS
带来的风险和后果。
Content Security Policy
严格的 CSP
在 XSS
的防范中可以起到以下的作用:
GitHub
使用)。Google Map
移动版在使用)。XSS
,利于尽快修复问题。对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS
发生,但可以增加 XSS
攻击的难度。
HTTP-only Cookie
: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie
XSS
攻击检测两种方法:
XSS
攻击字符串手动检测 XSS
漏洞jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
这个字符串能够检测到存在于 HTML
属性、HTML
文字内容、HTML
注释、跳转链接、内联 JavaScript
字符串、内联 CSS
样式表等多种上下文中的 XSS
漏洞,也能检测 eval()
、setTimeout()
、setInterval()
、Function()
、innerHTML
、document.write()
等 DOM
型 XSS
漏洞,并且能绕过一些 XSS
过滤器。
只要在网站的各输入框中提交这个字符串,或者把它拼接到 URL 参数上,就可以进行检测了。
http://xxx/search?keyword=jaVasCript%3A%2F*-%2F*%60%2F*%60%2F*%27%2F*%22%2F**%2F(%2F*%20*%2FoNcliCk%3Dalert()%20)%2F%2F%250D%250A%250d%250a%2F%2F%3C%2FstYle%2F%3C%2FtitLe%2F%3C%2FteXtarEa%2F%3C%2FscRipt%2F--!%3E%3CsVg%2F%3CsVg%2FoNloAd%3Dalert()%2F%2F%3E%3E
XSS
漏洞常见自动扫描工具寻找 XSS 漏洞,例如 Arachni、Mozilla HTTP Observatory、w3af 等
XSS
攻击简单总结XSS
是后端的责任。而 DOM
型 XSS
是前端 的责任。防御XSS
是需要后端和前端共同参与的系统工程HTML
时进行,而不是在提交用户输入时 HTML
属性、HTML
文字内容、HTML
注释、跳转链接、内联 JavaScript
字符串、内联 CSS
样式表等,所需要的转义规则不一致。 业务 需要选取合适的转义库,并针对不同的上下文调用不同的转义规则总结以下原则减少漏洞的产生:
利用模板引擎 :开启模板引擎自带的 HTML
转义功能。例如:
ejs
中,尽量使用 <%= data %>
而不是 <%- data %>
doT.js
中,尽量使用 {{! data }
而不是 {{= data }
FreeMarker
中,确保引擎版本高于 2.3.24,并且选择正确的 freemarker.core.OutputFormat
onLoad="onload('{{data}}')"
、onClick="go('{{action}}')"
这种拼接内联事件的写法。在 JavaScript
中通过 .addEventlistener()
事件绑定会更安全HTML
的方法比较危险,如果框架允许,使用 createElement
、setAttribute
之类的方法实现。或者采用比较成熟的渲染框架,如 Vue/React
等DOM
属性、链接等位置时,要打起精神,严加防范 CSP
、输入长度配置、接口安全措施等方法,增加攻击的难度,降低攻击的后果 XSS
攻击字符串和自动扫描工具寻找潜在的 XSS
漏洞XSS
利用的是用户对网站的信任,CSRF
利用的是网站对用户网页浏览器的信任。
参考文章: