8

I want to implement CORS for multiple origins and I understand I need to do so via a lambda function as I cannot do that via the MOCK method

exports.handler = async (event) => {
  const corsUrls = (process.env.CORS_URLS || '').split(',')
  const requestOrigin = (event.headers && event.headers.origin) || ''

  if (corsUrls.includes(requestOrigin)) {
    return {
      statusCode: 204,
      headers: {
        "Access-Control-Allow-Headers": 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With',
        'Access-Control-Allow-Origin': requestOrigin,
        'Access-Control-Allow-Methods': 'POST,DELETE,OPTIONS'
      }
    }
  }

  return {
    statusCode: 403,
    body: JSON.stringify({
      status: 'Invalid CORS origin'
    })
  }
}

Firstly, does the above looks ok? Then I am getting origin from headers event.headers.origin. But I find that I can just set that header manually to "bypass" cors. Is there a reliable way to detect the origin domain?

Jiew Meng
  • 84,767
  • 185
  • 495
  • 805
  • Sounds like the recommended way to do it is to have your server read the Origin header from the client, compare that to the list of domains you would like to allow, and if it matches, echo the value of the `Origin` header back to the client as the `Access-Control-Allow-Origin` header in the response. – Kousic Dec 26 '18 at 07:06
  • Just posted an answer. If you give some more context around what your goals are and some further information about what you expect `corsUrls` to equal and what you expect `requestOrigin` to be, I can further refine my answer to give more specific details to your exact situation. I am curious about what your objectives and goals are. That will really help determine a solution that fits your needs. – Charlie Fish Dec 27 '18 at 02:31

2 Answers2

3

Firstly, does the above looks ok?

Your code looks good to me at first glance, and other than your point But I find that I can just set that header manually to "bypass" cors, I don't see any major problems with it.

Then I am getting origin from headers event.headers.origin. But I find that I can just set that header manually to "bypass" cors. Is there a reliable way to detect the origin domain?

The code you are currently using is the only way I can think of how to detect the origin domain off the top of my head. Although as you said, you can just set that header manually, and there is 0 assurances that header is correct or valid. It shouldn't be used as a layer of trust for security. For browsers, they restrict how this header can be set (see Forbidden header name). But if you control the HTTP client (ex. curl, postman, etc.) you can easily can send whatever headers you want. There is nothing technology wise preventing me from sending any headers with whatever values I want to your web server.

Therefor, at the end of the day, it might not be a huge concern. If someone tampers with that header, they are opening themselves up to security risks and unexpected behavior. There are a ton of ways to bypass CORS, like this, or this, or this maybe. So at the end of the day, it's possible to bypass CORS, despite your best efforts to enforce it. Although all of those tricks are hacks, and probably won't be used by normal users. Same with changing the origin header, not likely to be done by normal users.

There are a few other tricks you could look into tho to try to enforce it a little bit more. You could look into the refer header, and see if that is the same as the origin header. Again, possible to send anything for any header, but will make it a bit harder and enforce what you want a little bit more.

If you assume that your origin header should always equal the domain of your API Gateway API then the other thing you can look into is the event.requestContext object that API Gateway gives you. That object has resourceId, stage, accountId, apiId, and a few other interesting properties attached to it. You could look into building a system that will also verify those and based on those values, determine which API in API Gateway is making the request. This might require ensuring that you have separated out each domain into a separate API gateway API.

I don't see anyway those values in the event.requestContext could be tampered with tho since AWS sets them before passing the event object off to you. They are derived from AWS and can not be easily tampered with by a user (unless the entire makeup of the request changes). For sure a lot less tamperproof than headers which are just sent with the request, and AWS passes through to you.

Of course you can combine multiple of those solutions together to create a solution that enforces your policy more. Remember, security is a spectrum, so how far down that spectrum you go is up to you.

I would also encourage you to remember that CORS is not totally meant to hide information on the internet. Those methods I shared about how you can bypass CORS with a simple backend system, or plugin, show that it's not completely foolproof and if someone really wants to fake headers they will be able to. But of course at the end of the day you can make it as hard as possible for that to be achieved. But that requires implementing and writing a lot of code and doing a lot of checks to make that happen.

You really have to ask yourself what the objectives and goals are. I think that really determines your next steps. You might determine that your current setup is good enough and no further changes are necessary. You might determine that you are trying to protect sensitive data from being sent to unauthorized origins, which in that case CORS probably isn't a solid solution (due to the ability to set that header to anything). Or you might determine that you might wanna lock things down a bit more and use a few other signals to enforce your policy a bit more.


tldr You can for sure set the Origin header to anything you want, therefor it should not be completely trusted. If you assume that your origin header should always equal the domain of your API Gateway API, you can try to use the event.requestContext object to get more information about the API in API Gateway to gain more information about the request. You could also look into the Refer header to see if you can compare that against the Origin header.


Further information:

Charlie Fish
  • 18,491
  • 19
  • 86
  • 179
-1

The only way to validate multiple origins is as you did, with your lambda read the Origin header, compare that to the list of domains you would like to allow, and if it matches, return the value of the Origin header back to the client as the Access-Control-Allow-Origin header in the response.

An information: the Origin header is one of the headers that are set automatically by the user agent.. so anyone can't altered programatically or through extensions. For more details, look at MDN.

Lucas Kauer
  • 278
  • 1
  • 3
  • 12
  • It’s important to note that at the end of the day, anyone can set the origin header to anything they want and therefor shouldn’t be completely relied on as a defense strategy. – Charlie Fish Dec 26 '18 at 19:45
  • As specified in MDN, you can't change this header. Look at here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin – Lucas Kauer Dec 26 '18 at 19:59
  • I've actually [asked this question before](https://stackoverflow.com/questions/49933544/is-http-content-length-header-safe-to-trust). The reality is anyone can send any HTTP request to a server. Just because it's a protected header for BROWSERS does not mean that you should automatically trust it. I can use a tool like Postman or build a custom HTTP library to send whatever headers or content I want in my request. There is no guarantee that the information I'm sending you is correct. You make it sound like there is some type of guarantee that the origin header is always correct. – Charlie Fish Dec 26 '18 at 20:44
  • That's not what I meant ... what I meant was that, requests made by browsers, will not allow you to change the header. But thank you for complement the response. – Lucas Kauer Dec 27 '18 at 11:17