- Evil tries to make an HTTP request to bank.com/transfer.php
- The browser happily performs the request, authenticated with your cookies, and the bank, having a CSRF vulnerability, happily sends your money to the attacker.
- Since 'evil.com' and 'bank.com' are different origins, Browser refuses to provide the response to evil.com, but the attacker doesn't care, he got the money.
CORS allows you to relax these restrictions, not tighten them.
Now, bank doesn't like these attacks. So they make the legitimate application send an additional custom header, "X-Totally-Secure: true". Despite being a really bad idea, if (big if) the browser follows the standards, this prevents the attack:
- evil.com tries to make the HTTP request as before
- Browser lets it through, as before
- Bank rejects the request because it's missing the magic header
So the attacker adds the header to the request:
- evil.com tries to make a non-standard HTTP request to bank.com/transfer.php, with the header attached
- BECAUSE IT'S A NON-STANDARD REQUEST, browser asks bank.com (as you described, OPTIONS)
- Bank.com replies "wtf do you want I don't know what OPTIONS is"
- Browser refuses to make the request
Unfortunately, the bank forgot that they have a marketing department, that runs ournewbankapp.com, and shows your current balance in the fake screenshot of the app to show how awesome it is. And your bosses' bosses' boss has yelled at the IT department that rolled out the security measure to make it work again. They make ournewbankapp.com send the magic header (including access-control-allow-credentials), but now the OPTIONS request fails. So they teach the web server to respond with "everyone is allowed" (with "access-control-allow-origin: *" as you described) because they're lazy and dumb.
But because browser vendors know that developers are lazy and dumb, the browser completely ignores this: If access-control-allow-credentials is set, the allowed origin must be listed explicitly. The developers give in, and explicitly add ournewbankapp.com to the header, and now it works, but the attack doesn't work.
I was the original lead designer on Ultima Online and the key designer of this system. A few notes, because this article has MANY inaccuracies.
1. Ultima Online wasn't even in development for three years total. An early prototype was February to September 1995, done by Rick Delashmit. Starting late August and early September 1995, the core team showed up. We showed the game at E3 in spring of 1996 in alpha form. We showed it in beta form at E3 in the spring of 1997. And we launched on Sept 26th, 1997. The ecology was in the alpha test, and was removed during the beta after being rewritten by an engineer who didn't really like or understand it.
2. "Not many players know about" this is false. The strategy guide published simultaneously with the game even lists all the resource values for how much meat, hide, feathers, whatever, each creature represented. All of those statistics remained in the game and still are there to this day twenty years later. What was disabled was the AI. The values are still used by crafting, harvesting, and lots of other systems in the game.
3. Said AI is exhaustively documented on my website here: https://www.raphkoster.com/2006/06/03/uos-resource-system/ (first article, there is a sidebar with links to the follow-ons) and also collected in my recent book POSTMORTEMS, which has a huge pile of historical info on Ultima Online as well as Star Wars Galaxies and other games I've worked on.
4. The reason the AI was disabled had nothing to do with why the ecology collapsed. The AI was disabled because of the cost of doing radial searches followed by pathfinding. "The way players fit into this equation was that the they would embark on quests to kill the carnivorous animals and the pelts that they gained from those quests would be worth more than those gained from the herbivores" doesn't make any sense. :)
5. The ecology collapsed for a different reason: we had a closed economy loop at first, where everything was spawned from a fixed resource pool. It fell victim to player hoarding: when players killed sheep, they then made zillions of shirts from the wool to grind crafting advancement. Then they hoarded them or sold them very slowly. The result was the central bank ran out of wool, and then couldn't spawn more sheep. This is documented in one of the earliest detailed analyses of MMO economies, Zach Simpson's "In-Game Economics of Ultima Online," a very influential piece which led to the widespread use of the term "faucet-drain economy" in online game design. See https://web.archive.org/web/20020730225856/https://www.mine-...
6. "This problem is also what spawned multiple instances of servers (or as they called them, “shards”) that people know and recognize from most MMOs today." This is not why we ended up with shards, either. UO was originally designed for a concurrency around 250, much like Meridian 59 and other MUD heirs of the day. Its original lifetime forecast was only 30000 or so units, but we knew from early on we'd need multiple servers, even at that population count. Meridian 59 launched with a whole bunch of them, for example. After we got 50000 tester sign-ups, we were asked to hugely increase server size, which led to Rick inventing a server boundary mirroring technology we called "multiserver," which allowed the map load to be shared across clusters of machines. The entire game was then rearchitected for that in between 1996 and 1997. The term shards came from the fiction of earlier Ultimas, see https://www.raphkoster.com/2009/01/08/database-sharding-came...
7. "At the time when 3D graphics cards were new" -- they were nonexistent when we started.
8. The source for the article is a more accurate video at Ars Technica, which has war stories from Richard Garriott. But Richard's memory is, alas, faulty on some of these finer details.
9. There are some great Quora answers on the tech stack for the game and whatnot which have been on HN before, but if you're interested, you may want to check them out.
I will say, it's awesome and flattering and super cool that so many people still harbor so much affection and so many memories from this game. I was around 25 when I was leading design on it, and the early days when we were doing the impossible are still some of the fondest memories of my career. For lots more war stories, do check out either the book, my site, or this postmortem presented at GDC for the game's 20th anniversary: https://www.youtube.com/watch?v=lnnsDi7Sxq0
- You visit evil.com
- Evil tries to make an HTTP request to bank.com/transfer.php
- The browser happily performs the request, authenticated with your cookies, and the bank, having a CSRF vulnerability, happily sends your money to the attacker.
- Since 'evil.com' and 'bank.com' are different origins, Browser refuses to provide the response to evil.com, but the attacker doesn't care, he got the money.
CORS allows you to relax these restrictions, not tighten them.
Now, bank doesn't like these attacks. So they make the legitimate application send an additional custom header, "X-Totally-Secure: true". Despite being a really bad idea, if (big if) the browser follows the standards, this prevents the attack:
- evil.com tries to make the HTTP request as before
- Browser lets it through, as before
- Bank rejects the request because it's missing the magic header
So the attacker adds the header to the request:
- evil.com tries to make a non-standard HTTP request to bank.com/transfer.php, with the header attached
- BECAUSE IT'S A NON-STANDARD REQUEST, browser asks bank.com (as you described, OPTIONS)
- Bank.com replies "wtf do you want I don't know what OPTIONS is"
- Browser refuses to make the request
Unfortunately, the bank forgot that they have a marketing department, that runs ournewbankapp.com, and shows your current balance in the fake screenshot of the app to show how awesome it is. And your bosses' bosses' boss has yelled at the IT department that rolled out the security measure to make it work again. They make ournewbankapp.com send the magic header (including access-control-allow-credentials), but now the OPTIONS request fails. So they teach the web server to respond with "everyone is allowed" (with "access-control-allow-origin: *" as you described) because they're lazy and dumb.
But because browser vendors know that developers are lazy and dumb, the browser completely ignores this: If access-control-allow-credentials is set, the allowed origin must be listed explicitly. The developers give in, and explicitly add ournewbankapp.com to the header, and now it works, but the attack doesn't work.
(part 2 follows)