SSRF: localhost block checks only the literal host, so Allow Private IPs opens loopback #4

Closed
opened 2026-10-16 17:51:15 -07:00 by eric · 0 comments
Owner

Found in security audit run 3 (2026-10-16, 06d630d). Severity: low.

Problem

resolve_and_validate_url() runs is_loopback_address() only on the literal URL host (includes/class-apprise-client.php:412), never on the resolved IP. A loopback address reached any other way falls through to the private/reserved range check (:463), and the admin "Allow Private IPs" setting overrides that check (:489-490).

The localhost exception is meant to be code-only, through the apprise_notifications_allow_localhost filter. Its docblock says this is to stop admins probing local services. With the setting on, an admin can still reach localhost using [::], 0177.0.0.1, 2130706433, 127.1, or any hostname that resolves to 127.x. Test Connection then returns the transport error or status code, which shows whether a port is open.

is_loopback_address() also treats 0.0.0.0 as loopback but not its IPv6 equivalent :: (:636-637).

This matters where admins can't otherwise run code: multisite subsite admins, or sites with DISALLOW_FILE_MODS.

Fix

  • After resolution and the IPv4-mapped unwrap, check the resolved IP with is_loopback_address(). Send any match through apprise_notifications_allow_localhost before the private-range override.
  • Treat 0.0.0.0/8 and :: as loopback.
  • Regression cases in tests/Unit/ClientTest.php with allow_private_ips = true: [::], 0177.0.0.1, 2130706433, 127.1, and a name resolving to 127.x. Each should give localhost_blocked.
Found in security audit run 3 (2026-10-16, `06d630d`). Severity: **low**. ## Problem `resolve_and_validate_url()` runs `is_loopback_address()` only on the literal URL host (`includes/class-apprise-client.php:412`), never on the resolved IP. A loopback address reached any other way falls through to the private/reserved range check (`:463`), and the admin "Allow Private IPs" setting overrides that check (`:489-490`). The localhost exception is meant to be code-only, through the `apprise_notifications_allow_localhost` filter. Its docblock says this is to stop admins probing local services. With the setting on, an admin can still reach localhost using `[::]`, `0177.0.0.1`, `2130706433`, `127.1`, or any hostname that resolves to 127.x. Test Connection then returns the transport error or status code, which shows whether a port is open. `is_loopback_address()` also treats `0.0.0.0` as loopback but not its IPv6 equivalent `::` (`:636-637`). This matters where admins can't otherwise run code: multisite subsite admins, or sites with `DISALLOW_FILE_MODS`. ## Fix - After resolution and the IPv4-mapped unwrap, check the **resolved** IP with `is_loopback_address()`. Send any match through `apprise_notifications_allow_localhost` before the private-range override. - Treat `0.0.0.0/8` and `::` as loopback. - Regression cases in `tests/Unit/ClientTest.php` with `allow_private_ips = true`: `[::]`, `0177.0.0.1`, `2130706433`, `127.1`, and a name resolving to 127.x. Each should give `localhost_blocked`.
eric closed this issue 2026-10-16 21:45:17 -07:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eric/apprise-wordpress-notifications#4
No description provided.