Object.prototype.toString.call()是否存在安全问题?是否应仅检查原始数值并规避_.isNumber()?
这问题问到点子上了——JavaScript的类型系统本身就有不少坑,尤其是涉及到包装对象和自定义子类的时候,安全风险确实值得重视。我来一步步给你捋清楚:
为什么基于Object.toString的方法(比如_.isNumber)有安全隐患?
Underscore的_.isNumber本质是通过Object.prototype.toString.call(value)来判断类型,只要返回"[object Number]"就会被识别为数字。但问题在于:数字包装对象(包括你自定义的Num子类实例)都会满足这个条件,而这些对象的valueOf、toString方法是可以被篡改的,就像你给出的例子:
class Num extends Number { constructor (q) { super(q); this.value = q; } valueOf () { console.log('HACKED'); // 换成窃取数据、修改逻辑的恶意代码就危险了 return this.value; } } var n = new Num(5);
当这个对象被用于隐式类型转换(比如和数字做运算、用==比较、甚至被某些API自动转换)时,就会触发valueOf方法,执行里面的代码。如果是不可信来源的对象,这完全可能成为注入恶意逻辑的入口。
什么时候应该用原始值检查(Number.isFinite/Number.isInteger)?
如果你的代码需要处理不可信输入(比如用户提交的数据、第三方接口返回的内容),或者你需要确保处理的是真正的原始数字类型,那必须优先用Number.isFinite()或Number.isInteger()。这两个方法的特点是:
- 只对原始数字返回
true - 对任何数字包装对象(包括自定义子类实例)都返回
false
比如测试你的恶意对象:
Number.isFinite(n); // false Number.isInteger(n); // false
这样就能直接把有风险的对象挡在外面,避免触发恶意方法。
是不是必须完全避免_.isNumber这类方法?
也不是绝对的。如果你的代码运行在可控环境里(比如内部工具、没有外部不可信输入的业务逻辑),或者你确实有场景需要识别数字包装对象(虽然这种场景现在非常少见,因为几乎没人会主动用new Number()创建数字),那_.isNumber是可以正常使用的。
但一定要明确:在处理不可信输入的场景下,绝对要避免这类方法,否则很容易引入安全漏洞。
额外建议
如果有时候你不确定输入是原始值还是包装对象,又需要安全处理,可以先尝试把值转换为原始数字,但要注意:转换过程(比如Number(n))也可能触发valueOf。所以最安全的做法还是先通过Number.isFinite()或Number.isInteger()判断类型,确认是原始数字后再处理。
内容的提问来源于stack exchange,提问作者Killy

