day32 安全:JS代码和程序都需要注意哪些安全问题?

跨站脚本攻击(XSS)

为了降低出现 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 会将其直译为字符串或类字符串,而不作转换。
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.

另外,默认的情况下,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

DOMParser API,它可以将 parseFromString 方法中的字符串内容加载到 DOM 节点中。对于从服务器端加载结构化的 DOM,这种方式可能很方便,但是却有安全的隐患。替代方案:document.createElement() 和 document.appendChild() 可以降低风险

var parser = new DOMParser();
var html = parser.parseFromString('`);

Blob 和 SVG 的 API 也是需要注意的接口,因为它们存储任意数据,并且能够执行代码,所以很容易成为污点汇聚点(sinks)。
即使是将不带标签的字符串注入 DOM 时,要确保脚本不会趁虚而入也很难。比如下面的例子就可以绕开标签或单双引号的检查,通过冒号和字符串方法执行弹窗脚本,显示"XSS"。

click me

跨站请求伪造
以上就是针对跨站脚本攻击的一些防御方案,接下来我们看看跨站请求伪造(CSRF),这也是另外一个需要注意的安全风险。
请求来源检查
因为 CSRF 的请求是来自应用以外的,所以我们可以通过检查请求源来减少相关风险。在 HTTP 中,有两个标头可以帮助我们检查请求的源头,它们分别是 Referer 和 Origin。

Origin 标头只在 HTTP POST 请求中发送,它表明的就是请求的来源,和 Referer 不同,这个标头也在 HTTPS 请求中存在。

Origin: https://www.example.com:80

Referer 标头也是表示请求来源。除非设置成 rel=noreferer,否则它的显示如下:

Referer: https://www.example.com:80

如果可以的话,你应该两个都检查。如果两个都没有,那基本可以假设这个请求不是标准的请求并且应该被拒绝。这两个标头是安全的第一道防线,但在有一种情况下,它可能会破防。如果攻击者被加入来源白名单了,特别是当你的网站允许用户生成内容,这时可能就需要额外的安全策略来防止类似的攻击了。
使用 CSRF 令牌
使用 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 防御。大多的现代服务器都允许创建一个在执行任何逻辑前,在所有访问中都可以路由到的中间件。
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 更轻盈、更灵活,能使负载更加快速和简便。

此文章为2月Day11学习笔记,内容来源于极客时间《Jvascript进阶实战课》,大家共同进步

你可能感兴趣的:(前端javascript)