XSS
什么是XSS
Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击
。攻击者通过在目标网站上注入恶意脚本
,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。
XSS 的本质是:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。
XSS 漏洞的发生和修复
小案例
开发一个搜索页,根据 URL
参数决定关键词的内容。HTML代码如下:
<input type="text" value="<%= getParameter("keyword") %>">
<button>搜索</button>
<div> 您搜索的关键词是:<%= getParameter("keyword") %></div>
当浏览器请求http://xxx/search?keyword="><script>alert('XSS')</script>
时,服务端会解析出请求参数keyword
,得到"><script>alert('XSS')</script>
,拼接到 HTML 中返回给浏览器。形成如下HTML:
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜索</button>
<div>您搜索的关键词是:"><script>alert('XSS');</script>
</div>
浏览器无法分辨出 <script>alert('XSS');</script>
是恶意代码,因而将其执行。
这里不仅仅 div 的内容被注入了,而且 input 的 value 属性也被注入,alert会弹出两次。
其实,这只是浏览器把用户的输入当成了脚本进行了执行。那么只要告诉浏览器这段内容是文本就可以了。
对代码片段进行转译
<input type="text" value="<%= escapeHTML(getParameter("keyword")) %>">
<button>搜索</button>
<div> 您搜索的关键词是:<%= escapeHTML(getParameter("keyword")) %> </div>
function escapeHTML(text) {return text.replace(/[<>\"&\'\/]/g, function(match, pos, originalText) {switch(match) {case "<":return "<";case ">":return ">";case "&":return "&";case "\"":return """;case "\'":return "'";case "/":return "/";}})
}
经过了转义函数的处理后,最终浏览器接收到的响应为:
<input type="text" value=""><script>alert('XSS');</script>">
<button>搜索</button>
<div>您搜索的关键词是:"><script>alert('XSS');</script>
</div>
恶意代码都被转义,不再被浏览器执行,而且搜索词能够完美的在页面显示出来。
结论
:
- 页面中包含的
用户输入内容
都在固定的容器或者属性内,以文本
的形式展示。* 攻击者利用这些页面的用户输入区域
,拼接特殊格式的字符串,突破原有位置的限制,形成了代码片段。* 攻击者通过在目标网站上注入脚本
,使之在用户的浏览器上运行,从而引发潜在风险。* 通过 HTML 转义,可以一定程度上
防止 XSS 攻击。#### 注意特殊的 HTML 属性、JavaScript API
有些情况,虽然对代码进行了转义,但还是会有风险:
<a href="<%= escapeHTML(getParameter("redirect_to")) %>">跳转...</a>
这段代码,当攻击 URL 为 http://xxx/?redirect_to=javascript:alert('XSS')
,服务端响应就成了:
<a href="javascript:alert('XSS')">跳转...</a>
虽然代码不会立即执行,但一旦用户点击 a
标签时,浏览器会就会弹出“XSS”。
用户的数据并没有在位置上突破我们的限制,仍然是正确的 href 属性。但其内容并不是我们所预期的类型。
原来不仅仅是特殊字符,javascript:
如果出现在特定的位置也会引发 XSS 攻击。
可以通过设置白名单的方式解决:
// 根据项目情况进行过滤,禁止掉 "javascript:" 链接、非法 scheme 等
allowSchemes = ["http", "https"];
valid = isValid(getParameter("redirect_to"), allowSchemes);
if (valid) {<a href="<%= escapeHTML(getParameter("redirect_to"))%>">跳转...</a>
} else {<a href="/404">跳转...</a>
}
结论:
-
仅仅对插入到html中的代码进行转译还不够。* 对于链接跳转,如
<a href="xxx"
或location.href="xxx"
,要检验其内容,禁止以javascript:
开头的链接,和其他非法的 scheme。漏洞总结:
-
在 HTML 中内嵌的文本中,恶意内容以
script 标签
形成注入。* 在标签属性
中,恶意内容包含引号,从而突破属性值的限制
,注入其他属性或者标签。* 在标签的 href、src 等属性中,包含javascript:
等可执行代码。### XSS 分类
根据攻击的来源,XSS 攻击可分为存储型、反射型和 DOM 型三种。
存储型 XSS(持久型)
存储型 XSS 的攻击步骤:
1.攻击者将恶意代码提交到目标网站的数据库
中。
2.用户打开目标网站时,网站服务端将恶意代码从数据库取出
,拼接在 HTML 中返回给浏览器。
3.用户浏览器接收到响应后解析执行
,混在其中的恶意代码也被执行。
4.恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为
,调用目标网站接口,执行攻击者指定的操作。
这种攻击常见于带有用户保存数据的网站功能,如论坛发帖
、商品评论
、用户私信
等。
反射型 XSS(非持久型)
反射型 XSS 的攻击步骤:
1.攻击者构造出特殊的 URL
,其中包含恶意代码。
2.用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出
,拼接在 HTML 中返回给浏览器。
3.用户浏览器接收到响应后解析执行
,混在其中的恶意代码也被执行
。
4.恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为
,调用目标网站接口,执行攻击者指定的操作。
反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。
反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索
、跳转
等。
由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。
DOM 型 XSS
基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击。
DOM 型 XSS 的攻击步骤:
1.攻击者构造出特殊的 URL
,其中包含恶意代码。
2.用户打开带有恶意代码的 URL
。
3.用户浏览器接收到响应后解析执行
,前端 JavaScript 取出 URL 中的恶意代码并执行。
4.恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为
,调用目标网站接口,执行攻击者指定的操作。
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端
完成,属于前端 JavaScript 自身的安全漏洞
,而其他两种 XSS 都属于服务端的安全漏洞
。
举个例子:
<h2>XSS: </h2>
<input type="text" id="input">
<button id="btn">Submit</button>
<div id="div"></div>
<script> const input = document.getElementById('input');const btn = document.getElementById('btn');const div = document.getElementById('div');let val; input.addEventListener('change', (e) => {val = e.target.value;}, false);btn.addEventListener('click', () => {div.innerHTML = `<a href=${val}>testLink</a>`}, false); </script>
点击 Submit
按钮后,会在当前页面插入一个链接,其地址为用户的输入内容。如果用户在输入时构造了如下内容:
'' onclick=alert(/xss/)
用户提交之后,页面代码就变成了:
<a href onlick="alert(/xss/)">testLink</a>
此时,用户点击生成的链接,就会执行对应的脚本:
XSS 攻击的预防
预防DOM型 XSS 攻击
在使用 .innerHTML
、.outerHTML
、document.write()
时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent
、.setAttribute()
等。
如果用 Vue/React 技术栈,使用 v-html
/dangerouslySetInnerHTML
功能时,可以配合DOMPurify
插件来处理插入的数据。
DOM 中的事件监听器,如 location
、onclick
、onerror
、onload
、onmouseover
等,<a>
标签的 href
属性,JavaScript 的 eval()
、setTimeout()
、setInterval()
等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。
<!-- 内联事件监听器中包含恶意代码 -->
<img onclick="UNTRUSTED" onerror="UNTRUSTED" src="data:image/png,">
<!-- 链接内包含恶意代码 -->
<a href="UNTRUSTED">1</a>
<script> // setTimeout()/setInterval() 中调用恶意代码
setTimeout("UNTRUSTED")
setInterval("UNTRUSTED")
// location 调用恶意代码
location.href = 'UNTRUSTED'
// eval() 中调用恶意代码
eval("UNTRUSTED") </script>
其他 XSS 防范措施
CSP(Content Security Policy)
严格的 CSP 在 XSS 的防范中可以起到以下的作用:
禁止加载外域代码
,防止复杂的攻击逻辑。禁止外域提交
,网站被攻击后,用户的数据不会泄露到外域。禁止内联脚本执行
(规则较严格,目前发现 GitHub 使用)。禁止未授权的脚本执行
(新特性,Google Map 移动版在使用)。合理使用上报
可以及时发现 XSS,利于尽快修复问题。
两种方法可以启用 CSP。一种是通过 HTTP 头信息的Content-Security-Policy
的字段。另一种是通过网页的<meta>
标签。
HTTP 头信息的Content-Security-Policy
(首选):
"Content-Security-Policy:" 策略
"Content-Security-Policy-Report-Only:" 策略
通过网页的<meta>
标签:
<meta http-equiv="content-security-policy" content="策略">
<meta http-equiv="content-security-policy-report-only" content="策略">
default-src ‘none’; script-src ‘self’; connect-src ‘self’; img-src ‘self’; style-src ‘self’;
该策略允许加载同源的图片、脚本、AJAX和CSS资源,并阻止加载其他任何资源,对于大多数网站是一个不错的配置。
输入内容长度控制
对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。
其他安全措施
- HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。
Set-Cookie: Name=Value; expires=Wednesday, 01-May-2014 12:45:10 GMT; HttpOnly
- 验证码:防止脚本冒充用户提交危险操作。
CSRF
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站
,在第三方网站中,向被攻击网站发送跨站请求
。利用受害者在被攻击网站已经获取的注册凭证
,绕过
后台的用户验证
,达到冒充用户对被攻击的网站执行某项操作的目的。
一个典型的CSRF攻击有着如下的流程:
用户登录a.com
,并保留了登录凭证(Cookie)。攻击者引诱用户访问了b.com
。b.com
向a.com
发送了一个请求:a.com/act=xx
a.com
接收到请求后,对请求进行验证,并确认是用户的凭证
,误以为是受害者自己发送的请求。a.com
正常执行了act=xx。- 攻击完成,攻击者在用户不知情的情况下,冒充用户,让
a.com
执行了自己定义的操作。
几种常见的攻击类型
- GET类型的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就会收到包含受害者登录信息的一次跨域请求。
- POST类型的CSRF
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>
- 链接类型的CSRF
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">重磅消息!!
<a/>
由于之前用户登录了信任的网站A,并且保存登录状态,只要用户主动访问上面的这个PHP页面,则表示攻击成功。
CSRF的特点
- 攻击一般发起在
第三方网站
,而不是被攻击的网站。被攻击的网站无法防止攻击发生。* 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作
;而不是直接窃取数据。* 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”
。* 跨站请求可以用各种方式:图片URL
、超链接
、CORS
、Form提交
等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。CSRF通常是跨域的,因为外域通常更容易被攻击者掌控。但是如果本域下有容易被利用的功能,比如可以发图和链接的论坛和评论区,攻击可以直接在本域下进行,而且这种攻击更加危险。
防御策略
同源检测
因为CSRF通常发生在外域,针对外域做以下防御,在HTTP协议中,每一个异步请求都会携带两个Header,用于标记来源域名:
- Origin Header* Referer Header这两个Header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。服务器可以通过解析这两个Header中的域名,确定请求的来源域。
Referrer Policy: strict-origin-when-cross-origin
Origin Header
Request Headers:origin: https://www.jd.com
在部分与CSRF有关的请求中,请求的Header中会携带Origin字段。字段内包含请求的域名(不包含path及query)。
如果Origin存在,那么直接使用Origin中的字段确认来源域名就可以。
但是Origin在以下两种情况下并不存在:
- IE11同源策略: IE 11 不会在跨站CORS请求上添加Origin标头,Referer头将仍然是唯一的标识。* 302重定向: 在302重定向之后Origin不包含在重定向的请求中,因为Origin可能会被认为是其他来源的敏感信息。对于302重定向的情况来说都是定向到新的服务器上的URL,因此浏览器不想将Origin泄漏到新的服务器上。##### Referer Header
Request Headers:referer: https://m.knowbox.cn/
Referrer Policy States:
新的Referrer规定了五种策略:
No Referrer
:任何情况下都不发送Referrer信息No Referrer When Downgrade
:仅当协议降级(如HTTPS页面引入HTTP资源)时不发送Referrer信息。是大部分浏览器默认策略。Origin Only
:发送只包含host部分的referrer.Origin When Cross-origin
:仅在发生跨域访问时发送只包含host的Referer,同域下还是完整的。与Origin Only的区别是多判断了是否Cross-origin。协议、域名和端口都一致,浏览器才认为是同域。Unsafe URL
:全部都发送Referrer信息。最宽松最不安全的策略。
设置Referrer Policy的方法有三种:
1.在CSP设置
Content-Security-Policy: referrer no-referrer|no-referrer-when-downgrade|origin|origin-when-cross-origin|unsafe-url;
2.页面头部增加meta标签
<meta name="referrer" content="no-referrer|no-referrer-when-downgrade|origin|origin-when-crossorigin|unsafe-url">
3.a标签增加referrerpolicy属性
<a href="http://example.com" referrer="no-referrer|origin|unsafe-url">xxx</a>
因此,攻击者可以在自己的请求中隐藏Referer:
<img src="http://bank.example/withdraw?amount=10000&for=hacker" referrerpolicy="no-referrer">
那么这个请求发起的攻击将不携带Referer。所以这种设置安全性不太高。
另外在以下情况下Referer没有或者不可信:
1.IE6、7下使用window.location.href=url进行界面的跳转,会丢失Referer。2.IE6、7下使用window.open,也会缺失Referer。3.HTTPS页面跳转到HTTP页面,所有浏览器Referer都丢失。4.点击Flash上到达另外一个网站的时候,Referer不太可信。#### CSRF Token
CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。
原理:
CSRF Token的防护策略分为三个步骤:
1.将CSRF Token生成输出到页面中
首先,用户打开页面的时候,服务器需要给这个用户生成一个Token
,该Token通过加密算法对数据进行加密
,一般Token都包括随机字符串和时间戳的组合,显然在提交时Token不能再放在Cookie中了,否则又会被攻击者冒用。
2.页面提交的请求携带这个Token
对于GET请求,Token将附在请求地址之后,这样URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上:
<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>
这样,就把Token以参数的形式加入请求了。
3.服务器验证Token
是否正确
当用户从客户端得到了Token,再次提交给服务器的时候,服务器需要判断Token的有效性,验证过程是先解密Token,对比加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个Token就是有效的。
但是此方法的实现比较复杂,需要给每一个页面都写入Token(前端无法使用纯静态页面),每一个Form及Ajax请求都携带这个Token,后端对每一个接口都进行校验,并保证页面Token及请求Token一致。
Samesite Cookie属性
防止CSRF攻击的办法已经有上面的预防措施。为了从源头上解决这个问题,Google起草了一份草案来改进HTTP协议,那就是为Set-Cookie响应头新增Samesite属性,它用来标明这个 Cookie是个“同站 Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方Cookie,Samesite 有两个属性值,分别是 Strict 和 Lax
Samesite=Strict
如果SamesiteCookie被设置为Strict,浏览器在任何跨域请求中都不会携带Cookie,新标签重新打开也不携带,所以说CSRF攻击基本没有机会。这种称为严格模式,表明这个 Cookie 在任何情况下都不可能作为第三方 Cookie。
SamesiteCookie目前有一个致命的缺陷:不支持子域。跳转子域名或者是新标签重新打开刚登陆的网站,之前的Cookie都不会存在。尤其是有登录的网站,那么我们新打开一个标签进入,或者跳转到子域名的网站,都需要重新登录。对于用户来讲,可能体验不会很好。
另外一个问题是Samesite的兼容性不是很好,现阶段除了从新版Chrome和Firefox支持以外,Safari以及iOS Safari都还不支持。
Samesite=Lax
如果SamesiteCookie被设置为Lax,那么其他网站通过页面跳转过来的时候可以使用Cookie,可以保障外域连接打开页面时用户的登录状态。但相应的,其安全性也比较低。
总结
常见防御 XSS 攻击措施:
- CSP
- HttpOnly 防止劫取 Cookie
- 用户的输入检查
- 服务端的输出检查
常见防御 CSRF 攻击措施:
- 同源检测
- Token验证
- 分布式校验
- 双重Cookie验证
- Samesite Cookie
参考文章
网络安全基础入门需要学习哪些知识?
网络安全学习路线
这是一份网络安全从零基础到进阶的学习路线大纲全览,小伙伴们记得点个收藏!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vgHGd8u7-1676466283433)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]编辑
阶段一:基础入门
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-quMQWVNP-1676466283434)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
网络安全导论
渗透测试基础
网络基础
操作系统基础
Web安全基础
数据库基础
编程基础
CTF基础
该阶段学完即可年薪15w+
阶段二:技术进阶(到了这一步你才算入门)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m2l7iJDy-1676466283434)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
弱口令与口令爆破
XSS漏洞
CSRF漏洞
SSRF漏洞
XXE漏洞
SQL注入
任意文件操作漏洞
业务逻辑漏洞
该阶段学完年薪25w+
阶段三:高阶提升
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X5hK7Km6-1676466283435)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
反序列化漏洞
RCE
综合靶场实操项目
内网渗透
流量分析
日志分析
恶意代码分析
应急响应
实战训练
该阶段学完即可年薪30w+
阶段四:蓝队课程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xotRl8Un-1676466283435)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]
蓝队基础
蓝队进阶
该部分主攻蓝队的防御,即更容易被大家理解的网络安全工程师。
攻防兼备,年薪收入可以达到40w+
阶段五:面试指南&阶段六:升级内容
需要上述路线图对应的网络安全配套视频、源码以及更多网络安全相关书籍&面试题等内容
同学们可以扫描下方二维码获取哦!