With no script at all, what requests can a hostile page emit at a clinic's cookie-authenticated booking endpoints?
answer
- two families, one with a body
- subresource loads fire on page load
- the form's enctype attribute
- three encodings, no fourth
- no custom request header field
basics
~20 sPlain markup can emit a GET through any subresource or navigation, and a form POST whose body is one of three encodings: application/x-www-form-urlencoded, multipart/form-data or text/plain. It cannot add a request header field of its own.
solid answer
~40 sTwo families. **Subresource loads and navigations** produce a GET at any URL the markup names — an image source, a frame source, a stylesheet or script source, a link the victim follows, a document-level refresh — and the subresource ones fire on page load with no interaction. **A form element** produces a GET or a POST, and its body encoding is limited to what the `enctype` attribute allows: `application/x-www-form-urlencoded` (the default), `multipart/form-data` or `text/plain`. What markup cannot do is add a request header field of its own choosing, or use a method other than the two a form supports. Everything in both families carries the clinic's session cookie, because cookie attachment follows the destination.
code
html · 11 lines<h1>Claim your free pet dental check</h1>
<form action="https://booking.vetclinic.example/appointments/8412/reschedule"
method="post"
enctype="application/x-www-form-urlencoded">
<input type="hidden" name="slot" value="2026-10-02T09:00">
<button>Claim now</button>
</form>
<img src="https://booking.vetclinic.example/appointments/8411/cancel"
width="1" height="1" alt="">go deeper
Remember that ordinary markup — an image source, a frame, a form — is enough to make a request at another site, and that script is not required for the attack to work.
Be able to list both families and the three form body encodings, and to say which of them fire without any interaction from the victim.
Use the limits, not the capabilities, when reasoning about defence: no custom request header field, two methods, three body encodings, no readable response. Every workable defence lives in one of those gaps.
Treat the capability list as the attack surface an estate inherits from hypertext itself, and audit endpoints against it rather than against the scripted path that developers picture first.
## Why "without script" is the right frame Interviewers ask this because the natural mental model of an attack is scripted: something running on a hostile page reaches out to your site. That model is wrong in a way that matters, because the scripted path is the one the browser's cross-origin permission model governs, while the two families below predate it, are part of ordinary hypertext, and are not going anywhere. A candidate who only knows the scripted path will reach for the wrong defences. ## Family one: subresource loads and navigations Any markup that names a URL makes the browser fetch it, and every such fetch is a GET carrying whatever cookies the destination's scope matches: - an image source pointing at the clinic — fires on load, no interaction, no visible result; - a frame source — same, and it can be sized to a pixel; - a stylesheet, script or media source — same fetch, different element; - a link the victim clicks, or a document-level refresh directive — a top-level navigation, which the victim can at least see in the address bar afterwards. The first group is the dangerous one, because nothing is required of the victim beyond opening the page. This is why a state-changing endpoint reachable by GET is a much larger target than one that is not. ## Family two: the form element A form gives the attacker a body. Its method may be GET or POST, and its body encoding is whichever of three values the `enctype` attribute selects: | `enctype` value | What the body looks like | Notes | |---|---|---| | `application/x-www-form-urlencoded` | `name=value` pairs joined by `&`, percent-encoded | the default when `enctype` is absent | | `multipart/form-data` | each field in its own part, separated by a boundary | the encoding defined for file-bearing forms in RFC 7578 | | `text/plain` | `name=value`, one per line, no percent-encoding | lossy and rarely used deliberately | A form submission is a **navigation**, not a scripted call: the browser leaves the hostile page and lands on the clinic's response. Attackers hide that behind a frame, or accept the flash of a strange page, or simply time it so the victim is leaving anyway. ## What markup cannot do The limits are as important as the capabilities, because every defence that works at all works inside one of them: - **It cannot add a request header field of its own.** There is no markup for "send this request with this extra field". This is the property that a header-carried value relies on, and it is why a cross-site document cannot reproduce one. - **It cannot choose an arbitrary method.** A form offers GET and POST. - **It cannot choose an arbitrary body media type.** The three `enctype` values above are the complete list. What a server ought to require of an incoming body as a consequence is a defence question owned elsewhere; the mechanical fact is all that belongs here. - **It cannot read the response.** The clinic's answer lands in the browser, not in the attacker's hands. ## What the hostile document looks like In practice such a page is short and boring: a button labelled with whatever the lure promises, a form whose fields are named exactly as the clinic's endpoint expects, and perhaps an image element next to it aimed at a second endpoint. The attacker discovers the field names by using the clinic's own site as a customer and reading the form the clinic itself serves — nothing about this step requires privileged access. ## The consequence for endpoint design Two things follow directly and neither is a defence scheme in itself: 1. An endpoint that changes state on GET is reachable from family one, which needs no interaction at all. Moving it to an unsafe method removes that path and nothing more — family two still reaches it. 2. An endpoint that accepts any of the three form encodings is reachable from family two. Endpoints whose clients are all scripted have no reason to accept them. Beyond that, the defences are the sibling subject: a value the server can require and a cross-site document cannot produce, and a check on where the request came from.
- Why does it matter so much that markup cannot add a request header field of its own?Because it creates an asymmetry the server can rely on. A document the clinic served can be given a value and told to send it in a header field; a cross-site document has no way to produce that field at all. Several defences are built on exactly that gap, and they stop working the moment the value is carried somewhere markup can reach.
- The clinic's endpoint only accepts a JSON body. Does that make it unreachable from markup?It removes the straightforward form path, because a form's body encoding is limited to the three `enctype` values. Whether that amounts to a defence depends entirely on how strictly the endpoint enforces the media type rather than sniffing the body — which is the subject of the content-type defence material, not of attack mechanics.
- How does the attacker know which field names to put in the form?By being a customer. The clinic serves its own booking form to anyone who signs up, and the field names, the endpoint path and the accepted values are all visible in it. Nothing about the request the attacker forges is secret; only the session is, and the browser supplies that.
saying these in an interview costs you the question
- Thinks an attack page must run script to reach another site
- Believes a cross-origin permission check stops a plain form submission
- Says a form can set any media type it likes on the body
- Assumes an image element cannot cause a state change
- Thinks hidden form fields are in some way private to the page