Skip to content

[Security] Add redirect loop protection to AuthenticationService #751

@dereuromark

Description

@dereuromark

[Security] Add redirect loop protection to AuthenticationService

Summary

The AuthenticationService::getLoginRedirect() method is vulnerable to redirect loop attacks where malicious actors or misconfigured bots can create deeply nested redirect chains that waste server resources and potentially enable security exploits.

Current Behavior

When an unauthenticated user tries to access a protected page, the authentication middleware redirects them to the login page with a redirect query parameter containing the original URL:

GET /protected/page → 302 Redirect to /login?redirect=/protected/page

However, the getLoginRedirect() method (lines 433-461 in src/AuthenticationService.php) only validates that the redirect URL:

  • Is not empty
  • Doesn't contain a host or scheme (prevents external redirects)
  • Starts with /

It does NOT check for:

  • ❌ Recursive redirect parameters
  • ❌ Multiple encoding levels
  • ❌ Redirecting back to authentication-related pages

Vulnerability Example

A bot or attacker can create URLs like:

/login?redirect=/login?redirect=/login?redirect=/protected/page

Which when URL-encoded becomes:

/login?redirect=%2Flogin%3Fredirect%3D%252Flogin%253Fredirect%253D%25252Fprotected%25252Fpage

Each time the system processes this, it creates another level of nesting, potentially leading to:

  1. Resource exhaustion - Server wastes CPU parsing deeply nested URLs
  2. Log pollution - Malformed URLs flood access logs
  3. SEO damage - Search engines index login pages with redirect loops
  4. Potential exploits - Could be combined with other vulnerabilities

Real-World Evidence

From production access logs (GPTBot crawling a CakePHP site):

74.7.243.239 - - [20/Nov/2025:13:23:46 +0000] "GET /login?redirect=%2Flogin%3Fredirect%3D%252Flogin%253Fredirect%253D%25252Flogin%25253Fredirect%25253D%2525252Flogin%2525253Fredirect%2525253D%252525252Flogin%252525253Fredirect%252525253D%25252525252Fcommunity%25252525252Fstories..." 200 8802 "-" "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.3; +https://openai.com/gptbot)"

This shows 6-7 levels of nested redirects being actively exploited by AI crawlers.

Proposed Solution

Add configurable redirect loop protection to AuthenticationService with opt-in backward compatibility:

Option 1: Configuration-based (Recommended)

// config/app.php or bootstrap
$service->setConfig([
    'queryParam' => 'redirect',
    'redirectValidation' => [
        'enabled' => true,  // opt-in for BC
        'maxDepth' => 1,    // max allowed "redirect=" occurrences
        'maxEncodingLevels' => 1,  // max allowed %25XX patterns
        'maxLength' => 2000,  // max URL length
        'blockedPatterns' => [  // regex patterns to reject
            '#/login#i',
            '#/logout#i',
        ],
    ],
]);

Option 2: Method-based validation

Add a new protected method that can be overridden:

protected function validateRedirect(string $redirect): ?string
{
    if (!$this->getConfig('redirectValidation.enabled')) {
        return $redirect;  // BC: disabled by default
    }

    // Validation logic here
    $maxDepth = $this->getConfig('redirectValidation.maxDepth', 1);
    $decodedUrl = urldecode($redirect);

    if (substr_count($decodedUrl, 'redirect=') > $maxDepth) {
        $this->log('Redirect loop detected: ' . $redirect, 'warning');
        return null;  // Invalid, fallback to default
    }

    // More validation...

    return $redirect;
}

Then call this from getLoginRedirect():

public function getLoginRedirect(ServerRequestInterface $request): ?string
{
    $redirect = // ... existing parsing logic ...

    return $this->validateRedirect($redirect);
}

Validation Checks (suggested)

  1. Redirect depth check: Count occurrences of redirect= in decoded URL
  2. Encoding level check: Count occurrences of %25 (percent-encoding of %)
  3. Auth page loop check: Reject redirects to /login, /logout, /register, etc.
  4. URL length check: Reject excessively long URLs (DOS prevention)
  5. Pattern blocklist: Allow custom regex patterns to block

Backward Compatibility

  • Default: Validation disabled by default to maintain BC
  • Opt-in: Users enable via config: 'redirectValidation' => ['enabled' => true]
  • CakePHP 6+: Consider enabling by default with deprecation notice in 5.x

Benefits

  • ✅ Prevents redirect loop attacks
  • ✅ Reduces server resource waste
  • ✅ Protects against bot abuse
  • ✅ Improves security posture
  • ✅ Fully backward compatible
  • ✅ Configurable and extensible

Alternative: Framework-level middleware

If this doesn't fit the authentication plugin's scope, consider adding it to:

  • cakephp/cakephp core as middleware
  • cakephp/authorization plugin's redirect handlers

References

Implementation offer

I'm happy to submit a PR with tests if this proposal is accepted. Our production implementation (which we can contribute) includes:

  1. SafeAuthenticationService extending AuthenticationService
  2. SafeRedirectHandler extending authorization redirect handlers
  3. Comprehensive validation with configurable limits
  4. Logging of blocked attempts
  5. Full test coverage

Environment:

  • CakePHP: 5.2+
  • Authentication plugin: 3.x
  • PHP: 8.3+

Let me know if you'd like me to proceed with a PR!

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