fix(core): removed eval from PHx and hardened @FILE - #2428
Open
elcreator wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The setup: what $key looks like when it arrives
By the time resolveSGVar() runs, _getSGVar() has already massaged the tag. For a template tag [[$_GET(id)]]:
So $key is a string like $_GET['id'], $_SERVER['HTTP_HOST'], $_SESSION['user']['name'], or the bare form $_SERVER. The job is to turn that string into the actual value without letting PHP interpret it. The old code did
eval("return {$key};") — which is why $_SERVER .
idexecuted a shell command. This code reads the value by hand instead.resolveSGVar() — three steps
Step 1: identify and allowlist the superglobal
The string must start with one of the eight known superglobal names. $matches[0] is the matched prefix (e.g. $_GET), $matches[1] is the bare name (e.g. GET). Anything else — $GLOBALS, $this, a bare expression — is refused
immediately. This is the allowlist: nothing outside these eight names can ever be reached.
Step 2: parse the ['key'] accessors into a path
This walks the remaining string one [...] at a time, building a list of keys. For $_SESSION['user']['name'] it consumes ['user'] then ['name'], producing $path = ['user', 'name'].
The regex is the important guard — breaking it down:
The [^\[\]\'"] character class is the teeth: a key can't contain a bracket, quote, backtick, $, ., or any other character that could start an expression — because those characters would have to appear inside the class to be
allowed, and they aren't. Anything that isn't a clean [key] accessor makes the regex fail, and the whole tag is refused with ''. That's how $_SERVER .
iddies: after matching $_SERVER, $rest is .id, which isn't [...], soreturn ''.
Note: because the caller already turned (id) into ['id'], the unquoted accessor form ($accessor[1] empty) is what a (key) tag becomes, and the quoted form is what a literal ['key'] tag is — the regex accepts both via the
optional-quote group.
Step 3: walk the real array
returned is a genuine array element — never anything evaluated.
getSuperGlobal() — why a function instead of $$name
This maps the name string to the real superglobal explicitly. It matters that it's a hardcoded switch and not $GLOBALS[$name] or a variable-variable $$name — a dynamic lookup would reintroduce exactly the "attacker controls
which variable I read" problem the allowlist just closed. The switch can only ever return one of the eight.
The SESSION case is special: it returns a copy (PHP arrays are copy-on-assignment), then unset()s mgrFormValues and token from that copy. So a [[$_SESSION]] dump can't leak the CSRF token or stored form values, and because
it's a copy, the real $_SESSION is untouched. This preserves the redaction the original _getSGVar did.
The net effect
Every value that comes out is either a literal array element you named or a print_r of a redacted array. There is no code path where any part of the tag is executed — the string is only ever matched against regexes and used as
array keys. That's the whole point: same reads as before for legitimate $_GET(id)-style tags, zero reachability for
id, ;phpinfo(), concatenation, or anything else.