skip to content

In PHP, why is extract($_GET) or a variable variable built from request keys dangerous, and how should you read request values instead?

level: middleimportance: nice to knowfreq 26%

answer

  1. request keys become local variable names
  2. extract can overwrite existing variables
  3. EXTR_OVERWRITE is the default flag
  4. extract skips GLOBALS, throws on $this
  5. read expected keys explicitly by name

basics

~20 s

extract($_GET) creates a local variable for every request key, so a request can define or overwrite variables your logic trusts (like $isAdmin). The default EXTR_OVERWRITE clobbers existing ones; variable variables ($$key) share the flaw. Read expected keys by name instead.

solid answer

~50 s

`extract($array)` walks the array and creates a local variable for each key, so `extract($_GET)` lets the **request name your variables**. With the default flag `EXTR_OVERWRITE`, an incoming key overwrites an existing local — a request `?isAdmin=1` sets `$isAdmin` even if your code assumed it was `false`. This resurrects the old `register_globals` hazard (removed in PHP 5.4) by hand. Variable variables (`$$key = $value` over request data) are the same defect written differently. The engine only refuses two names — it **skips `GLOBALS`** and throws an `Error` (`Cannot re-assign $this`) for `$this` — which is not a security model. Flags like `EXTR_SKIP` (don't overwrite) or `EXTR_PREFIX_ALL` reduce the blast radius, and the manual still warns against using `extract` on untrusted data at all. The correct pattern is explicit: read the keys you expect by name (`$page = $_GET['page'] ?? 'home';`), validating each.

go deeper

for a junior

Know that extract($_GET) lets a request set your variables and to read expected keys by name instead.

for a middle

Explain the EXTR_OVERWRITE default, that GLOBALS is skipped and $this throws, and why that is not a security control.

for a senior

Recognise a register_globals-style overwrite bug in review and replace extract/variable-variables with explicit reads or destructuring.

for a principal

Decide when fanning a trusted array into scope is acceptable and set a rule against extract on any request superglobal.

## What extract() and variable variables do `extract(array $array, int $flags = EXTR_OVERWRITE, string $prefix = "")` imports an array into the current symbol table: each key becomes a variable name, each value its value. Variable variables do the same one at a time — `$$name = $value` creates a variable whose name is the *runtime value* of `$name`. Both let **data decide which variables exist**. With request input that is a vulnerability, because your code's decisions rest on variables an attacker can now define: ```php $isAdmin = currentUser()->isAdmin(); // false for a normal user extract($_GET); // ?isAdmin=1 overwrites it if ($isAdmin) { showAdminPanel(); } // now true ``` The request supplied a variable the code trusted. This is exactly the failure mode of `register_globals`, a setting so dangerous it was **removed in PHP 5.4**; `extract($_REQUEST)` reintroduces it in application code. ## The flags, and what the engine refuses The second argument controls collisions: | Flag | Behaviour on a name clash | |---|---| | `EXTR_OVERWRITE` (default) | overwrite the existing variable | | `EXTR_SKIP` | keep the existing variable, ignore the incoming one | | `EXTR_PREFIX_ALL` | prefix every name with the given prefix + `_` | | `EXTR_PREFIX_INVALID` | prefix only otherwise-invalid names | | `EXTR_IF_EXISTS` | import only names that already exist | The engine itself protects only two names: it **silently skips a `GLOBALS` key**, and it **throws `Error: Cannot re-assign $this`** if a key is `this`. Invalid variable names (starting with a digit, etc.) are skipped unless a prefix flag makes them valid. None of this constrains an attacker who simply targets your ordinary variable names. ## Why flags are mitigation, not a fix `EXTR_SKIP` means a request can no longer *overwrite* a variable you already set — a real improvement — but it can still *create* ones you check later with `isset()` or use before initialising, and it depends on you having set every sensitive variable first (in the order of `variables_order`, as the manual cautions). `EXTR_PREFIX_ALL` namespaces the imports, which is safer but at that point you are reading `$prefix_page` anyway, so you may as well read `$_GET['page']`. The manual's guidance is blunt: **do not run `extract()` on untrusted data**. ## The pattern to use instead 1. **Read expected keys by name**, with a default and validation: ```php $page = $_GET['page'] ?? 'home'; $sort = in_array($_GET['sort'] ?? '', ['name', 'date'], true) ? $_GET['sort'] : 'name'; ``` 2. **Never build a variable name from input** (`$$key`, `${$_GET['x']}`); use an array or an allow-listed `match`. 3. If you must fan a *trusted* array into variables, prefer array **destructuring** (`['page' => $page] = $data;`) so the names are written literally in your code and visible to static analysis, rather than decided by the data. `extract` has legitimate uses on data you built yourself (e.g. a template's variables), but on `$_GET`, `$_POST`, `$_REQUEST`, `$_COOKIE`, or decoded request bodies it hands the caller control of your local scope. Where the raw values originate is owned by the request-handling leaf; the sink here is the act of turning request *keys* into variable *names*.

  • Doesn't `EXTR_SKIP` make `extract($_GET)` safe?
    It stops the request from *overwriting* variables you already assigned, which removes the worst case, but only if you set every sensitive variable first. It can still create variables you later test with `isset()`, and it is fragile to refactoring. Reading the specific keys you expect is simpler and not order-dependent.
  • What names can a request NOT clobber through extract?
    The engine skips a `GLOBALS` key and throws `Error: Cannot re-assign $this` for a `this` key, and it won't create syntactically invalid names (like ones starting with a digit) unless a prefix flag fixes them. Everything else — including your ordinary `$isAdmin`-style variables — is fair game, so those two exclusions are not a defence.
  • Is array destructuring a safe alternative?
    Yes, when the source is trusted: `['page' => $page] = $data;` writes the variable names literally in your code, so the data supplies only values, never names, and static analysis can see every variable. It is the readable, safe replacement for `extract` on data you control.

saying these in an interview costs you the question

  • extract($_GET) is a handy shortcut for reading request params
  • EXTR_SKIP makes extracting untrusted input fully safe
  • extract cannot overwrite existing variables
  • variable variables ($$key) are safer than extract
  • the engine blocks all sensitive names automatically