Released

Howdah 1.3.0 is released — Layer 1 (request-shape limits) plus Layer 2 (input inspection, log-only). Howdah starts in detect mode: the critical limits block, everything else only logs. Read the coverage table for exactly what has been tested on your PHP version before you switch rules to block.

Home / Blog / Howdah 1.3

Howdah 1.3: phar, SSRF, and closing the PHP 5.x gap

Howdah 1.3 is a fast follow to 1.2. Layer 3 learns to watch two more things PHP does on an attacker's behalf, phar:// and outbound requests, and the include and eval guards, which 1.2 could only offer on PHP 7.0 and later, now work on PHP 5.x as well. As always, it logs first. Both new guards are off by default, and this post is as much about what they do not catch as what they do.

What's new in 1.3

  • phar:// guard. Howdah proxies PHP's phar stream wrapper, so filesystem functions, include and class entry points that open a phar:// URL pass through one check. On PHP 5.x and 7.x that covers phar metadata deserialization through the wrapper. [sinks.phar] allow_paths lists realpath prefixes that stay allowed.
  • SSRF egress guard. It checks file_get_contents, fopen, get_headers, fsockopen, stream_socket_client and curl against reserved and internal address ranges. [sinks.ssrf] allow_hosts takes hostnames, IPs and CIDRs for the internal services you do mean to call.
  • include and eval on PHP 5.x. The 1.2 mechanism needed the PHP 7 VM. On 5.x, 1.3 hooks the engine's compile step instead, the same route Suhosin took.

Off by default, log first

Neither new guard does anything until you turn it on with an explicit per-sink mode: [sinks.phar] mode = "detect" or [sinks.ssrf] mode = "detect". Setting [sinks] mode = "block" or the global howdah.mode = block does not arm them. SSRF is off for a practical reason too: turning it on adds a DNS lookup for each distinct outbound host, and that lookup has no timeout, so a stalled DNS server stalls the call. Read the log first, then decide whether to block. Events never contain the URL or path.

What these guards do not catch

The limits are the important part, so here they are plainly.

phar

  • PHP 8.x gets detection and denial only. PHP 8.0 stopped deserializing phar metadata automatically, so on 8.x the guard can log or refuse phar:// use, but we make no deserialization claim there.
  • The direct Phar API is a gap. new Phar, PharData, Phar::loadPhar, Phar::mapPhar and Phar::getMetadata unserialize before any wrapper operation, so the wrapper guard never sees them. Closing that needs another hook and is not in 1.3.
  • If another extension re-registers phar after Howdah, or phar is compiled statically into PHP, the guard fails open. Calling stream_wrapper_unregister and stream_wrapper_restore on phar is untested and could drop it.

SSRF

  • It is best-effort detection, not a connection-time boundary. Howdah resolves the host when the call is made. DNS rebinding, redirects to a new host, curl_multi_*, CURLOPT_RESOLVE and CURLOPT_CONNECT_TO, and a non-string or absent CURLOPT_URL all get around it. If you need a real egress boundary, use a firewall or an egress proxy.
  • Not every outbound primitive is hooked. copy, file, readfile, SoapClient, DOMDocument::load* and simplexml_load_file can still reach out unseen.
  • A blocked fsockopen or stream_socket_client leaves $errno and $errstr unset, and a blocked curl_exec returns false with curl_errno 0 and an empty curl_error. More than 32 resolved addresses for one host are capped, and a host longer than 256 bytes is treated as suspicious.

PHP 5.x include

  • Compile-time only. On 5.x, an include served from a warm opcache is not compiled again, so it is not re-inspected: already-cached application files are not checked. eval is never cached and is fully covered. On PHP 7.0+ the guard is a runtime handler and opcache does not affect it.

How far it was verified

The three features were tested end to end on real PHP 5.4, 5.6, 7.4, 8.3 and 8.5. Other minors build and are expected to work, but we have not independently verified them, and we do not claim they pass. A review of memory leaks on a PHP debug build is still pending, because the 5.x work was tested on release builds. Our reproducers recreate only the harmless shape of each attack, never a working exploit.

Get it

If you already run Howdah, apt-get update && apt-get upgrade (or the yum equivalent) brings in 1.3, and nothing changes until you opt in. New here? Start at the install picker, read the configuration reference for [sinks.phar] and [sinks.ssrf], and check the coverage page for the per-version table. The source and this site are on GitHub.