One ../ and the server hands over the wrong file
Unauthenticated WordPress path traversal lets an anonymous request select which file the server reads; execution follows where a code-executing sink exists.
An unauthenticated path traversal means an anonymous request decides which file the server reads. No login. No token. No valid session. The attacker sends a request, controls a parameter that names a file, and the server returns content the request was never scoped to reach. That is the boundary failure stated in one line. Everything after it is consequence.
From the attacker side, the first request is not an exploit. It is a probe. The question being asked is narrow: does this parameter reach the filesystem, and does it respect a boundary. A single ../ sequence answers both at once. If the response body changes when the traversal string changes, the parameter is a file selector. If the response returns a file that sits outside the intended directory, the boundary is not being enforced. Two requests establish the entire attack surface, and neither of them requires credentials.
Conditional RCE is the accurate label, and the word conditional carries weight. Path traversal on its own reads files. It does not run them. Read access becomes code execution only when a second, separate condition is met. Collapsing the two capabilities into one term misstates the exposure. State them apart. Capability one is arbitrary file read as an unauthenticated party. Capability two is execution, which exists only where a specific condition holds. Leadership assessing this should treat the first as confirmed the moment traversal works, and the second as a question answered per environment.
The observable behavior is simple to describe because it requires no view into the code. A request parameter that names a file is passed to a filesystem read, and the value of that parameter is fully attacker controlled. Supply the expected filename and the server returns the expected file. Supply ../../../../etc/passwd and the server returns a file outside the web root. The server does not reject the traversal sequence. It resolves it and returns the result. That behavior is visible entirely from outside the system, with no internal knowledge assumed.
The boundary that broke is the input trust boundary at the parameter. Input crossed from untrusted, meaning network sourced and unauthenticated, into a privileged operation, meaning a filesystem read, with no control enforced between the two. No canonicalization of the resolved path. No allow-list of permitted files. No confinement to a fixed base directory. The parameter was handled as if it were a name. It was in fact a path, and a path is an instruction to the filesystem rather than a label the application controls.
Identity is where this compounds, and it compounds by absence. Unauthenticated means there is no identity to evaluate, which means there is no access decision being made at all. The request is not this user may read this file. The request is read this file. The system that should sit between the request and the filesystem, deciding who is asking and what they are permitted to reach, is not in the path. When there is no identity, the only remaining boundary is the process permission set, and that boundary was never designed to be the last line.
The reason it fails traces to one action the application takes: it accepts attacker input and hands it to a filesystem call without normalizing the path or restricting the target. The proof is observable and does not depend on reading source. The same endpoint returns different files based only on the traversal string supplied. That is sufficient to conclude the control is absent. If a path control existed and were enforced, the ../ request would be rejected or confined to a base directory. It was not. A control that does not stop the behavior it is meant to stop is not a control. It is documentation.
Execution context sets the ceiling on what read alone can reach. The web server process runs as a specific user, and that user’s filesystem permissions define the blast radius of arbitrary read. Within that radius sit the configuration file holding database credentials, secret keys, authentication salts, any private keys the process can read, and application source. Read access to configuration is not a lesser outcome to be discounted. Credentials pulled from disk are credentials. They are usable wherever the boundary they protect is reachable, which is frequently far beyond the host that leaked them.
The condition that converts read to execution is where the second capability lives, and it must be stated as a set of distinct conditions rather than a single guaranteed step. Execution requires the read path to resolve to content the server will interpret rather than return as text. Three conditions commonly satisfy this. The traversal feeds an include or require call, so the targeted file is executed as PHP instead of served as bytes. The attacker first writes controlled content to a known location, an uploaded image, a poisoned log line, a session file on disk, and then traverses to that location, turning read into run. Or a stream wrapper is accepted by the vulnerable parameter, turning the file selector itself into a source of code. None of these is present by default. Each is a condition. Where one holds, arbitrary read becomes code execution in the web server’s context. Where none holds, the exposure remains arbitrary file read, which is still full disclosure of everything that process can reach.
The mechanism is delegation. The application does not decide which file to return. It forwards that decision to the filesystem by passing an attacker supplied string into a read call. The filesystem resolves paths. It does not authorize requests. A path handed to the filesystem is an instruction the filesystem will follow. A security decision was moved to a component that makes no security decisions. That relocation is the failure, and it is complete before any file is read.
The traversal sequence is not an attack in the filesystem’s terms. It is a valid instruction. Parent directory references resolve because resolving them is defined behavior. The failure is not that the filesystem obeyed. The failure is that untrusted, unauthenticated input reached an operation whose defined behavior includes leaving the intended directory. Once the input reaches the call, the result is fixed. That the response returned a file outside the web root is the observable proof that nothing downstream corrected the path. No later check exists in that path, because a later check would have produced a different response.
The mechanism does not fail once. It fails identically for every target the process user can read. One absent control produces unbounded selection across the readable filesystem, bounded only by process permissions. The configuration file holding database credentials, secret keys, authentication salts, any private key the process can read, and application source are each reached by the same operation with a different string. The attacker does not need a new technique per file. The technique is one string substitution repeated against one unguarded sink.
The pattern is the sink, not the string. Filtering the traversal sequence treats the string as the problem. The mechanism says the problem is that attacker controlled input reaches a sink whose behavior the attacker then directs. The traversal string is one expression of that condition. The sink is the constant. Where the sink reads bytes, the outcome is disclosure. Where the sink interprets bytes, per the stated conditions, the outcome is execution. Same untrusted input. Same absent control. Different terminal operation.
The three conditions that convert read to execution are one pattern expressed three ways. An include or require call makes the sink an interpreter, so the selected file runs as PHP. Write then traverse supplies content the sink will act on, an uploaded image, a poisoned log line, a session file on disk, then aims the selector at it. A stream wrapper changes what the selector resolves to. In each, the attacker controls the selector, and where the condition holds, the attacker also controls the nature of the operation that selector reaches. The condition is not chance. It is whether a code executing sink sits at the end of the same delegated path.
Read and execute are therefore properties of the environment’s sinks, not properties of the vulnerability. The vulnerability is fixed. An unauthenticated caller selects the file. What varies is what the selected file is fed to. Assessing only whether it can read understates the class, because it measures one sink and ignores the rest reachable by the same input. Which code executing sinks exist in a given deployment is not confirmed. That is a condition to resolve per environment. It is not a reason to downgrade the finding, and it is not permission to assume execution.
Arbitrary file read by an unauthenticated party is confirmed the moment traversal resolves outside the web root. Execution is conditional on a code executing sink, and whether one is present is not confirmed until the deployment is examined. Capability one is present. Capability two is unresolved. Absence of a known execution sink is not evidence of safety. It is an unmeasured state, and an unmeasured state is not a passing state.
What must now be true is narrow. The file returned must be decided by the application, not by the caller’s string. A path originating from an unauthenticated request must not reach a filesystem read unless the resolved target is confined to a permitted set. The parameter must be handled as attacker input at every point it is used, because that is what it is. The test is single. If the resolved path can leave the base directory, the control does not exist. The behavior described met that test in the failing direction.
The process permission set was the only boundary standing, and it was never designed to be the boundary. Identity was absent, so no access decision was made. A read that no identity authorized returned a file that no policy scoped. That is not specific to WordPress. It is the outcome when input selects a target and nothing between the network and the filesystem is enforced. If a system allows it, it will be requested, and this was requested in two requests without credentials. Everything the process can read is disclosed. Everything the process can interpret is a candidate for execution. Both follow from one unenforced boundary, and the boundary is identity.
Keep Reading
persistent authenticationThe bypass is a feature
Persistent authentication stores a completed verification as a token, then acts on the token forever. Reference replaces validation, and the person goes unchecked.
TOTPTOTP fixes the channel, not the credential
TOTP kills SMS interception and SIM-swap OTP theft, but AiTM phishing still steals the session token. What TOTP secures and what it doesn't.
open-source decision modelsYour decisioning problem isn't accuracy.
Run open-source decision models locally-pinned versions, validation, immutable logs-so every approval or denial stays reproducible and auditable.
Latest on the Wire
Full wire →- AI Ends the Fixed-Function App — and the Author Is Building a Phone for ItHacker News
- Appeals Court Backs Pentagon's Blacklisting of Anthropic Over ClaudeHacker News
- Botched Samsung firmware update bricks smart fridges, spoils food before Korean holidayHacker News
- DHH's Rails World keynote pulls a flagship app off Rails and bets everything on LLMsHacker News
New signal daily · RSS
Stay in the loop
New writing delivered when it's ready. No schedule, no spam.