Lesson 1 · Reading · 25 min
Web Fundamentals
Everything you will ever find on a web target, from a cross-site scripting bug to a full account takeover, is a request you sent and a response you read. This lesson is about those two things: what they are, what carries them, and what the browser does with them when you are not looking. Read it once, then do the lab, which makes you send each part of a request by hand.
The network, briefly
A web application is a program running on a computer somewhere else. Your browser talks to it over a network, and the network only knows how to do a few things.
IP addresses. Every machine on the internet that can be reached has an address, a number like 93.184.216.34. Packets are small envelopes with a "to" and a "from" address, and routers pass them along until they arrive. That is the whole postal system.
Ports. One machine runs many programs. A port is a number that says which program a packet is for. Web servers listen on port 80 for plain HTTP and 443 for HTTPS. When you type https://example.com, the browser silently adds :443.
DNS. People remember names, not numbers. The Domain Name System turns example.com into an IP address. Before your browser can send anything it asks a DNS resolver "what is the address of example.com?" and gets a number back. This lookup is a separate request, on a separate protocol, and it happens before HTTP is involved at all.
TCP. Packets can arrive out of order or not at all. TCP is the layer that turns unreliable packets into a reliable, ordered stream of bytes between two ports, so the browser and the server can talk as if they had a cable between them. A TCP connection starts with a handshake: the client says hello, the server says hello back, the client acknowledges. Three packets, then the stream is open.
TLS. HTTPS is HTTP inside a TLS tunnel. TLS does two jobs: it encrypts the stream so nobody in between can read it, and it lets the browser check that the server holds a certificate for the name you typed. This is why a proxy like Burp has to install its own certificate on your machine: to read HTTPS traffic it has to sit in the middle and be trusted by your browser.
You do not need to understand any of these in depth to test a web app. You need to know they exist, in that order, because when something "just does not connect", one of them is why.
What a browser actually does
Type https://example.com/products?sort=price and press Enter. In under a second the browser has done all of this:
- Split the URL into parts: scheme
https, hostexample.com, port443(implied), path/products, query?sort=price. - Asked DNS for the address of
example.com. - Opened a TCP connection to that address on port 443.
- Negotiated TLS over that connection and checked the certificate.
- Written an HTTP request into the stream.
- Read the HTTP response back.
- Parsed the HTML in the response, then sent more requests for every stylesheet, script and image the page referenced, each one a request-response pair of its own.
- Run the JavaScript, which may send yet more requests without any page loading at all.
Steps 5 and 6 are what this whole field is about. Everything before them is plumbing. Everything after them is the browser doing what the response told it to.
The thing to hold onto: the browser is not the application. It is a client that renders whatever the server sends and sends whatever you, or the page's JavaScript, tell it to. Any request the browser can make, you can make without the browser, and you can change every byte of it.
The request
An HTTP request is plain text. Here is the one from the example above, more or less exactly as it crosses the wire:
GET /products?sort=price HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0 (X11; Linux x86_64) Firefox/128.0 Accept: text/html,application/xhtml+xml Accept-Language: en-GB,en;q=0.5 Cookie: session=8f3a9c1e77b2 Connection: keep-alive
Four parts, in order.
The request line. GET /products?sort=price HTTP/1.1. A method, a path (with its query string), and the protocol version.
The method is what you are asking the server to do with the path. GET asks for a representation of it. POST sends data to it. PUT replaces it, PATCH changes part of it, DELETE removes it, OPTIONS asks what is allowed, HEAD is GET without the body. These are conventions, not laws: the server decides what each one means for each path, and one of the first things a tester does is try a method the developer did not expect.
The headers. One per line, Name: value, until a blank line. Headers are metadata about the request: who it is for (Host), what the client is (User-Agent), what it will accept back (Accept), and, most importantly for us, who the client claims to be (Cookie, Authorization). Header names are case-insensitive. You can add any header you like; the server ignores the ones it does not know.
The blank line. It separates headers from body. It is not decoration; the server reads until it sees it.
The body. GET requests usually have none. A POST from an HTML form looks like this:
POST /login HTTP/1.1 Host: example.com Content-Type: application/x-www-form-urlencoded Content-Length: 35 username=alice&password=hunter2xyz
and one from a JavaScript app usually sends JSON instead:
POST /api/login HTTP/1.1 Host: example.com Content-Type: application/json Content-Length: 46 {"username":"alice","password":"hunter2xyz"}
Content-Type tells the server how to read the body; Content-Length tells it how many bytes to read. Both are just headers, and both can be wrong on purpose.
Every single piece of this, the method, the path, the query, every header, every byte of the body, is chosen by the client. The server has to decide what to trust. It should trust none of it. Most bugs are a server that did.
The response
The server answers in the same shape, reversed:
HTTP/1.1 200 OK Date: Wed, 10 Sep 2026 09:14:02 GMT Content-Type: text/html; charset=utf-8 Content-Length: 5123 Set-Cookie: session=8f3a9c1e77b2; Path=/; HttpOnly; Secure; SameSite=Lax Cache-Control: no-store X-Frame-Options: DENY <!doctype html> <html> ...
The status line. Version, a three-digit status code, and a short reason phrase. The first digit is the story:
| Range | Meaning | Ones you will see constantly |
|---|---|---|
2xx |
It worked | 200 OK, 201 Created, 204 No Content |
3xx |
Go somewhere else | 301/302 redirects, 304 Not Modified |
4xx |
You did something wrong | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 405 Method Not Allowed, 429 Too Many Requests |
5xx |
The server broke | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |
The difference between 401 and 403 matters: 401 means "I do not know who you are", 403 means "I know who you are and the answer is no". A 403 on a resource you were not told about is a resource that exists. A 500 where you expected a 400 is a server that did not expect your input, and that is where you start looking.
The headers. What the body is (Content-Type), how long it is, whether to cache it, and instructions for the browser: Set-Cookie asks the browser to store a cookie and send it back on later requests; Location on a 3xx says where to go; the X-Frame-Options, Content-Security-Policy and Strict-Transport-Security family are the server telling the browser to protect the user from certain classes of attack. Headers are the part of a response almost nobody reads, and they routinely say more than the page does.
The body. HTML for a page, JSON for an API, an image, a PDF, anything. The browser renders it; you read it. A response body has already been decided by the server, so bugs are rarely in it, but it is where you see the effect of what you sent: the error message that echoes your input, the JSON field that was not supposed to be there, the other user's data.
Cookies, and why the server remembers you
HTTP itself has no memory. Every request arrives on its own; the server has no idea whether this GET /account is from the same person as the POST /login a second ago. That would make logging in pointless, so servers hand out a session identifier, and the browser sends it back with every later request.
Look at the two headers again:
Set-Cookie: session=8f3a9c1e77b2; Path=/; HttpOnly; Secure; SameSite=Lax
is the server saying "store this, and send it back to me". From then on the browser adds
Cookie: session=8f3a9c1e77b2
to every request to that host, automatically, without asking you. That header is your login. Whoever sends that value is you, as far as the server can tell. The attributes after it (HttpOnly, Secure, SameSite) are the server asking the browser to be careful with it: not readable from JavaScript, only over HTTPS, not sent on cross-site requests. Each of those is a whole class of bug when it is missing, and later lessons cover them.
The idea to take from this lesson is smaller: a cookie is not special. It is a request header. Your browser sets it for you; a tool like curl does not, and you have to set it yourself. Once you have done that once, by hand, you will never again think of "being logged in" as something the browser does to you.
Seeing it for yourself
There are three ways to look at the traffic, and you will use all of them.
The browser's developer tools. Press F12, open the Network tab, reload. Every row is one request-response pair. Click one: you get the request headers, the response headers, the body, the timing. This is the fastest way to answer "what did the page just send?" and it costs nothing. Its limit is that you can only see, not change.
curl. A command-line client that speaks HTTP and does exactly what you tell it, nothing more.
curl -i https://example.com/ # -i prints the response headers too curl -i -X POST https://example.com/step2 # -X sets the method curl -i -H "X-Custom: 1" https://example.com/ # -H adds a request header curl -i --cookie "session=abc" https://example.com/account curl -i -d "username=alice&password=x" https://example.com/login # -d sends a form body (and implies POST)
If a request can be made, curl can make it, and every part of it is a flag you typed. That is why testers reach for it: there is no browser between you and the bytes.
An intercepting proxy (Burp Suite, Caido, mitmproxy). Your browser sends everything to the proxy; the proxy shows you each request, lets you edit it, then forwards it. You get the convenience of the browser and the control of curl. This is the tool the rest of the Academy assumes you have, and the next lesson is about setting it up. For today, DevTools and curl are enough.
What to take away
- The web is a client sending a request and a server sending a response, over TCP, usually inside TLS, to an address DNS gave you.
- A request is a method, a path, headers, and maybe a body. Every byte of it is the client's to choose.
- A response is a status, headers, and a body. The headers say more than most people ever read.
- Cookies are request headers. Being logged in means sending the right one.
- The browser is a convenience, not a boundary. Anything it does, you can do by hand, and differently.
Now do the lab. It is a tiny server that only answers people who read the whole response, change the method on purpose, and send a cookie themselves. When it hands you the flag you will have done, by hand, the thing every later lesson builds on.
This lesson has 1 lab to try what you just read.