Cybersecurity · Web security

How a website falls: an illustrated anatomy of a web attack

Reconnaissance, XSS, SQL injection, CSRF, IDOR, files and interpreters: a visual investigation follows the full chain of a compromise and the defences that can break it.

Editorial cross-section of a web application where several attack paths meet the identity, API, database and logging controls.
In this article

A website almost never falls because of a single spectacular move. It gives way more like a building in which several doors have been left ajar: an over-talkative technology reveals its version, an old subdomain stays online, a backup archive becomes public, an access check is only performed in the interface, and then a piece of data supplied by a user reaches an interpreter that takes it for an instruction.

Each weakness can seem modest. Together, they trace a route. That is the path we are going to follow, from the first publicly visible information to the incident, and then into the first hours of the response. To make it concrete, let us imagine Maison Aster, a fictitious online shop with a catalogue, customer accounts, an admin area, a contact form and a payment service.

This investigation is not a burglary manual. It describes mechanisms, trust boundaries, observable signals and the measures capable of breaking the chain. The point is not to memorise malicious payloads, but to understand why an application sometimes accepts a piece of data as something it is not.

A website is a chain of decisions

The visible page is only the façade. Behind it sit the browser, the web server, the application, one or more APIs, the identity system, the database, file storage, third-party services, automated jobs and an administration infrastructure. A single action passes through several of these layers.

When a customer changes their delivery address, the browser sends a request, the session identifies the account, the application checks the permissions, the database stores the new value and the logs record the operation. Security depends on every decision taken along this path. If one layer assumes that the previous one has already checked the data, a gap opens between the two.

The classic scenario follows five stages: map, probe, exploit a trust error, widen access, then try to persist or make the investigation harder. These stages are not mandatory and their order can vary. They nonetheless provide a useful reading grid: the most effective defences are those that prevent a local weakness from becoming a general compromise.

Step one: reconnaissance, or the art of reading what is already public

Before touching an application, an adversary can learn a great deal without sending it a single unusual request. Domain names and subdomains, job postings, public documentation, code repositories, certificates, legal notices, professional profiles and indexed files sometimes reveal an organisation's architecture.

Maison Aster, for example, has publicised a recent migration. A job advert mentions its framework. An old repository contains the name of a staging environment. An archive created during the migration has remained accessible from the public server. None of these elements on its own amounts to an intrusion. Put together, however, they reduce uncertainty: the adversary knows which doors to look for and which hypotheses to test.

Active reconnaissance begins when the systems are queried directly: exposed services, HTTP responses, headers, error pages, discovery files, forgotten interfaces or component versions. An overly precise banner can link a service to a known vulnerability. A publicly listed directory can reveal a backup. A file meant for search engine crawlers can point to a sensitive area without protecting it.

Reconnaissance gathers scattered clues around a website until its dependencies, forgotten annexes and exposed paths come into view.

The defensive response starts with a real inventory. You need to know the domains, applications, APIs, administration services, dependencies and environments that are still reachable. A surface the organisation has forgotten no longer receives patches, but remains perfectly visible from the Internet. Deployment archives, database exports, configuration files and old versions must leave the web root. Directories must not be listed. Error messages and banners should give users what they need without publishing a full technical data sheet.

The scanner finds patterns; humans understand the business

Automated tools excel at recognising signatures: an outdated component, a common configuration, a parameter that reacts unusually, a missing header or accidentally exposed content. They can examine a large surface quickly and repeat the same checks after every deployment.

Their limits appear as soon as the vulnerability depends on the meaning of an action. A scanner can see that a route returns a document. It does not always know that a customer should never be able to read another customer's invoice. It can observe that a form accepts a value without understanding that a staff-only discount becomes stackable with a refund. Business logic errors hide less in syntax than in implicit rules.

Maison Aster thus has an order page that is technically perfectly valid. The browser hides the cancel button once the order has shipped, but the server does not apply the same rule. The interface gives the impression of a locked state; the API nonetheless keeps accepting the action. This kind of gap requires reading the entire journey and the roles: customer, operator, administrator, automated service.

The machine spots known cracks at scale while human review follows the relationships between users, objects and business rules.

Good practice therefore combines dependency analysis, automated tests, configuration review and scenario-based manual testing. It also checks the negative cases: what a logged-out user, an ordinary customer or an account belonging to another organisation must be unable to do. The expected result is not just "the feature works", but "it correctly refuses any context that does not satisfy its rules".

XSS: when the browser prints data as an instruction

Cross-site scripting, or XSS, occurs when data controlled by a third party is embedded in a page in such a way that the browser interprets it as active content. The trust boundary here is particularly delicate: the code runs in the context of the legitimate site. For the browser, it therefore shares the origin, the interface and often the permissions of the open session.

