y18n引发安全漏洞,警惕javascript原型链污染

简介: 前端的安全问题不容忽视,及时的升级版本很重要,即便是非常微不足道的小组件都可能应发安全问题,来看一看y18n,它仅仅是一个用于处理i18n的npm包而已,却也能爆出高危漏洞。

2021-04-01收到了github的邮件,还以为是关于愚人节的(误),提醒我有一条Dependabot alerts,包依赖存在安全问题,当然这不是第一次了,正常情况我批准merge掉后就不会关心了,不过我发现这个包是y18n,这让我感到奇怪,这个包的功能非常简单,其实就是i18n,做语言国际化用的,逻辑应该非常简单,怎么会出安全问题呢?

我找到了这个安全问题的具体信息,他的CVE ID为CVE-2020-7774,并且上报了SNYK,代码为SNYK-JS-Y18N-1021887,SNYK对此安全问题给出了7.3的高分,并将安全类别划分为CWE-400(资源消耗不受限制),如此严重的问题是怎么回事呢?

找到具体的修复PR,在https://github.com/yargs/y18n/pull/108,commit只有一个,为https://github.com/yargs/y18n/pull/108/commits/e90e8ce71b2fd5aa27c5109884ea47525fde961f,打开这个commit,发现其实主要只改了一行代码,加了两个检测原型的单元测试,这一行代码修改如下

-    this.cache = {
   }
+    this.cache = Object.create(null)

可能有人不明白这两行代码有什么区别,使用{}创建的对象自带原型属性,而Object.create(null)是干净的,在console中就可以试出来,输入{},点开返回的内容,发现其中只有一个__proto__属性,点开这个属性可以发现其中还有constructorhasOwnProperty等等,而输入Object.create(null),返回的是一个没有任何属性的空的{}

为什么带__proto__的对象有安全隐患呢,原因很简单,__proto__是所有对象共享的,而不是一个对象独占,举一个很简单的例子,在console运行如下code

let foo = {
   }
let bar = {
   }
foo.__proto__.isBad = true
bar.isBad
//true

我们发现bar.isBad得到了true,其实此时任何原型对象都有了一个isBad,这就足以说明其危险性,而Object.create(null)是没有__proto__的,即使强行赋一个__proto__的值,也不会改变其他对象的__proto__

当然在很多时候我们发现不少项目都有直接写{}的习惯,他们都有安全问题吗?当然不是,如果你能完全控制好自己的对象,不使其收到用户输入与环境的影响,那自然是没问题的,但上文提到的cache对象并不是这样,大致阅读下index.ts的代码就可以发现,外部可以给予它任何属性,没有做任何的检查,这当然是存在严重的问题的,使用y18n的程序只需要被注入一小段以__proto__为locale代码,系统很快就会奔溃掉。

关于使用Object.freeze防止原型链污染

其实除了用Object.create(null)创建对象以外,如果适当的时机使用Object.freeze()冻结__proto__,这个问题也不会发生

const foo = {
   }
const bar = {
   }
foo.progress//progress of this foo object
foo.__proto__ = Object.freeze(foo.__proto__)
foo.__proto__.isBad = true
//foo.__proto__.isBad is undefined

freeze掉__proto__之后再对__proto__进行修改时,不会产生任何效果,因为foo已经被冻结,此时即使对象暴露在外部,全局__proto__指针也是安全的,它事实上已经是只读的,但请注意只冻结对象是没有用的,Object.freeze无法冻结整个属性链,例如

const a = Object.freeze({
   b: {
   c: 1}})
a.b.c = 2

这样修改是成功的,因为Object.freeze只冻结了b属性,但b自己也是一个对象,它的属性c是不会被冻结的,在__proto__上也是一样的,

const foo = Object.freeze({
   })
const bar = {
   }
foo.__proto__.isBad = true
bar.isBad
//true

__proto__本身被冻结了,foo.__proto__ = xx是不会生效的,但是对__proto__中属性的操作仍然都是有效的

其他注意事项

  1. 不少情况下,Map都要比Object更好,应该经常注意是否具备用Map替代Object的情况,在2016年ES6兴起的时候,国外就有社区对这个问题进行了大量交流,也有很多大牛发文响应这一观点,近年来也有很多相关的中文文章
  2. 对外部JSON输入进行安全验证,例如使用jpv 一类的验证器,不过这些组件的安全问题也是值得关注的,例如CVE-2020-17479这样伪装成数组绕过验证的神奇漏洞
  3. 防止直接的代码注入,例如在web项目的console中警告用户不要输入未知代码,例如Facebook使用多国语言在console中警告用户停止输入未知内容的行为,在electron应用中应该在发布时关闭develop面板

FYI

本文写作于2021年4月2日并发布于lyrieek的掘金,于2023年7月16日进行修订发布于lyrieek的阿里云开发者社区。

目录
相关文章
|
2天前
|
JavaScript 前端开发
谈谈对 JavaScript 中的原型链的理解。
JavaScript中的原型链是实现继承和共享属性的关键机制,它通过对象的`prototype`属性连接原型对象。当访问对象属性时,若对象本身没有该属性,则会查找原型链。此机制减少内存占用,实现代码复用。例如,实例对象可继承原型对象的方法。原型链也用于继承,子类通过原型链获取父类属性和方法。然而,原型属性共享可能导致数据冲突,且查找过程可能影响性能。理解原型链对JavaScript面向对象编程至关重要。如有更多问题,欢迎继续探讨😊
16 3
|
2天前
|
JavaScript 前端开发 安全
JavaScript原型链的使用
【4月更文挑战第22天】JavaScript中的原型链是理解继承的关键,它允许对象复用属性和方法,减少代码冗余。示例展示如何通过原型链实现继承、扩展内置对象、构造函数与原型链的关系以及查找机制。应注意避免修改`Object.prototype`,使用安全方式设置原型链,并谨慎处理构造函数和副作用。
|
1天前
|
JavaScript 前端开发
JavaScript 原型链继承:掌握面向对象的基础
JavaScript 原型链继承:掌握面向对象的基础
|
2天前
|
JavaScript 前端开发
JavaScript原型链:工作原理与深入探究
【4月更文挑战第22天】JavaScript原型链是对象属性查找的关键,它通过对象间的链接形成链式结构。当访问属性时,JS从对象自身开始查找,若未找到则沿原型链向上搜索,直至`null`。原型链用于继承、扩展内置对象和实现多态,但要注意避免修改内置对象原型、控制链长度及使用`Object.create()`创建对象。理解并合理运用原型链能深化JS面向对象编程的理解。
|
2天前
|
JavaScript
什么是js的原型链
什么是js的原型链
|
2天前
|
JavaScript 前端开发
深入探讨JavaScript中的原型链与继承机制
JavaScript作为一种灵活而强大的编程语言,其独特的原型链与继承机制是其核心特性之一。本文将深入探讨JavaScript中的原型链与继承机制,从基础概念到实际应用,帮助读者更好地理解和利用JavaScript的继承特性。
|
2天前
|
JavaScript 前端开发
在JavaScript中,如何优化原型链的性能?
在JavaScript中,如何优化原型链的性能?
18 2
|
2天前
|
JavaScript 前端开发
谈谈对 JavaScript 中的原型链的理解。
谈谈对 JavaScript 中的原型链的理解。
18 1
|
2天前
|
JavaScript 前端开发
js继承的超详细讲解:原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承、class继承
js继承的超详细讲解:原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承、class继承
60 0
|
2天前
|
JavaScript 前端开发
深入理解 JavaScript 对象原型,解密原型链之谜(下)
深入理解 JavaScript 对象原型,解密原型链之谜(下)