WEB PENTESTING / VISUAL FIELD GUIDE / 02

From Input to Attack Chain

Follow input into a report builder and a staff review screen. Understand where data becomes instructions—and what evidence connects the consequences.

By Botnet Security6 min read
From Input to Attack Chain: untrusted data reaches an interpreter, with a boundary check separating it from impact.

Inspired by generalized themes from a Botnet Security education-sector assessment. Campus Demo, its users, records, messages and code are fictional teaching examples—not customer evidence or an exploitable application.

A report filter should select data. A feedback note should display text. Trouble starts when either is interpreted as instructions.

Part 1 followed identities and access decisions. Here, follow input into two different interpreters: a database query engine and a browser. The goal is to recognize the failed boundary, understand the repair and avoid claiming an attack chain that the evidence does not support.

01 / A field name is not a value

Campus Demo lets staff choose a report field and a status filter. Both arrive as input, but they play different roles. The status is a literal value. The chosen field influences query structure.

● CAMPUS DEMO / REPORT BUILDERFICTIONAL UI

Admissions report

Two inputs. Two different trust decisions.

Field choice
createdDate
Status filter
submitted
Tenant scope
From validated session
Secure design: the server chooses the database field; the filter remains bound data.
Client field token createdDate maps to a fixed server-owned database column. The status submitted is bound separately as a value. Both reach a tenant-scoped query under a restricted database principal.
FIG 03 Map structural choices to fixed definitions. Bind literal values separately. View full size ↗

Prepared statements protect values. Most database APIs cannot use an ordinary value placeholder for a table name, column name or sort direction. Those choices need fixed server-owned mappings. Passing an unchecked client string into query structure breaks the boundary between data and command syntax.

This pseudocode illustrates the intended repair. Its query builder is conceptual: a real implementation must use its library's documented identifier handling and parameter binding.

DEFENSIVE DESIGN / FIXED FIELDS + BOUND VALUES
# Defensive pseudocode — not a complete endpoint
fields = {
  "createdDate": "created_at",
  "applicationStatus": "status"
}

column = fields.get(request.field)
if column is None:
    reject_unknown_field()

require_field_permission(actor, column)
query.select_fixed_column(column)
query.where_equal("tenant_id", actor.tenant_id)
query.where_equal("status", request.status)
query.execute_with_bound_values()

A permitted column can still hold data the actor may not see, so field permissions and tenant scoping remain necessary. Restrict the reporting database account to the views and operations it needs. Read-only access can still expose too much; SQL injection does not automatically establish operating-system access.

Evidence checkpoint: a database error or slow response is a lead, not proof of query control. Application behavior, code review or database telemetry must distinguish the suspected failure from validation errors, load and network delay.

02 / Stored does not mean trusted

A student submits a note. The application stores it. Later, a staff member opens a review screen. The input has changed location and reader, but it has not become trustworthy.

Untrusted note moves from a student form to storage and a staff view. Plain-text rendering preserves data. Unsafe interpretation can make content executable in the viewing origin, subject to browser policy and the viewer's permissions.
FIG 04 The dangerous decision happens when the saved value is rendered—not merely when it is stored. View full size ↗

Stored cross-site scripting occurs when persisted untrusted input later becomes executable in a browser context. The submitting user and the affected viewer may differ. That explains why a low-privilege feature can matter to a staff workflow, without proving that every staff action becomes available.

For ordinary text, use a text-safe rendering API. This benign example contains formatting characters, not an exploit. It shows why preserving data as data is a deliberate design decision.

SAFE BROWSER EXAMPLE / NO HTML INTERPRETATION
// A plain-text note does not need an HTML parser.
const note = "Lab note: <b>draft</b>";
preview.textContent = note;

// Visible result:
// Lab note: <b>draft</b>
// The tag characters remain ordinary text.

Rich text is different: if formatting is required, use a maintained sanitizer with a restricted policy. Encoding must match the destination; HTML text, URLs and JavaScript contexts are not interchangeable. Review previews, notification views and exports where the same stored value can be rendered again.

Content Security Policy may limit script execution, depending on the actual policy. HttpOnly blocks JavaScript from reading a cookie, but does not prevent an injected script from making requests through the signed-in browser. Neither control replaces correct output handling.

Evidence checkpoint: a saved marker proves persistence. Its appearance in a staff view proves a data flow. Neither observation, by itself, proves script execution or an administrative consequence.

03 / Connect evidence, not labels

SQL injection and XSS appearing in one report do not automatically form a chain. A chain requires one step's supported outcome to satisfy the next step's precondition in the same environment.

Evidence ladder: a note is saved, a staff view renders it, an unsafe execution context is confirmed, and a specific consequence is demonstrated. The last two links are marked unverified in this fictional example.
FIG 05 A missing link stays visible. Do not turn a plausible connection into a claimed result. View full size ↗

In the fictional ladder, the first two observations establish where content travels. The later connections remain unverified. Calling the result “administrator takeover” would jump over both the execution boundary and the question of what that browser session can actually do.

Use precise evidence labels: observed for recorded behavior, demonstrated for a supported security consequence, and hypothesized for an unverified connection. Explain the starting identity, failed control, resulting capability and controls that still constrain it. Severity scores cannot be added together to calculate a chain score.

  • Retest the query rule: field choices remain fixed, values stay bound, and the reporting principal retains only required access.
  • Retest the rendering rule: text remains text, while supported rich content follows the same sanitization policy in every relevant view.
  • Retest the business outcome: the repaired control blocks the unauthorized consequence without breaking legitimate work.

The research habit: trace where data changes meaning, prove the smallest meaningful consequence within scope, and keep uncertainty visible. That makes the report useful to the engineer who has to repair it.

REVISIT PART 01Beyond the Login 5 min read · Identity, access and state
NEXT STEP

Find the decision that failed.

Build an assessment around your application's roles, data boundaries and workflows. Testing begins with scope and written authorization.