In PHP, which $_SERVER entries can a client control, and which can you rely on — REMOTE_ADDR, HTTP_HOST, SERVER_NAME, PHP_SELF?
answer
- every HTTP_* key is client input
- REQUEST_URI and QUERY_STRING too
- PHP_SELF includes PATH_INFO
- SERVER_NAME may echo the Host header
- REMOTE_ADDR: the peer, maybe a proxy
basics
~20 sEvery HTTP_* entry, REQUEST_URI, QUERY_STRING and PHP_SELF carry client input. SERVER_NAME can reflect the client's Host header unless the server pins it. REMOTE_ADDR is the connecting peer's address — reliable, but behind a proxy it is the proxy's.
solid answer
~50 s`$_SERVER` mixes values the server computes with values copied from the request, and nothing marks which is which. Client-controlled: every `HTTP_*` key (including `HTTP_HOST`, `HTTP_REFERER` and `HTTP_X_FORWARDED_FOR`), `REQUEST_URI`, `QUERY_STRING`, and `PHP_SELF`, which is `SCRIPT_NAME` plus any `PATH_INFO` — so a request for `/book.php/"><script>` puts markup into it, and echoing `PHP_SELF` into a form action is a classic XSS. `SERVER_NAME` is only as reliable as the server configuration: the manual warns that under Apache without a pinned canonical name it reflects the host the client supplied. `REMOTE_ADDR` is the address of the TCP peer, which a client cannot simply make up — but behind a load balancer it is the balancer, and the original client appears in a header you may trust only when your own proxy set it. Build absolute URLs from configuration, never from `HTTP_HOST`.
code
php · 13 lines<?php
declare(strict_types=1);
$baseUrl = getenv('BOOKING_BASE_URL') ?: 'https://booking.example'; // configured, not from Host
$trustedProxies = ['10.0.0.5', '10.0.0.6'];
$clientIp = $_SERVER['REMOTE_ADDR'] ?? '';
if (in_array($clientIp, $trustedProxies, true) && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$hops = array_map('trim', explode(',', (string) $_SERVER['HTTP_X_FORWARDED_FOR']));
$clientIp = end($hops); // the address our own proxy appended
}
$resetLink = $baseUrl . '/reset?token=' . urlencode($token ?? '');go deeper
Recall that $SERVER values copied from the request, such as HTTP* headers and REQUEST_URI, are as untrusted as $_GET.
Sort the common keys by origin, explain why PHP_SELF includes path info, and why SERVER_NAME depends on server configuration.
Build absolute URLs from configuration, derive client IPs through a trusted-proxy list, and remove PHP_SELF and HTTP_HOST from security-relevant code.
Define where host names and client identity are established — at the edge or in configuration — so application code never guesses them from headers.
## Two kinds of values in one array `$_SERVER` looks like server information, but it is assembled from two very different sources: - values the **server** determines — the script's path on disk, the connecting address, the server's configured name; - values copied from the **request** — the URL, the query string and every header. Nothing in the array marks which is which. The table sorts the common keys by who controls them. | Key | Comes from | Trust | |---|---|---| | `HTTP_*` (all headers, incl. `HTTP_HOST`, `HTTP_REFERER`, `HTTP_USER_AGENT`) | Request headers | Client-controlled | | `REQUEST_URI`, `QUERY_STRING` | The request line | Client-controlled | | `PHP_SELF` | `SCRIPT_NAME` plus `PATH_INFO` | Partly client-controlled | | `REQUEST_METHOD` | The request line | Client-chosen, but only from what the server accepts | | `SERVER_NAME` | Server configuration, or the Host header | Depends on the server setup | | `REMOTE_ADDR` | The TCP connection | Reliable, but may be a proxy | | `SCRIPT_FILENAME`, `DOCUMENT_ROOT` | Server configuration | Server-controlled | ## PHP_SELF and PATH_INFO `PHP_SELF` is built as `SCRIPT_NAME` followed by any **path info** — the part of the URL after the script's name. A request for `/book.php/"><script>alert(1)</script>` still runs `book.php`, and `PHP_SELF` contains the attacker's markup. Code such as `<form action="<?= $_SERVER['PHP_SELF'] ?>">` then reflects it into the page. Use `SCRIPT_NAME` or a fixed route, and escape any request-derived value when you output it. ## Host headers `HTTP_HOST` is whatever the client put in its `Host` header. Using it to build absolute URLs is dangerous: 1. A password-reset or booking-confirmation email built as `https://{$_SERVER['HTTP_HOST']}/reset?token=...` can point at an attacker's domain if the request carried a forged `Host`. 2. Cache entries keyed on the host can be poisoned. `SERVER_NAME` is not automatically safer. The PHP manual notes that under Apache 2, unless `UseCanonicalName = On` and `ServerName` are set, `SERVER_NAME` reflects the hostname supplied by the client, and that it is not safe to rely on it in security-dependent contexts. The robust approach is a configured base URL — an environment variable or config value — plus, if the application serves several hosts, an explicit allow-list checked against `HTTP_HOST`. ## Client IP addresses `REMOTE_ADDR` is the address of the machine that opened the TCP connection to the server. A client cannot simply make it up and still receive a response, which makes it the one reliable address value — but in most deployments that machine is a reverse proxy or load balancer: - the original client's address travels in a header such as `X-Forwarded-For`, visible as `HTTP_X_FORWARDED_FOR`; - that header is client-controlled unless your own proxy **overwrites** it, and a client can prepend fake entries to it; - trust it only when `REMOTE_ADDR` is one of your known proxies, and then only the entries your proxies added. Rate limiting or blocking by a spoofable header lets an attacker rotate "addresses" at will. ## REQUEST_METHOD `REQUEST_METHOD` is copied from the request line, so the client chooses it, but only among the methods the web server passes through. It is safe to **branch on** — comparing it with `'POST'` or `'GET'` — and unsafe to assume it is one of a few known values. Some clients also send an override header or form field to simulate `PUT` or `DELETE`; honouring such overrides is an application decision, not something `$_SERVER` does for you. ## Logging request values Access and audit logs often record `REQUEST_URI`, `HTTP_USER_AGENT` and `HTTP_REFERER`. Because they are client-controlled, they can carry newlines that forge extra log lines, very long strings, or markup that becomes dangerous when a log viewer renders HTML. Truncate them, strip or encode control characters, and escape them in any web-based log viewer. ## Practical rules - Treat every `HTTP_*` value, `REQUEST_URI`, `QUERY_STRING` and `PHP_SELF` exactly like `$_GET`: validate on input, escape on output. - Build absolute URLs from configuration, not from request headers. - Derive the client IP from `REMOTE_ADDR`, and consult forwarding headers only through a trusted-proxy list. - Log request-derived values carefully; they can contain control characters and markup.
- Why is echoing $_SERVER['PHP_SELF'] into a form's action attribute dangerous?`PHP_SELF` is `SCRIPT_NAME` plus any path info from the URL, and path info is chosen by the client. A request for `/book.php/"><script>...` still runs the script, and the markup ends up in the page. Use `SCRIPT_NAME` or a fixed route, and escape whatever request-derived value you output.
- Is SERVER_NAME a safe replacement for HTTP_HOST when building links?Not by itself. The manual warns that under Apache without `UseCanonicalName = On` and a set `ServerName`, `SERVER_NAME` reflects the hostname the client supplied. Configure a base URL for the application instead, and if several hosts are served, check `HTTP_HOST` against an allow-list.
saying these in an interview costs you the question
- $_SERVER is filled by the server, so all of its values are safe
- HTTP_X_FORWARDED_FOR holds the real client IP and can be trusted
- PHP_SELF contains only the script's path, so it is safe to echo
- SERVER_NAME always comes from server configuration, never the client
- REMOTE_ADDR is always the end user's address, even behind a load balancer