Lesson 3 · Reading · 25 min
What the browser trusts
You now know how to read a request and send one three different ways. This lesson is about a single idea that, once it clicks, changes how you look at every web page for the rest of your life: the browser is not a boundary. Everything the browser does, it does because the server told it to, and everything the server told it can be ignored, changed, or replayed by whoever is holding the browser. Which is you.
HTML and JavaScript are just things the server sent
When you load a page, the server sends back a response body. For a web page that body is HTML: text that describes a document. Your browser reads it and draws it. If the HTML says "here is a form with a Submit button", the browser draws a form with a Submit button, not because the button is real in any deep sense, but because the text said so.
Some of that HTML pulls in JavaScript: more text, this time code, that the browser runs. The JavaScript can change the page, react to clicks, and make more requests. This is what makes a page feel like an application.
Here is the part people miss. All of it, the HTML and the JavaScript, arrived in a response. It is a copy, sent to you, running on your machine, under your control. You can read every line of the JavaScript (it is right there in DevTools → Sources). You can change any part of the page (the Elements tab lets you edit the live HTML). You can stop the JavaScript from running, or run your own instead. The page is not the application talking to you from a safe distance; it is a document the application handed you, and once it is in your hands it is yours.
The same-origin policy, and what it is actually for
If a page is that malleable, why is the web not chaos? Because of one rule the browser enforces very strictly: the same-origin policy.
An origin is the combination of three things: the scheme, the host, and the port. https://mail.example.com and https://mail.example.com/inbox are the same origin. https://mail.example.com and https://shop.example.com are different origins (different host). So are http://… and https://… of the same host (different scheme).
The same-origin policy says: script running on one origin cannot freely read data from another origin. The JavaScript on evil.com cannot read your mail.example.com inbox, even though your browser is logged in to both, because they are different origins and the browser refuses to hand one origin's responses to another origin's code.
This is a genuinely important protection, and it is worth being precise about what it protects. The same-origin policy protects users from malicious other sites. It is not the application's access control. It stops evil.com reading your mail. It does absolutely nothing to stop you reading the mail server's responses to your own requests, because those are your responses, on your origin, in your browser. When you test an app, you are not a hostile third site; you are a logged-in user poking at your own traffic, and the same-origin policy was never meant to stand in your way. Beginners sometimes think "the browser would not let me do that." For your own requests to your own session, the browser lets you do almost anything.
Where the bugs live: controls that only exist in the page
Put those two ideas together, the page is yours and the browser does not police your own requests, and you arrive at the most common mistake in web development, and one of the most common sources of findings.
A developer wants only Pro users to download a report. The easy way, the way that looks done, is to do it in the page:
- Grey out the button and add
disabledto it. - Hide the "admin" menu unless the user is an admin.
- Have the JavaScript check
if (user.plan !== 'pro') return;before it submits. - Validate the form fields in JavaScript before sending.
Every one of those runs in the browser. Every one of those is a decision made by code the server handed you, on your machine, and every one of those is yours to undo. You can remove disabled in the Elements tab. You can call the endpoint the hidden menu pointed to. You can change the field the JavaScript checked, or just send the request yourself with curl and skip the JavaScript entirely.
A control that is only enforced in the browser is not a control at all. The only place a rule about who-can-do-what actually holds is on the server, where the attacker cannot reach the code. If the server does not re-check, on every request, that this user is allowed this action, then the "check" in the page was a suggestion, and the tester's job is to notice the difference.
This is not a rare or exotic bug. Disabled buttons that hide a working endpoint, forms that trust a client-set price, "you must be an admin" enforced only by hiding a link, these are everywhere, and finding them is often just: read what the page is stopping you from doing, and then do it anyway with a tool that does not care about the page's rules.
A word on markup the server echoes
One more thing to name, because you will meet it soon. When the browser trusts markup the server sent, and that markup contains something an attacker chose, you get cross-site scripting: the attacker's HTML or JavaScript runs in the victim's page, on the victim's origin, with all the trust that origin has. If a site takes your search term and prints it back into the page without care, and your "search term" is <script>…</script>, the browser runs it, because the browser trusts the markup the server sent it. That is a whole shelf of the Academy on its own, and this lesson is not where you learn to exploit it. Name it for now: the browser trusts what the server sends, and when an attacker can influence what the server sends, that trust is the bug.
What to take away
- The HTML and JavaScript of a page are a copy the server sent you. They run on your machine, under your control, and you can read and change all of it.
- The same-origin policy stops one site's script reading another site's data. It protects users from other sites. It is not the app's access control, and it does not stand between you and your own traffic.
- Any control that lives only in the page, a disabled button, a hidden menu, a JavaScript check, is not enforced. The server is the only boundary. If the server does not re-check on every request, the rule is not real.
- When an attacker can influence markup the server sends, the browser's trust in that markup becomes a bug (XSS). More on that later.
Now do the lab
The lab is a site called Reportly. It shows you a report download that is "Pro members only": the button is disabled, and the page's JavaScript refuses to submit for a Free user. Your account is Free. The report is one request away. Find the control, and go around it, the whole point is that it was never on the server at all.
This lesson has 1 lab to try what you just read.