如何在网页中安全地“评估”用户代码?

13
我正在开发一个Web应用程序来教授编程概念。网页上有关于编程概念的一些文本,然后让用户在文本编辑器窗口中键入JavaScript代码来尝试解决编程问题。当用户单击“提交”时,我会分析他们键入的文本以查看他们是否已经解决了问题。例如,我要求他们“编写一个名为f的函数,将其参数加三”。

以下是我分析用户文本的过程:

  1. 使用严格设置在文本上运行JSLint,特别是不假定浏览器或控制台功能。
  2. 如果有任何错误,请显示错误并停止。
  3. eval(usertext);
  4. 循环遍历传递任务的条件eval(condition)。 一个示例条件是"f(1)===4"。 条件来自可信源。
  5. 显示通过/未通过的条件。

我的问题:这足以防止安全问题吗? 我还能做什么来保持警惕? 有更好的方法可以实现我的需求吗?

如果相关,我的应用程序在具有Python后端的Google App Engine上,使用JQuery,具有单独的用户帐户。


我想知道 jsfiddle 是否有任何源代码/注释可用... 我确定在某些情况下,它仅仅是因为该网站没有易受 XSS 或类似攻击。 (XSS 将允许某人提供链接到在该页面上评估的代码,然后可能以查看链接的实际用户的身份“运行”。) - user166390
我必须说当我在压缩之前使用JSLint扫描我的JS以查找问题时,我会尝试禁用所有仅代表作者偏好而不是实际代码问题的功能,然后忽略90%的内容并寻找缺少分号等问题。如果我是你,我会考虑一下你的用户有多喜欢阅读JSLint发现的“错误”。 - tomfumb
这是一个很好的观点,我会添加一个选项来关闭“风格”检查。但是我希望默认情况下是严格的,因为项目的一部分是教授规范的JavaScript编程。 - Nathan Whitehead
5个回答

12
据我所知,如果你只为用户评估输入,这并不是一个安全问题。只有当他们的输入被评估用于其他用户时才存在问题。
评估用户输入与查看源代码、查看HTTP头、使用Firebug检查JavaScript对象等操作没有本质差别。他们已经可以访问所有内容。
话虽如此,如果您确实需要保护他们的代码,请查看Google Cajahttp://code.google.com/p/google-caja/

这是一个很好的观点。如果你有 Firebug,你可以评估任何你想要的东西。 - Nathan Whitehead
1
我在这里表示同意。用户能够操纵自己的浏览器并不构成安全问题。已经有很多方法可以实现这一点(如Firebug、GreaseMonkey、复制源代码并进行编辑等)。只有当其他用户能够在属于不同用户的环境中执行未受信任的代码而不知道它可能会做什么时,才会成为安全问题。 - jfriend00

2

这是一个有点“陷阱”的问题。在您的网站上,没有一种安全的方式可以使用eval()来执行用户代码。


是的,但请考虑 jsfiddle - 如何才能“在可接受的范围内”完成呢? - user166390

2
不清楚eval()是在客户端还是服务器端发生的。对于客户端:
- 我认为在配置良好的iframe (https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/) 中可以安全地进行eval。 - 这应该是100%安全的,但需要一些库并且有一些限制(不支持ES6):https://github.com/NeilFraser/JS-Interpreter。 - 有一些更轻量级的替代方案,但不是100%安全,例如https://github.com/commenthol/safer-eval。 - 或者,我认为可以手动实现类似的功能,在with语句中包装代码,覆盖this、全局变量和参数。虽然永远不会100%安全,但在你的情况下可能可行。

1

这是不可能的。浏览器没有为网页提供任何API来限制在给定上下文中可以执行什么样的代码。

然而,这可能并不重要。如果您的网站根本不使用任何cookie,那么执行任意Javascript可能不是一个问题。毕竟,如果没有认证的概念,那么伪造请求就没有问题了。此外,如果您可以确认用户“想要”执行所发送的脚本,那么您也应该受到保护,例如,如果您只运行键入到页面上的脚本,而不是通过GET或POST数据提交的脚本,或者如果您包括某种唯一标记与这些请求一起以确认请求源自您的网站。

尽管如此,对于“核心”问题的答案基本上是它是不可能的,用户输入永远不能被信任。抱歉 :/


有一个认证的概念,有Google用户帐户。我不知道它们在GAE中是如何实现的,但我猜测cookie存储了一个会话共享密钥或类似的东西。您能否更详细地解释一下如何区分键入的脚本和GET POST请求?我该如何区分? - Nathan Whitehead
@Nathan: 如果用户在页面上输入脚本,则可以放心运行,因为我们知道用户打算提交它。但是,如果代码来自GET或POST数据,则恶意用户可能会欺骗另一个用户发送该请求并运行该脚本。(对于GET,使用链接很容易。对于POST,自动提交表单也不是那么困难。)因此,除非您可以确定请求来自您的站点,否则不应允许这些请求,这意味着您网站的表单需要包含恶意用户无法获取的数据。了解跨站点请求伪造,并为用户提供秘密令牌。 - Matchu
我不认为这个说法是100%正确的,你可以在一个带有sandbox="allow-scripts"属性的iframe中使用eval,详见此处:(https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/) - 但我同意“永远不要相信用户输入”的观点。 - cancerbero

0
你最大的问题始终是防止用户提供的代码出现无限循环。你可以通过在正确的上下文中运行eval来隐藏“私有”引用,例如:
let userInput = getUserInput();
setTimeout(() => {
  let window = null;
  let global = null;
  let this = null;
  // ... set any additional references to `null`
  
  eval(userInput);
}, 0);

你可以将上述代码放在 try/catch 中,以防止语法和逻辑错误崩溃控制范围外的 eval,但你(很可能)永远无法检测到传入的用户输入是否定义了一个无限循环,这将占用javascript的单个线程,使其运行时上下文完全停滞。解决这类问题的唯一方法是定义自己的javascript解释器,使用它来处理用户的输入,并提供机制来限制你的javascript解释器愿意采取的步骤数。那将是很麻烦的!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接