In stored XSS, the dangerous data is saved — in a comment, a bio, a product page or a ticket — and then served to every reader. In reflected XSS, it comes straight back in a response, often from a URL parameter or an error message. In DOM-based XSS, it is the code running in the browser that picks up a value and inserts it into a dangerous location, sometimes without going back through the server.

Imagine Maison Aster's review editor. The interface cleans the text before sending it, but the API also accepts direct requests and stores the raw value. When it is displayed, the page then injects it into an area interpreted as HTML. Two checks exist, but neither sits at the decisive boundary: the server trusts the browser on input, then the browser trusts the data on output.

XSS is not just about stealing a cookie. A session cookie marked HttpOnly cannot be read directly by the page's script, which reduces a significant risk. Active content can nonetheless act from within the already open session: read what the user can read on the page, trigger certain actions, alter a form or display a fake interface. The consequence therefore depends on the victim's permissions and the functions reachable from their browser.

Untrusted data can reach the browser's press through a stored, reflected or DOM-executed route; contextual encoding and sanitisation interrupt that passage.

Protection relies first on encoding suited to the output context. Data displayed in HTML text, an attribute, a URL or a JavaScript string does not follow the same rules. Modern frameworks generally escape text by default; functions that insert raw HTML must become rare, documented and reviewed. When the product must accept rich HTML, a maintained sanitisation library must apply an explicit policy on the server side and, where useful, on the client side.

A content security policy can restrict the permitted sources and make it harder for injected content to execute. It is an additional layer, not a replacement for encoding and sanitisation. The detailed OWASP recommendations on XSS prevention stress precisely these contextual controls and the choice of safe sinks.

Signals include repeated errors in rich-text fields, unusual sequences in comments, content policy violations, page changes with no corresponding action and internal calls triggered right after a piece of content is displayed. Keeping the raw value in the logs, however, requires isolating it: an admin tool must not in turn turn the malicious log entry into an active page.

SQL injection: the database confuses the question with the data

An application queries its database to find an account, record an order or generate an invoice. SQL injection becomes possible when it builds that query by directly concatenating a value supplied by the user. The database no longer receives a fixed instruction accompanied by a parameter; it receives a single string in which an untrusted part can change the structure.

In our fictitious shop, the order search expects a reference. If the application pastes that reference into the query, a value crafted to change the meaning of the condition can bypass the filter, trigger a revealing error or broaden the data returned. Some injections produce a visible result. Others are called blind: the adversary infers an answer from a difference in content, status or timing, without the database directly displaying its data.

The consequences depend on the account the application uses. An account limited to reading a few tables reduces the impact. An account able to modify the schema, access every database or call system functions turns the same error into a far more serious incident. The principle of least privilege therefore acts as a bulkhead, even when the first protection has failed.

In the dangerous path, the data merges into the structure of the query; in the safe path, the instruction stays fixed and the parameter remains in its reserved slot.

The main defence is to use parameterised prepared statements. The engine receives the structure of the operation and the values separately. A data access library or an ORM only protects you if its safe mode is actually used: a raw query built by concatenation remains dangerous, even inside a reputable tool.

Table names, column names or sort directions cannot always be parameterised like values. They must then come from a closed list controlled by the application. Format validation remains useful for data quality and for reducing the attack surface, but it does not replace the separation between command and parameter. OWASP recommends prepared statements as the central defence, complemented by minimal privileges and allow-lists when the structure must vary.

On the detection side, teams should monitor database errors, unusually slow queries, mass access, sudden changes in volume and reads that do not match the user journey. Error messages sent to the client must not reveal the query, the engine or the internal structure. Details useful for the investigation stay in protected logs.

CSRF: an authentic session, an absent intention

Cross-site request forgery, or CSRF, exploits normal browser behaviour: when it sends a request to a site, it can automatically attach that site's cookies. If the user is logged in to Maison Aster and visits a malicious page at the same time, that page can try to trigger a request to the shop. The server sees a valid session, but does not yet know whether the user intended the action.

This attack is different from XSS. The malicious content does not need to run in Maison Aster's page; it seeks to have a request sent from another origin. It becomes possible when the application relies solely on the cookie and accepts a sensitive operation without additional proof of origin or intention.

State changes must never depend on a simple GET. Sensitive forms and calls must use the framework's native CSRF protections, with an unpredictable token tied to the session or the request. The server can also check origin headers where the context allows. The cookie SameSite attribute reduces automatic cross-site sending: Strict is the most restrictive, Lax suits many journeys, and None requires a precise understanding of the need as well as secure transport.

The browser legitimately carries the session, but another origin tries to divert the mail; origin checks, the token and confirmation restore proof of intention.

For a critical change — payment method, recovery address, password, payee — re-authentication or explicit confirmation adds a further barrier. An API authenticated by a secret deliberately placed in a header, and not attached automatically by the browser, follows a different model, provided that secret is not exposed to a third-party script.

