How do I safely "eval" user code in a webpage?

Viewed 3941

I'm working on a webapp to teach programming concepts. Webpages have some text about a programming concept, then let the user type in javascript code into a text editor window to try to answer a programming problem. When the user clicks "submit", I analyse the text they've typed to see if they have solved the problem. For example, I ask them to "write a function named f that adds three to its argument".

Here's what I'm doing to analyse the user's text:

  1. Run JSLint on the text with strict settings, in particular without assuming browser or console functions.
  2. If there are any errors, show the errors and stop.
  3. eval(usertext);
  4. Loop through conditions for passing the assignment, eval(condition). An example condition is "f(1)===4". Conditions come from trusted source.
  5. Show passing/failing conditions.

My questions: is this good enough to prevent security problems? What else can I do to be paranoid? Is there a better way to do what I want?

In case it is relevant my application is on Google App Engine with Python backend, uses JQuery, has individual user accounts.

5 Answers

Not clear if the eval() occurs on client or server side. For client side:

Your biggest issue will always be preventing infinite loops for occurring in user-provided code. You may be able to hide "private" references by running eval in the right context, e.g.:

let userInput = getUserInput();
setTimeout(() => {
  let window = null;
  let global = null;
  let this = null;
  // ... set any additional references to `null`
  
  eval(userInput);
}, 0);

And you could wrap the above code in a try/catch to prevent syntax and logic errors from crashing outside of the controlled eval scope, but you will (provably) never be able to detect whether incoming user input defines an infinite loop that will tie up javascript's single thread, rendering its runtime context completely stalled. The only solution to a problem like this is to define your own javascript interpreter, use it to process the user's input, and provide a mechanism to limit the number of steps your javascript interpreter is willing to take. That would be a lot of trouble!

Related