-
Notifications
You must be signed in to change notification settings - Fork 110
Add optional redirect loop protection to AuthenticationService #752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add optional redirect loop protection to AuthenticationService #752
Conversation
- Add configurable redirect validation to prevent redirect loop attacks - Checks for nested redirects, deep encoding, blocked patterns, and URL length - Disabled by default for backward compatibility (opt-in) - Add comprehensive test coverage (8 new tests) - Add detailed documentation with security considerations - Fixes issue cakephp#751 Real-world evidence shows bots creating 6-7 levels of nested redirects, wasting server resources and potentially enabling security exploits. Configuration example: 'redirectValidation' => [ 'enabled' => true, 'maxDepth' => 1, 'maxEncodingLevels' => 1, 'maxLength' => 2000, 'blockedPatterns' => ['#/login#i', '#/logout#i'], ]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds optional redirect validation to AuthenticationService::getLoginRedirect() to prevent redirect loop attacks observed in production. The feature is disabled by default for backward compatibility and can be enabled via the redirectValidation configuration option.
- Adds
validateRedirect()protected method with four validation checks (depth, encoding, length, patterns) - Adds comprehensive test coverage with 8 new test cases
- Provides detailed documentation with security considerations and usage examples
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/AuthenticationService.php | Implements the validateRedirect() method and adds redirectValidation configuration with defaults |
| tests/TestCase/AuthenticationServiceTest.php | Adds 8 comprehensive test cases covering disabled validation, nested redirects, encoding levels, blocked patterns, URL length, custom patterns, and query parameters |
| docs/en/redirect-validation.rst | New documentation page explaining the feature, configuration options, validation logic, and security considerations |
| docs/en/index.rst | Adds link to the new redirect-validation documentation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Fix comparison operators: Use >= instead of > for maxDepth and maxEncodingLevels This correctly blocks URLs when they meet or exceed the threshold - Replace empty() with ! for enabled check (cleaner intent) - All tests still pass (312 tests, 926 assertions) Addresses feedback from @ADmad and @Copilot in PR cakephp#752
|
Thanks for the quick review @ADmad and @copilot! I've addressed all the feedback in commit 1ae39c2: ✅ Fixed comparison operators (>= instead of >)
✅ Replaced empty() with !
All tests still pass (312 tests, 926 assertions). Ready for another review! |
markstory
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole thing seems quite complex for the problem it is trying to solve.
Address maintainer feedback from @markstory and @ADmad: - Remove blockedPatterns configuration option - Remove pattern-based validation logic - Update documentation to show custom pattern validation in subclass - Remove 2 pattern-based tests (testGetLoginRedirectValidationBlockedPatterns, testGetLoginRedirectValidationCustomPatterns) Result: Simpler, focused implementation covering the core security issues: - Nested redirect detection - Deep encoding detection - URL length limits Custom pattern blocking can still be achieved by overriding validateRedirect(). All tests pass: 310 tests, 920 assertions Code style checks pass
|
Thanks for the feedback @markstory and @ADmad! I've simplified the implementation in commit 591ec19: Removed❌ blockedPatterns configuration - Removed entirely What Remains (Core Security Features)✅ Nested redirect detection - Blocks Custom Pattern BlockingApplications that need pattern-based blocking (e.g., blocking redirects to Result: Much simpler implementation focused on the core redirect loop vulnerability.
Ready for another review! |
|
Gonna test it on the server before we tag any release. |
|
So far so good. Only one more case where it can run into loops. when working together with authorization - and UnauthorizedHandler. This seems to solve it, but its not really too nice. // SafeRedirectHandler extends plugin one
public function handle(Exception $exception, ServerRequestInterface $request, array $options = []): ResponseInterface {
// If user is already logged in but not authorized, redirect to fallback
// instead of login page (which would cause a loop)
$identity = $request->getAttribute('identity');
if ($identity) {
$message = $options['unauthorizedMessage'] ?? __('You are not authorized to access that location.');
if ($message) {
/** @var \Cake\Http\ServerRequest $request */
$session = $request->getSession();
$session->write('Flash.flash', [
[
'message' => $message,
'key' => 'flash',
'element' => 'flash/error',
'params' => [],
],
]);
}
$fallbackUrl = Router::url(['prefix' => false, 'plugin' => false, 'controller' => 'Account', 'action' => 'index']);
return (new Response())
->withHeader('Location', $fallbackUrl)
->withStatus(302);
} |
Summary
This PR adds optional redirect validation to
AuthenticationService::getLoginRedirect()to prevent redirect loop attacks that have been observed in production environments.Problem
The current implementation validates that redirect URLs are relative (not external) but does NOT check for:
Real-world evidence: Production logs show bots (especially GPTBot) creating 6-7 levels of nested redirects, wasting server resources and potentially enabling security exploits.
Solution
Adds configurable
redirectValidationoption with:validateRedirect()method can be overriddenConfiguration Example
Changes
validateRedirect()protected method toAuthenticationServiceredirectValidationconfiguration with sensible defaultsgetLoginRedirect()to call validationdocs/en/redirect-validation.rst)Validation Logic
redirect=occurrences in decoded URL%25(percent-encoding of %)If validation fails, returns
nullinstead of invalid URL.Security Impact
Before:
/login?redirect=/login?redirect=/login...%252F/logoutcausing loopsAfter (when enabled):
References
Testing
Checklist
Ready for review! Happy to make any adjustments based on feedback.