An active XSS on the same site can often read a token present in the page or trigger the action as the user. That is why the OWASP CSRF prevention cheat sheet points out that fixing XSS remains essential. Protections reinforce one another; no single ticked box makes the whole thing magic.

IDOR: knowing the identifier does not give you the right to open the file

IDOR, or insecure direct object reference, belongs to the family of access control flaws. A URL or request contains the identifier of an invoice, a profile, a ticket or an order. The application checks that the user is logged in, retrieves the requested object and returns it without confirming that it actually belongs to that user or their organisation.

The problem is not that the identifier is short or predictable. Replacing a sequential number with a long identifier makes exploration less convenient, but does not create an authorisation. If a link copied into another session still displays the document, the server is still missing the rule.

For Maison Aster, the right question is therefore not "does this order exist?" but "is this account allowed to perform this action on this order in its current state?". The check must be repeated for every read, modification, download and deletion. It must depend neither on a hidden button nor on a list already filtered in the interface.

A valid session key opens its own file; without an ownership check on the server, it also reaches the neighbouring file, while a central gate checks identity, role and relationship to the object.

Robust models start from denial. They define roles, relationships between subjects and objects, and permitted states, then apply the rule in a shared layer. Tests should cross-check at least two ordinary accounts, two organisations, a privileged account and a disabled account. Downloads deserve the same check as HTML pages: a file URL is not an authorisation.

Signs of abuse include rapid sequences of identifiers, an account viewing unrelated objects, serial downloads and repeated denials followed by success on a neighbouring route. The Broken Access Control category of the OWASP Top 10 places precisely these errors at the heart of application risk.

File uploads: the parcel must never go straight into the workshop

An avatar, an attachment or an imported document crosses a powerful boundary. The file comes from a user, but will be stored, analysed, transformed and sometimes served to others. Checking only its extension or the type declared by the browser amounts to trusting the label stuck on the parcel.

A safe policy starts with a list of the formats actually needed. It checks the size, the number of files, the detected type and, where relevant, the internal signature. It assigns a new name, strips paths supplied by the client and stores the content outside the public root, in a location that cannot execute code. Downloads then go back through a route that checks permissions and forces an appropriate response type.

Antivirus scanning or the reconstruction of certain documents can add a layer, but does not replace isolation. Image, PDF and archive engines are themselves software that must be maintained. Archives require limits on their uncompressed size, depth and number of files to prevent a small upload from exhausting storage or memory.

An upload weakness becomes critical when a file placed on the server is interpreted as a program, when an active format is served in the context of the site, or when a privileged process automatically opens an untrusted attachment. Design must therefore start from this question: what is the most dangerous thing each component could believe about this file?

Code and command injection: the second interpretation that should never have existed

Command injection occurs when an application assembles a system command with untrusted data. Code injection is a broader family: the data reaches a template engine, an evaluation function or another interpreter that gives it the power to describe behaviour.

A subtle case arises with double rendering. Maison Aster allows a few simple variables in an email template. The first engine replaces these variables and produces text. To save time, a second mechanism then treats that result as a new, more powerful template. A value that should have remained content then becomes an instruction for the second interpreter. The flaw lies in the decision to reinterpret the output.

The best defence is to remove that step: produce text or a data structure, then display it without further evaluation. For a system operation, a dedicated API is preferable to launching a shell. If no replacement exists, the program must separate the executable from its arguments, choose operations from a closed list, validate every parameter and run under a very limited account. Escaping a string for a shell remains fragile because several layers may parse it differently.

In the dangerous circuit, a declared parcel becomes an instruction for the machine and the template press; the safe circuit inspects, isolates and only passes bounded parameters to a dedicated tool.

Web processes should not be able to write to their own code, modify central logs or launch tools unrelated to their function. Containers limit some consequences, but do not fix the injection: mounted secrets, internal network, shared volumes and orchestration interfaces determine the real extent of access. The OWASP cheat sheet on command injection favours avoiding system calls, then strict separation of parameters and allow-lists.

Vulnerabilities become serious when they answer one another

An organisation can fix each anomaly according to an isolated score and underestimate the chain. The old subdomain reveals a version. A public archive exposes a reused test secret. That account opens an internal interface. An IDOR gives access to an admin document. A stored XSS then reaches an operator's browser. Finally, an overly permissive upload or an injection makes it possible to act on the server.

Each link changes the value of the next one. An XSS on an anonymous page is already a flaw; on a console used by administrators, its potential impact increases. An SQL injection under a limited read-only account remains serious; under an account that owns every database, it can become systemic. Access to an isolated container is not the same as access to the host; an orchestration socket mounted in that container can erase that boundary.

