因为同源策略下,浏览器支持页面引用第三方资源,通过 CORS 支持跨域请求,XSS 可以利用这两点攻击页面。
- 窃取 Cookie 信息。恶意脚本通过
document.cookie获取,利用 CORS 通过 XMLHttpRequest 将数据发送给恶意服务器。 - 监听用户行为。恶意脚本使用
addEventListener可以监听键盘事件,获取用户的输入信息。 - 修改 DOM 伪造假页面。
- 在页面内生成浮窗广告。
- 在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
- 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
- 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
- 在标签的 href、src 等属性中,包含 javascript: 等可执行代码。
- 在 onload、onerror、onclick 等事件中,注入不受控制代码。
- 在 style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。
- 在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。
- hacker 将恶意 JS 代码提交到网站数据库中
- 用户向网站请求包含了恶意 JS 脚本的页面
- 用户浏览页面时,恶意脚本可以获取用户 Cookie 等信息
恶意 JS 脚本包含在了用户请求中,服务器收到请求会把恶意代码反射给浏览器,但不会存储在服务器中。
比如,页面会显示 URL 参数 xss 的值,如果在 URL 的参数中输入http://localhost:3000/?xss=<script>alert("已被xss攻击")</script>,那么这段恶意代码就会被插入页面,反射到用户页面。
这种 XSS 与服务器无关,hacker 将恶意脚本注入页面,修改 HTML 页面内容。
需要阻止恶意脚本的注入和恶意消息的发送。
- 服务器对输入脚本进行过滤或转码
- 利用 CSP,MDN文档。服务器响应头配置
Content-Security-Policy,或者在 HTML 文件的meta标签中添加,指定浏览器可执行的有效来源,让浏览器只执行白名单域中的脚本文件。- 限制浏览器加载其他域的资源文件
- 禁止向第三方域提交数据
- 禁止执行内联脚本和未授权的脚本
- 提供了上报机制
- 使用 HttpOnly 属性。在响应头的 set-cookie 里添加 HttpOnly 字段,那么站点的 Cookie 只能在 HTTP 请求中使用,JS 无法读取这段 Cookie
underscore 里的escape将以下字符做了转义,使用 hex code 代替这些字符:
// List of HTML entities for escaping.
var escapeMap = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
"`": "`",
};
// origin
("<script>alert('XSS');</script>");
// escaped
("<script>alert('XSS');</script>");这种过滤对于内联样式、内联 JS、内联 JSON 并不起效,需要使用更完善的转义策略。Java 工程库中常用的转义库为org.owasp.encoder.
与
encodeURI和encodeURIComponent不同,这两个 API 将 URL 中的特殊符号转成 utf-8 的 16 进制表示。
- 在使用
.innerHTML、.outerHTML、document.write()时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用.textContent、.setAttribute()等。 - 用 Vue/React 技术栈,并且不使用
v-html/dangerouslySetInnerHTML功能,就在前端 render 阶段避免innerHTML、outerHTML的 XSS 隐患。 - 避免使用 DOM 中的内联事件监听器,如
location=字符串、onclick、onerror、onload、onmouseover等 API,用addEventListener更合适。 <a>标签的href=字符串,避免使用eval()、setTimeout()、setInterval()等 API,不能拼接代码。
createElement会把script的 JSX,使用innerHTML的形式,包在一个div里,防止script执行。updateDOMProperties判断是否通过dangerouslySetInnerHTML设置,如果是使用这个 API,则会用setInnerHTML去设置innerHTML,否则会用setTextContent把输入设置成文本。
