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'spharstream wrapper, so filesystem functions,includeand class entry points that open aphar://URL pass through one check. On PHP 5.x and 7.x that covers phar metadata deserialization through the wrapper.[sinks.phar] allow_pathslists realpath prefixes that stay allowed.- SSRF egress guard. It checks
file_get_contents,fopen,get_headers,fsockopen,stream_socket_clientand curl against reserved and internal address ranges.[sinks.ssrf] allow_hoststakes hostnames, IPs and CIDRs for the internal services you do mean to call. includeandevalon 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::mapPharandPhar::getMetadataunserialize 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
pharafter Howdah, or phar is compiled statically into PHP, the guard fails open. Callingstream_wrapper_unregisterandstream_wrapper_restoreonpharis 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_RESOLVEandCURLOPT_CONNECT_TO, and a non-string or absentCURLOPT_URLall 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*andsimplexml_load_filecan still reach out unseen. - A blocked
fsockopenorstream_socket_clientleaves$errnoand$errstrunset, and a blockedcurl_execreturns false withcurl_errno0 and an emptycurl_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
includeserved from a warm opcache is not compiled again, so it is not re-inspected: already-cached application files are not checked.evalis 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.