The remedy is architectural. Test secrets must not work in production. Technical accounts get only the rights they need. Administration interfaces use strengthened identity and a controlled network. Outbound network traffic is limited to what is needed. Environments are separated. Backups and logs cannot be modified by the same account as the application.

This logic turns defence in depth into something concrete: even after a mistake in the code, the other layers continue to limit the data accessible, the actions possible, the duration of access and the ability to conceal the incident.

After access: persistence, traces and the investigator's viewpoint

A compromise does not end with the first access. An adversary may look for other credentials, create a way back in, modify an automated job or take advantage of a deployment integration. Describing concealment techniques as a recipe would be irresponsible; for the defender, what matters is recognising their effects: gaps in the timeline, emptied local logs, inconsistent dates, new accounts, unknown jobs, modified system files and services that restart without any planned change.

If the application can erase its own logs, an attacker who has taken over its identity can too. Important events must therefore leave the host quickly for a separate platform. Write permissions should be append-only where possible; viewing, deletion and modification follow distinct roles. Clocks must be synchronised so that a login, an API request, a database query and an administrative action can be linked.

Events from every layer converge on a separate vault; even if local traces disappear, the remote timeline makes it possible to correlate, alert, isolate and restore.

Logging everything is not the solution. A useful log answers questions: who requested what, on which object, with what result, from which context and when? It avoids passwords, session tokens and unnecessary personal data. It protects the integrity of the elements needed for the investigation and raises alerts on high-value behaviour: privilege escalation, creation of an admin account, mass export, rule changes or erasure of traces.

OWASP ranks logging and alerting failures among the major risks: an undetected event gives the adversary time to turn a one-off weakness into a lasting presence.

The first twenty-four hours of an incident

Haste can destroy evidence or leave access open. The first decision is to establish what is certain, what is probable and what is still unknown. The team preserves useful logs, state snapshots and volatile artefacts before rebuilding. It restricts access to the affected system without blindly cutting off the sources that still make it possible to understand the timeline.

The response can follow seven moves:

  • open a shared timeline and name a decision-maker;
  • preserve remote logs, account status, deployments and images of the systems concerned;
  • isolate the compromised component and block the confirmed vector;
  • revoke exposed sessions, keys and secrets according to their real scope;
  • search for the same indicator in other environments and dependent services;
  • rebuild from a known baseline, fix the cause and check neighbouring controls;
  • assess notification obligations, then communicate with dated facts rather than assumptions.

Changing every password does not fix an injection. Taking a server offline does not remove persistent access elsewhere. Restoring a backup without closing the entry point replays the incident. Remediation must link the technical cause, the privileges obtained, the possible movements and the data actually affected.

Once service is restored, the organisation turns the investigation into verifiable changes: a regression test, a detection rule, reduced privileges, a designated owner and a review date. A useful report does not look for a convenient culprit. It explains which decisions in the system allowed the chain and how each one will be made harder to reproduce.

The decisive control for each boundary

Web security becomes easier to read when each component answers a precise question:

  • inventory: do we know every reachable surface and its owner?
  • identity: does the session still correspond to an active account at the right assurance level?
  • authorisation: can this subject perform this action on this object in this state?
  • rendering: will this value remain data in the context where it is placed?
  • database: are the query and its parameters kept separate all the way to the engine?
  • browser: does the operation prove the expected intention and origin?
  • file: is its actual content checked, isolated, renamed and served without execution?
  • interpreter: can we remove evaluation or replace the shell with an API?
  • privileges: what can this component still reach if its code is compromised?
  • logging: will the timeline survive the loss of the host?

These questions should appear in design reviews, tests and incident procedures. They prevent security from being reduced to a scanner or a list of patches. A tool flags an anomaly; an architecture decides how far it can spread.

Breaking the chain before it becomes a story

The account of a successful attack often seems inevitable in hindsight. The clues were public, the archive was there, the check was missing, the log did not raise an alert. Before the incident, these same elements lived in different teams and tools. Nobody saw the full trajectory.

Maturity means making that trajectory visible sooner. It combines inventory, safe output, parameterised queries, per-object authorisation, protection of intention, isolated file handling, removal of unnecessary interpretation, least privilege and independent logs. None of these controls promises invulnerability. Together, they remove links, reduce impact and speed up detection.

A robust website is not one that never contains a mistake. It is one where a single mistake is not enough to open the next room, where abnormal actions leave a reliable trace and where the team knows exactly what to preserve, isolate and rebuild when an alert becomes an incident.

Editorial information
Written by
Jeremy Kraft
Last reviewed
Method
Public sources, editorial review and proportionate guidance.
← Author articles

Discussion

Comments

0

No comment yet. Feel free to open the discussion with a question or your own experience.

Have an account? Sign in

Plain text, 2 000 characters maximum. New comments are moderated. Your name is shown with the comment. No e-mail address is asked; only a non-reversible fingerprint of your IP address is kept to fight spam.