安全 #
通常情况下,执行像 mathjs 的表达式解析器所支持的任意表达式会存在风险。当您使用 mathjs 让用户执行任意表达式时,最好花点时间考虑一下潜在的安全和稳定性影响,尤其是在服务器端运行代码时。
安全风险 #
用户可能试图通过表达式解析器注入恶意 JavaScript 代码。mathjs 的表达式解析器提供了一个沙盒环境来执行表达式,这应该能防止这种情况发生。但仍然可能存在未知的安全漏洞,因此需要谨慎,尤其是在允许在服务器端执行任意表达式时。
mathjs 的表达式解析器以一种可控的方式将输入解析成表达式树或抽象语法树(AST)。在“编译”步骤中,它尽可能地对表达式的静态部分进行预处理,并生成一个高性能的函数,该函数可以动态传递作用域来重复计算表达式。
解析器主动阻止访问 JavaScript 的内置函数 eval 和 new Function,这是安全攻击的主要原因。mathjs 4 及更新版本在底层不使用 JavaScript 的 eval。版本 3 及更早版本在编译步骤中使用了 eval。这并非直接的安全问题,但可能增加攻击面。
在运行 Node.js 服务器时,了解不同类型的安全风险很重要。在浏览器中运行时风险可能有限,但仍需注意 跨站脚本 (XSS) 漏洞。Gergely Nemeth 在文章 Node.js 安全清单 中对 Node.js 服务器的安全风险进行了概述。
降低表达式解析器风险 #
表达式解析器中有少数函数会带来最大的安全风险。
import和createUnit可以改变内置功能,允许覆盖现有函数和单位;reviver可以将值解析为类实例。evaluate、parse、simplify、derivative和resolve会将任意输入解析成可操作的表达式树。
为了使表达式解析器更安全,同时仍支持大部分功能,可以禁用这些函数。
import { create, all } from 'mathjs'
const math = create(all)
const limitedEvaluate = math.evaluate
math.import({
// most important (hardly any functional impact)
'import': function () { throw new Error('Function import is disabled') },
'createUnit': function () { throw new Error('Function createUnit is disabled') },
'reviver': function () { throw new Error('Function reviver is disabled') },
// extra (has functional impact)
'evaluate': function () { throw new Error('Function evaluate is disabled') },
'parse': function () { throw new Error('Function parse is disabled') },
'simplify': function () { throw new Error('Function simplify is disabled') },
'derivative': function () { throw new Error('Function derivative is disabled') },
'resolve': function () { throw new Error('Function resolve is disabled') },
}, { override: true })
console.log(limitedEvaluate('sqrt(16)')) // Ok, 4
console.log(limitedEvaluate('parse("2+3")')) // Error: Function parse is disabled
发现安全漏洞?请私下报告! #
您发现了安全漏洞?太棒了!我们希望您没有恶意,而是希望帮助修复问题。请通过电子邮件或其他私密渠道联系维护人员,以私密方式报告漏洞。这样,我们就能在与所有人(包括不法分子)公开问题之前共同努力修复。
稳定性风险 #
用户可能会意外或故意执行一个繁重的表达式,例如创建非常大的矩阵。这可能导致 JavaScript 引擎内存不足,或者 CPU 长时间占用 100% 导致冻结。
为了防止此类问题,可以在单独的 Web Worker 或 child_process 中运行表达式解析器,这样它就不会影响主进程。当 Worker 运行时间过长或消耗过多内存时,可以将其终止。一个有用的库是 workerpool,它使得在浏览器和 Node.js 中管理 Worker 池变得容易。