摘要: 本文讨论web前端安全问题以及应对措施,浏览器同源策略以及对资源跨域访问的几种解决方案
本文分享自华为云社区《Web安全和浏览器跨域访问》,原文作者:kg-follower 。
今天说一说和前端相关的Web安全问题和开发过程中经常遇到的跨域问题。
1.Web安全
1.1 XSS
基本原理
XSS (Cross-Site Scripting),跨站脚本攻击通过在用户的浏览器内运行非法的HTML标签或JavaScript进行的一种攻击。
攻击手段
攻击者往 Web 页面里插入恶意网页脚本代码,当用户浏览该页面时,嵌入 Web 页面里面的脚本代码会被执行,从而达到攻击者盗取用户信息或其他侵犯用户安全隐私的目的。
XSS攻击分类
反射型xss攻击。通过给被攻击者发送带有恶意脚本的URL或将不可信内容插入页面,当URL地址被打开或页面被执行时,浏览器解析、执行恶意脚本。
反射型xss的攻击步骤:1. 攻击者构造出特殊的 URL或特殊数据;2. 用户打开带有恶意代码的 URL 时,Web服务器将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器;3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行;4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
防御:1.Web页面渲染的所有内容或数据都必须来自服务端;2. 客户端对用户输入的内容进行安全符转义,服务端对上交内容进行安全转义;3.避免拼接html。
存储型xss。恶意脚本被存储在目标服务器上。当浏览器请求数据时,脚本从服务器传回浏览器去执行。
存储型xss的攻击步骤:1. 攻击者将恶意代码提交到目标网站的数据库中;2.用户浏览到目标网站时,前端页面获得数据库中读出的恶意脚本时将其渲染执行。
防御:防范存储型XSS攻击,需要我们增加字符串的过滤:前端输入时过滤;服务端增加过滤;前端输出时过滤。
通常有三种方式防御XSS攻击:1. Content Security Policy(CSP)。CSP 本质上就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少 XSS 攻击。通常可以通过两种方式开启,例如只允许加载相同域下的资源:
设置 HTTP Header 中的 CSP(Content-Security-Policy: default-src 'self')
设置meta 标签的方式(<meta http-equiv="Content-Security-Policy" content="form-action 'self';">)
2. 转义字符。用户的输入永远不可信任的,最普遍的做法就是转义输入输出的内容,对于引号、尖括号、斜杠进行转义:
function escape(str) { str = str.replace(/&/g, '&') str = str.replace(/</g, '<') str = str.replace(/>/g, '>') str = str.replace(/"/g, '&quto;') str = str.replace(/'/g, ''') str = str.replace(/`/g, '`') str = str.replace(///g, '/') return str }
但是对于显示富文本来说,显然不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。对于这种情况,通常采用白名单过滤的办法:
const xss = require('xss') let html = xss('<h1 id="title">XSS Demo</h1><script>alert("xss");</script>') console.log(html) <h1>XSS Demo</h1><script>alert("xss");</script>
经过白名单过滤,dom中包含的<script>标签将不会被执行。
HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 cookie,使得 cookie只有http能够访问。
1.2 CSRF
基本概念
CSRF(Cross-site request forgery跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
CSRF攻击类型
主动型攻击。用户访问网站A并在浏览器保存A的登录状态(cookie等信息),攻击者诱导受害者访问网站B,网站B含有访问A接口的恶意代码,受害者访问B时带着A的登录状态,攻击者便可以冒充用户执行对A的恶意操作。
被动型攻击。攻击者在网站A发布带有恶意链接的评论或内容(提交对A带有增删改的诱导型标签),当其他拥有登录状态的受害者点击评论的恶意链接时,就会冒用受害者登录凭证发起攻击。
CSRF攻击防范
验证HTTP Referer字段。在HTTP头中有Referer字段,他记录该HTTP请求的来源地址,如果跳转的网站与来源地址相符,那就是合法的,如果不符则可能是csrf攻击,拒绝该请求。
SameSite。可以对 Cookie 设置 SameSite 属性。该属性表示 Cookie 不随着跨域请求发送,可以很大程度减少 CSRF 的攻击。
请求中加入token。服务端给用户生成一个token,加密后传递给用户,用户在提交请求时,需要携带这个token,服务端发现token不存在或者token校验不成功,那么就拒绝该请求。
1.3 流量劫持
DNS劫持
DNS劫持就是通过劫持了DNS服务器,通过某些手段来取得某个域名的解析控制权,进而修改此域名的解析结果,导致对该域名的访问由原IP地址转入到修改后的IP,其结果就是对特定的网站不能访问或访问的是假网址。
防御:使用https校验通信双方身份和数据完整性。
点击劫持
攻击者构建了一个非常有吸引力的网页,将被攻击的页面放置在当前页面的 iframe 中,使用样式将 iframe 叠加到非常有吸引力内容的上方,将iframe设置为100%透明,其实就是通过覆盖不可见的页面,诱导用户点击而造成的攻击行为。
防御措施。1. X-FRAME-OPTIONS设置允许iframe加载的域 2. 限制iframe页面中的JavaScript脚本执行。
无论是xss、csrf还是点击劫持,上面讨论的这几种攻击属于前端攻击,原因大多是开发者的脚本或模板代码存在不安全的隐患或是没有考虑网络传输安全问题。下面简单说一说恶意攻击利用网站后台漏洞发起的攻击。
1.4 SQL注入
SQL 注入漏洞存在的原因,就是拼接 SQL 参数。也就是将用于输入的查询参数,直接拼接在 SQL 语句中,恶意攻击者可以构造特殊的sql语句绕过安全验证。
SQL注入条件:1.攻击者可以控制输入的数据;2.服务器要执行的代码拼接了被控制的数据。
SQL注入防御。1. 严格限制Web应用的数据库的操作权限;2. 对进入数据库的特殊字符(’,”,,<,>,&,*,; 等)进行转义处理,或编码转换,类似防御xss攻击时对输入转义;3. 所有的查询语句建议使用数据库提供的参数化查询接口,如使用占位参数或对象关系映射ORM。
1.5 DDOS攻击
DOS攻击通过在网站的各个环节进行攻击,使得整个流程跑不起来,以达到瘫痪服务为目的。最常见的就是发送大量请求导致服务器过载宕机。DDOS攻击的原理就是利用分布式的客户端,向目标发起大量看上去合法的请求,消耗/占用大量资源,从而达到拒绝服务的目的。
攻击方式:1.端口扫描;2.ping洪水;3.SYN洪水;4.FTP跳转攻击;
DDOS防范。1.在服务器上删除未使用的服务,关闭未使用的端口。2. 进行实时监控,封禁某些恶意密集型请求IP段;3. 进行静态资源缓存,隔离源文件的访问,比如CDN加速;4. 隐藏服务器的真实IP地址
3 跨域和同源策略
同源策略是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。所谓同源是指“协议+域名+端口”三者均相同。
同源策略限制了客户端js代码的以下行为:
1.Cookie、LocalStorage 和 IndexDB 无法读取;
2.DOM节点。来自一个源的js只能读写自己源的DOM树不能读取其他源的DOM树。如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。
网站不开启同源策略,钓鱼网站便可以使用iframe标签加载中国银行登录界面,执行脚本进而拿到用户名密码。
当设置了同源策略,父子窗口执行获取对方DOM时会报错。
3.AJAX请求限制
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
除了架设服务器代理,还有以下几种方法规避同源限制:JSONP,WebSocket,CORS,本文详细讨论下后两种方法的实现。
WebSocket。WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。Websocket请求头信息包含一个origin字段,服务器根据这个字段判断是否允许本次通信。
CORS。CORS跨域资源共享是W3C标准,是解决跨域Ajax请求的最常见解决方法。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。只要同时满足以下两大条件,就属于简单请求:
(1) 请求方法是以下三种方法之一:HEAD、GET、POST
(2)HTTP的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段,该字段用来说明,本次请求来自哪个源。服务器根据这个值,决定是否同意这次请求。如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。若该响应的头信息没有包含Access-Control-Allow-Origin字段,就抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。若Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。其中Access-Control-Allow-Origin字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
对于非简单请求,在正式通信之前,会增加一次HTTP查询请求,称为"预检"请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。
除了Origin字段,"预检"请求的头信息包括两个特殊字段。
(1)Access-Control-Request-Method。该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法
(2)Access-Control-Request-Headers。该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。
预检请求的回应。
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。回应最关键的是Access-Control-Allow-Origin字段,表示允许该源的请求,若没有任何CORS相关头信息字段则说明服务器否认该请求。若服务器允许,则Access-Control-Allow-Methods字段是必须的,它的值是一个逗号分隔的字符串,表明服务器支持的方法。如果预检请求包含Access-Control-Request-Headers字段,则返回体中该字段也是必须的,它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。预检请求得到允许回应后,浏览器便发送正常CORS请求。
最近在开发一个前端poc项目时遇到了跨域资源访问被限制的问题,在本地启动angular项目,其他人可以通过ip访问到静态资源,发送ajax请求时被限制。于是想通过配置代理的方式解决这个跨域问题:在和package.json同级的目录中新建proxy.conf.json文件,target字段是后端服务真实的ip,changeOrigin字段设置为true,关闭secure字段。
{ "/": { "target": "http://10.173.99.224:8081/", "changeOrigin": true, "secure": false, "loglevel": "debug" } }
在package.json的启动命令中添加
"scripts": { "ng": "ng", "start": "ng serve --proxy-config proxy.conf.json --host 0.0.0.0", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test" },
--host 0.0.0.0 表示监听所有来源的主机。解决