使用 eval() 在 JavaScript 中运行实时生成的代码

信息安全 网页浏览器 javascript
2021-08-12 18:28:47

考虑一个前端 JavaScript 应用程序,其中需要根据一些简单的逻辑(用户拥有的角色和其他一些逻辑状态)显示或隐藏菜单项。

引入了一种简单的语言以简洁易读的方式定义此逻辑,并且为每个菜单项分配了一个字符串条件,如下所示"isLoggedIn() AND NOT role(PARTNER)"

为了实际检查这个条件,实现了一个简单的编译器,它将这种语言翻译成一个有效的 JavaScript 代码字符串,并使用eval()最后返回的布尔结果来执行它。

编译器通过替换一些结构来工作,比如AND,ORNOT有效的 JavaScript 等价物,比如&&,||!使用简单的正则表达式:

private compileExpression(expression: string) {

  // Adding commas around function arguments (to make them strings)
  expression = expression.replace(/(\w+)\((.*?)\)/g, `$1('$2')`);

  // Replacing "AND"
  expression = expression.replace(/\sAND\s/g, ' && ');

  // Replacing "OR"
  expression = expression.replace(/\sOR\s/g, ' || ');

  // Replacing "NOT"
  expression = expression.replace(/(\s|^)NOT\s/g, ' !');

  // Prefixing predicates
  expression = expression.replace(/(\w+)\((.*?)\)/g, 'Π.$1($2)');

  return expression;

}

这变成"isLoggedIn() AND NOT role(PARTNER)""Π.isLoggedIn() && !Π.role('PARTNER)"然后由 执行eval()

public matchCondition = (condition: string): boolean => {

  // Using greek letter "P" for predicate (for shortness and uniqueness)
  // noinspection NonAsciiCharacters
  const Π = this.predicates;

  const expression = this.compileExpression(condition);

  const result = eval(expression);

  return result;

}

Π是一个具有简单谓词函数的对象,它返回布尔值,isLoggedIn(): booleanrole(roleName: string): boolean

正在翻译和执行的表达式作为字符串静态存储在本地对象中,并且无法从全局上下文中访问。此外,所有表达式都是由开发人员编写的,而不是以任何方式来自应用程序的用户。

使用eval()这种方式是否安全,还是应该不惜一切代价避免(例如“eval 是邪恶的”、“从不使用 eval”等)?

有哪些可能的攻击向量可以用来破坏这种 eval 的使用?

如果表达式字符串将使用 XHR/Fetch 从 HTTPS 服务器加载,它会在安全方面改变情况吗?

引入这种语言而不是直接在代码中定义规则的原因是它要求这些条件可以在字符串值中定义,例如在 JSON 文件中。另一个原因是这样的语言更容易一目了然。

4个回答

在这种情况下使用eval不会产生任何漏洞,只要攻击者不能干扰传递给matchCondition.

如果您发现以这种方式更容易阅读/编程,并且您确信任何不受信任的输入将永远不会进入您的表达式编译器,那么就去做吧。

eval 不是邪恶的,不受信任的数据才是。


请注意,完全有可能eval通过提取谓词然后使用您的自定义函数处理它们来避免 ,例如:

if (predicate === 'isLoggedIn()') {
    return Π.isLoggedIn();
}

今天,一切都是由开发人员编写的。下个月或明年,有人会说“嘿,为什么不让用户自己写呢?” 巴姆。

此外,即使规则仅由开发人员编写,它们是否或将包含任何用户来源的数据?例如,标题、名称、类别之类的东西?这可能很快导致 XSS 攻击。

你的正则表达式是如此“开放”(使用很多.*没有任何验证),如果有任何不愉快的事情进入,它会eval在一分钟内直接溜走。

至少,如果你想保留eval,你应该有更严格的表达式而不是.*但这些很快就会变得难以理解,或者成为许多实际案例的障碍。

外观和期望

如果某件事看起来像是一种安全的表达方式,人们可能会像对待它一样对待它。如果一个字段看起来像任何其他数据字段,人们(甚至是开发人员)可能会将不受信任的数据放在那里。如果使用完整级别的应用程序访问来评估某些东西,它应该看起来和感觉像代码。

另一个问题是预编译器中的细微错误,这可能会引入不需要的错误/安全漏洞。在恶意攻击者可以利用某些东西之前,大多数漏洞都是从不需要的错误开始的。没有适当的审查/测试和严格定义的语法的新元语言只是另一层混乱和等待发生的错误。

如果只有开发人员根据您的条件编写代码,为什么不直接使用纯 JavaScript?元语言几乎没有带来任何好处。代码是由像代码一样的人处理的。

前端 javascript 本身完全取决于运行代码的客户端。 如果您依赖前端 javascript 来确保安全,那么您已经无法保护您的应用程序忘记评估。如果他们愿意,客户可以用他们自己的实现替换您的整个网站。因此,您的服务器应该验证客户端要求它执行的所有操作。

在您的情况下,您应该问自己,如果用户查看他们没有足够权限查看的菜单项是否违反安全规定。如果是这样,那么服务器不应这些菜单项传递给客户端,因为用户可以查看您传递给他们的任何 javascript。如果没有,那么您根本就没有任何安全问题——正如其他人所提到的,只有在针对未经清理的代码运行 eval 时,它才是“坏”的。由于您的 eval'd 代码目前已被清理,所以我认为您没有安全问题。