Skip to content

[3.0] Harden avatar-from-URL fetch against SSRF #4549

@imorland

Description

@imorland

Background

During a security review, the avatar-from-URL fetch path in the OAuth registration flow was identified as lacking SSRF mitigations beyond a DNS resolution check (active_url) and scheme validation (http/https).

The affected code is called when an OAuth provider supplies an avatar_url in the user profile response, which Flarum then fetches server-side to store the avatar locally.

1.xRegisterUserHandler::uploadAvatarFromUrl() passes the URL directly to Intervention\Image::make($url), which fetches via PHP's allow_url_fopen. No patch is being issued for 1.x (security-maintenance only).

2.xUserResource::retrieveAvatarFromUrl() uses a bare new GuzzleHttp\Client() with no configuration — no timeout, no size limit, redirect-following enabled by default. A partial fix will be applied before the 2.x release: redirect-following will be disabled and a timeout and size limit added. This closes the redirect-based SSRF vector specific to 2.x.

This issue tracks the remaining full fix for 3.0.

What needs doing in 3.0

  • Replace the Guzzle client with a fully hardened configuration:
    • allow_redirects => false (already done in 2.x partial fix)
    • Short timeout and a response size limit (already done in 2.x partial fix)
  • Add connection-level IP range filtering after hostname resolution, covering:
    • Loopback (127.0.0.0/8, ::1)
    • Link-local / cloud metadata (169.254.0.0/16, fe80::/10)
    • RFC 1918 private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
  • Filtering must be done atomically on the resolved connection — not via a pre-fetch gethostbyname() check, which is vulnerable to DNS rebinding race conditions
  • Upgrade to Intervention Image v4 (touches the same code path — doing both together avoids two passes at this area)

Acceptance criteria

  • Avatar fetch uses a hardened Guzzle client (no redirects, timeout, size limit)
  • Private/loopback/link-local IP ranges are blocked after connection-level resolution
  • DNS rebinding is not possible (filtering is on the connection, not a pre-check)
  • Intervention Image v4 in use
  • Existing avatar URL tests pass; new tests cover blocked IP ranges and redirect attempts

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions