[HN Gopher] Why do we have both CSRF protection and CORS?
___________________________________________________________________
Why do we have both CSRF protection and CORS?
Author : smagin
Score : 71 points
Date : 2025-03-02 15:32 UTC (7 hours ago)
(HTM) web link (smagin.fyi)
(TXT) w3m dump (smagin.fyi)
| IgorPartola wrote:
| What I never quite grasped despite working with HTTP for decades
| now: how come before CORS was a thing that you could send a
| request to any arbitrary endpoint that isn't the page origin just
| not be able to see the response. Was this an accidental thing
| that made it into the spec? Was this done on purpose in
| anticipation of XSS-I-mean-mashups-I-mean-web-apps? Was it just
| what the dominant browser did and others just followed suit?
| PantaloonFlames wrote:
| I don't have a Time Machine or a 1998-era browser but I'm not
| sure what you described was the case. I think in the before
| times, a browser could send a request to any arbitrary endpoint
| that was not the page origin, and it could also see the
| response. I might be wrong.
|
| But anyway, ancient history.
| fweimer wrote:
| Wikipedia references this part of the Netscape Navigator
| documentation: https://web.archive.org/web/20020808153106/htt
| p://wp.netscap...
|
| This suggests that the same-origin policy was introduced
| rather quickly after the introduction of Javascript, as a
| security fix.
| LegionMammal978 wrote:
| You still can make many kinds of requests [0] to an arbitrary
| endpoint that isn't the page origin, without being able to see
| the response. (Basically, anything that a <link> or a form
| submission could do.) And you can't include any cookies or
| other credentials in the request unless they have SameSite=None
| (except on ancient browsers), and if you do, then you still
| can't see the response unless the endpoint opts in.
|
| Really, there's exactly one thing that the mandatory CORS
| headers protect against: endpoints that authorize the request
| based on the requester's IP address and nothing else. (The
| biggest case of this would be local addresses in the
| requester's network, but they've been planning on adding even
| more mandatory headers for that [1].) They don't protect
| against data exfiltration, third-party cookie exfiltration
| (that's what the SameSite directive is for), or any other such
| attack vector.
|
| [0] https://developer.mozilla.org/en-
| US/docs/Web/HTTP/CORS#simpl...
|
| [1] https://wicg.github.io/private-network-access/
| IgorPartola wrote:
| Yes I know this is still the case today. My question is: how
| did this come about? It seems to me that in the olden days
| the idea of cross origin requests wasn't really needed as
| you'd be lucky to have your own domain name, let alone make
| requests to separate services in an era of static HTML pages
| with no CSS or JavaScript. What exactly was this feature for?
| Or was it not a feature and just an oversight of the security
| model that got codified into the spec?
| LegionMammal978 wrote:
| Hotlinking <img>s from other domains has been a thing
| forever, as far as I'm aware, and that's the archetypical
| example of a cross-origin request. <iframe>s (or rather,
| <frame>s) are another old example. And it's not like those
| would've been considered a security issue, since at worst
| it would eat up the other domain's bandwidth. The current
| status quo is a restriction on what scripts are allowed to
| do, compared to those elements.
| IgorPartola wrote:
| By definition those do allow you to see the response so
| that's not really what is being discussed.
| Muromec wrote:
| Not really. You as an application generally can't read
| across origin boundaries, but you can ask browser to show
| it to the user.
| hinkley wrote:
| The field of web applications didn't really blow open until
| we were into the DotCom era. By then Berners-Lee's concept
| for the web was already almost ten years old. I think it's
| hard for people to conceive today what it was like to have
| to own a bookshelf of books in order to be a productive
| programmer on Windows, for instance. Programming paradigms
| were measured literally in shelf-feet.
|
| Practically part of the reason Java took off was it was
| birthed onto the Internet and came with Javadoc. And even
| then the spec for Java Server Pages was so "late" to the
| party that I had already worked on my first web framework
| when the draft came out, for a company that was already on
| its second templating engine. Which put me in rarified air
| that I did not appreciate at the time.
|
| It was the Wild West and not in the gunslinger sense, but
| in the "one pair of wire cutters could isolate an entire
| community" sense.
| Muromec wrote:
| That makes perfect sense in the early model of internet where
| everything was just links and documents. You can make an HTML
| form with action attribute pointing to a different domain.
| That's a feature, not a bug and isn't a security vulnerability
| in itself. Common use for this is to make "search this site in
| google" widgets.
|
| Then you can make the form make post requests by changing a
| method. Nothing wrong with this either -- the browser will
| navigate there and serve the page to user, not to the _origin
| server_.
|
| What makes it problematic is the combination of cookies from
| the destination domain and programmatic input or hidden field
| from the origin domain. But the only problem it can cause is
| the side-effects that POST request causes on the back end, as
| it again doesn't let the origin page to read the result (i.e.
| content doesn't cross the domain boundary).
|
| Now in the world on JS applications that make requests on
| behalf of the user without any input and backend servers acting
| on POST requests as user input, the previously ignored side-
| effects are the main use and are a much bigger problem.
| smagin wrote:
| "search this site in google" shouldn't even be a POST
| request, but yeah, when we'll have better defaults for
| cookies it should work nicer. And if you are a web developer,
| you should check your session cookie attributes and
| explicitly set them to SameSite=Lax HttpOnly unless your
| frameworks does that already and unless you know what you're
| doing
| fweimer wrote:
| I think it once was a common design pattern to have static HTML
| with a form that was submitted to a different server on a
| different domain, or at least a different protocol. For
| example, login forms served over HTTP were common, but the
| actual POST request was sent over HTTPS (which at least hid the
| username/password from passive observers). When Javascript
| added the capability to perform client-side form validation, it
| inherited this cross-domain POST capability.
|
| I don't know why <script> has the ability to perform cross-
| domain reads (the exception to the not-able-see-the-response
| rule). I doubt anyone had CDNs with popular Javascript on their
| minds when this was set in stone.
| Muromec wrote:
| >I don't know why <script> has the ability to perform cross-
| domain reads
|
| That's because all scripts loaded on the page are operating
| in the same global namespace of the same javascript vm, which
| has origin of the page. Since there are no contexts
| granularity below the page level in VM, they have to either
| share it or not work.
|
| You can't read it however, you can ask browser to execute,
| the same way you can ask it to show an image. It's just
| execution can have result in a read as side-effect by calling
| a callback or setting well-know global variable
| fweimer wrote:
| What I meant is that from a 1996 perspective, I don't see a
| good reason _not_ to block cross-domain <script> loads.
| The risks must have already been obvious at the time
| (applications serving dynamically generated scripts that
| can execute out of origin and reveal otherwise inaccessible
| information). And the nascent web ad business had not yet
| adopted cross-domain script injection as the delivery
| method.
| Muromec wrote:
| There is no reason for browser to block them if the page
| is static and written by hand. If I added this script to
| my page, then I want to do the funny thing and if the
| script misbehaves, I remove it.
| fweimer wrote:
| Are you talking about the risk from inclusion to the
| including page? The concern about cross-origin requests
| was in the other direction (to the remote resource, not
| the including page). That concern applies to <script>
| inclusion as well, in addition to the possibility of
| running unwanted or incompatible script code.
| Muromec wrote:
| Yes, I was talking about the risk from the perspective of
| the including page. From the perspective of the risk to
| remote it makes even less sense from the 90ies point of
| view. Data isn't supposed to be in javascript anyway, it
| should be in XML. It's again on you (the remote) if you
| expose your secrets in the javascript that is dynamically
| generated per user.
|
| With a hindsight from _this year_ -- of course you have a
| point.
| fweimer wrote:
| Ahh, the data-in-XML argument is indeed very convincing
| from a historic perspective.
| edoceo wrote:
| The point being, presumably, the page author explicitly
| chose to include a cross-domain script
| Muromec wrote:
| The script author however didn't. CORS is more about the
| remote giving consent to exfiltrate the data than it is
| about preventing injecting the data into it. You can
| always reject the data coming in
| swatcoder wrote:
| The threat model of one site leveraging the user's
| browser to covertly and maliciously engage with a third-
| party site was something that emerged and matured
| gradually, as was the idea that a browser was somehow
| duty-bound to do something about it.
|
| Browsers were just software that rendered documents and
| ran their scripts, and it was taken for granted that
| anything they did was something the user wanted or would
| at least hold personal responsibility for. Outside of
| corporate IT environments, users didn't expect browsers
| to be nannying them with limitations inserted by anxious
| vendors and were more interested in seeing new
| capabilities become available than they were in seeing
| capabilities narrowed in the name of "safety" or
| "security".
|
| In that light, being able to acccess resources from other
| domains adds many exciting capabilities to a web session
| and opens up all kinds of innovate types of documents and
| web applications.
|
| It was a long and very gradual shift from that world to
| the one we're in now, where there's basically a cartel of
| three browser engines that decide what people can and
| can't do. That change is mostly for the best on net, when
| it comes to enabling a web that can offer better
| assurances around access to high-value personal and
| commercial data, but it took a while for a consensus to
| form that this was better than just having a more
| liberated and capable tool on one's computer.
| PantaloonFlames wrote:
| To me, this seems a better treatment of the topic:
|
| https://maddevs.io/blog/web-security-an-overview-of-sop-cors...
| Scaevolus wrote:
| > JS-initiated requests are not allowed cross-site by default
| anyway
|
| Incorrect. You can use fetch() to initiate cross-site requests as
| long as you only use the allowed headers.
|
| https://developer.mozilla.org/en-US/docs/Glossary/CORS-safel...
| duskwuff wrote:
| And JS can also indirectly initiate requests for resource or
| page fetches, e.g. by creating image tags or popup windows. It
| can't see the results directly, but it can make some
| inferences.
| 1oooqooq wrote:
| there are so, so, so many ways to read this data back it's
| not even fun.
| Muromec wrote:
| There are ways, but they generally need a cooperation of
| both sides of the inter-domain boundary. What you generally
| can't do is make arbitrary reads from the context of other
| domain (e.g. call GET on their api and read a result) into
| your domain without them explicitly allowing it.
| duskwuff wrote:
| Right. What you can sometimes do is observe the _effects_
| of the content being loaded, e.g. see the dimensions of
| an image element change when its content is loaded.
| RandomDistort wrote:
| Is there some document somewhere that lists all the
| potential ways of doing stuff like this?
| Herrera wrote:
| Yeah, https://xsleaks.dev tracks most of the known ways
| to leak cross-origin data.
| smagin wrote:
| oh hell yes. And oh yes iframes and postmessages, of
| course people would setup them incorrectly and even if
| they do some (probably not that important but still) data
| will leak if you're creative enough. Thanks for the link!
| smagin wrote:
| you're right, you can initiate cross-site requests that _could
| be_ form submissions. It was even in the post but I thought I'd
| omit that bit for clarity. I should have decided otherwise.
| TheRealPomax wrote:
| And why do browsers not let users go "I don't care about what
| this server's headers say, you will do as I say because I clicked
| the little checkbox that says I know better than you".
|
| (On which note, no CSRF/CORS post is complete without talking
| about CSP, too)
| siva7 wrote:
| Almost as pointless as "Yes, accept all cookies" for our
| european friends.
| LegionMammal978 wrote:
| I'd think SameSite/Secure directives on cookies are genuinely
| important to avoid any malicious website from stealing all your
| credentials. Otherwise, I'd imagine it's the usual "Because
| those dastardly corporations will tell people to disable it,
| just because they can't get it to work!!!"
| syntheticcdo wrote:
| You can! Go ahead and launch chrome with the --disable-web-
| security argument.
| tedunangst wrote:
| Because then you will get users whining I didn't know what the
| checkbox did and you shouldn't have let me check it.
| yoavm wrote:
| I wish the browser would just say "This website is trying to
| fetch data from example.com, do you agree?"
|
| The whole CORS thing is so off and it destroyed to ability to
| build so many things on the internet. I often think it protects
| websites more than it protects users. We could have at least
| allowed making cookie-less requests.
| mjevans wrote:
| A post I was replying to got deleted, but I'd still like to gripe
| about the positives and negatives of the current 'web browser +
| security things' model.
|
| Better in the sense of not being locked into an outdated and
| possibly protocol insecure crypto-system model.
|
| Worse in the sense: Random code from the Internet shouldn't have
| any chance to touch user credentials. At most it should be able
| to introspect the status of authentication, list of privileges,
| and what the end user has told their browser to do with that and
| the webpage.
|
| If it weren't for E.G. IE6 and major companies allergic to things
| not invented there we'd have stronger security foundations but
| easier end user interfaces to manage them. IRL metaphors such as
| a key ring and use of a key (or figurative representations like a
| cash / credit card in a digital wallet) could be part of the user
| interface and provide context for _when_ to offer such tokens.
| hu3 wrote:
| > Random code from the Internet shouldn't have any chance to
| touch user credentials
|
| One thing that helps with this are HttpOnly cookies.
|
| "A cookie with the HttpOnly attribute can't be accessed by
| JavaScript, for example using Document.cookie; it can only be
| accessed when it reaches the server. Cookies that persist user
| sessions for example should have the HttpOnly attribute set --
| it would be really insecure to make them available to
| JavaScript. This precaution helps mitigate cross-site scripting
| (XSS) attacks."
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
| matsemann wrote:
| One thing to note is that if you think you're safe from having to
| use csrf due to only serving endpoints you yourself consume by
| posting json, some libraries (like django rest framework) can
| also opaquely handle html forms if the content type header is
| set, accidentally opening you up for someone having a form on
| their site posting on users' behalf to yours.
| webdever wrote:
| subdomains are not always the same origin. See "public suffix
| list". For an example think of abc.github.io vs def.github.io
|
| I didn't get the part at the end about trusting browsers. As a
| website owner you can't rely on browers as hackers don't have to
| use a browser to send requests and read responses
| hinkley wrote:
| Or abc.wordpress.com
|
| Also a lot of university departments and divisions in large
| enough corporations need to be treated like separate entities.
| Muromec wrote:
| You do rely on browsers to isolate contexts. The problem with
| CSRF is that data leaks from one privileged context to another
| (think of reading from kernel memory of another vm on the same
| host on AWS). If you don't have the browser, you don't have the
| user session to abuse in the first place.
|
| The whole thing boils down to this:
|
| - browser has two tabs -- one with authenticated session to web
| banking, another with your bad app
|
| - you as a bad app can ask browser to make an http request to
| the bank API and the browser will not just happily do it, but
| also attach the cookie from the authenticated session the user
| has opened in the other tab. That's CSRF and it's not even a
| bug
|
| - you however can't as a bad app read the response unless the
| bank API tells browser you are allowed to, which is what CORS
| is for. maybe you have an integration with them or something
|
| Browser is holding both contexts and is there to enforce what
| data can cross the domain boundary. No browser, no problem
| beala wrote:
| If I may attempt to summarize:
|
| CORS is a mechanism for servers to explicitly tell browsers which
| cross-origin requests can read responses. By default, browsers
| block cross-origin scripts from reading responses. Unless
| explicitly permitted, the response cannot be read by the
| requesting domain.
|
| For example, a script on evil.com might send a request to
| bank.com/transactions to try and read the victim's transaction
| history. The browser allows the request to reach bank.com, but
| blocks evil.com from reading the response.
|
| CSRF protection prevents malicious cross-origin requests from
| performing unauthorized actions on behalf of an authenticated
| user. If a script on evil.com sends a request to perform actions
| on bank.com (e.g., transferring money by requesting
| bank.com/transfer?from=victim&to=hacker), the server-side CSRF
| protection at bank.com rejects it (likely because the request
| because it doesn't contains a secret CSRF token).
|
| In other words, CSRF protection is about write protection,
| preventing unauthorized cross-origin actions, while CORS is about
| read protection, controlling who can read cross-origin responses.
___________________________________________________________________
(page generated 2025-03-02 23:00 UTC)