CORS and Other Web Attacks

CORS

Cross-Origin Resource Sharing

  • The SOP is sometimes too restrictive
    • You host a public API that other sites call from their front ends
    • Your front end is at cse312.com and your API is at api.cse312.com (Different origins!)
  • Cross-Origin Resource Sharing (CORS) lets a server relax the SOP for its own responses
    • The server approves preflights, and marks which origins may read its responses
    • Uses Access-Control-Allow-* headers
  • CORS never makes anything more strict. It only allows things that the SOP would usually block

Preflight - Request

OPTIONS /api/posts/42 HTTP/1.1
Host: api.cse312.com
Origin: https://cse312.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type
  • Origin - The origin of the page that wants to send the request
  • Access-Control-Request-Method - The method of the real request
  • Access-Control-Request-Headers - The non-safelisted headers the real request will include
    • Content-Type is listed here since application/json is not one of the 3 simple types

Preflight - Response

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://cse312.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 600
  • Access-Control-Allow-Origin must match the requesting origin (or * to allow all origins)
  • The requested method and headers must be in the allowed lists
  • Access-Control-Max-Age - The browser can cache this approval for 600 seconds
  • The real response must also include Access-Control-Allow-Origin
    • Otherwise, the real request is sent, but JavaScript still can't read its response

Access-Control-Allow-Origin

  • Access-Control-Allow-Origin: https://cse312.com
    • JavaScript on https://cse312.com may read this response
    • Must be a full origin: protocol + host + port
    • Access-Control-Allow-Origin: cse312.com does not work
    • Only one origin is allowed in this header
  • Access-Control-Allow-Origin: *
    • JavaScript on any origin may read this response
    • Fine for truly public data (A public API, fonts, a CDN)
    • Do not use this as a hammer to get rid of your CORS errors!

Other Attacks

Login XSRF

  • Normal XSRF: Act as the victim on the victim's account
  • Login XSRF: Log the victim into the attacker's account
    • The attack page submits your login form with the attacker's username and password
    • The victim doesn't notice they're logged into someone else's account
    • Everything the victim does is saved to the attacker's account
      • Search history, uploaded files, a saved credit card number
    • The attacker logs in later and reads it all
  • Your login form needs XSRF protection too, even though the user has no authenticated session yet
    • Foreshadow: You'll see this again with OAuth and the state parameter

Forging JSON With a Form

<form action="https://bank.com/api/transfer" method="post" enctype="text/plain">
  <input type="hidden" name='{"to": "attacker", "amount": 10000, "x": "' value='"}'>
</form>
  • A text/plain form sends name=value with no encoding
  • The body of this request:
    • {"to": "attacker", "amount": 10000, "x": "="}
    • Valid JSON! And it's a simple request, so there's no preflight
  • If your server parses the body as JSON without checking for Content-Type: application/json
    • The attack works, even though your front end only sends JSON with fetch
  • Although this attack will not contain lax cookies, so it's not too threatening

XSS Defeats XSRF Defenses

  • Recall HTML injection (XSS): The attacker's JavaScript runs on your origin
  • JavaScript running on your origin can:
    • Read your pages, including the XSRF token
    • Send same-origin requests that include your cookies
  • Every XSRF defense assumes the attacker's code runs on a different origin
  • Always escape HTML in user-submitted content

Other Protections

Double-Submit Cookies

  • In practice, XSRF tokens are sometimes also stored in cookies
  • The server sets the XSRF token in a cookie (Not HttpOnly, so JavaScript can read it)
  • The server sends the same token in a form field or header
  • The server checks that the two values match. No need to store tokens on the server
  • The cookie might still be sent, but the attacker cannot read it so they can't add it to their forged form submission
    • The cookie is only where the token is stored, instead of in your database. The proof is the copy in the body or header

Re-Authentication

  • For the most sensitive actions, require the user's current password:
    • Changing their password or email address
    • Disabling two-factor authentication
  • The attacker doesn't know the password, so they can't forge the request
  • A confirmation step or CAPTCHA for high-value actions (eg. large transfers)
  • These hurt usability. Save them for the actions that matter most

Further Reading