Skip to content

Password recovery exemption: let only recovery requests through (B1, B3) - #251

Open
codeling wants to merge 1 commit into
mainfrom
ccr-c4633bb2-v97st1
Open

codeling wants to merge 1 commit into
mainfrom
ccr-c4633bb2-v97st1

Conversation

@codeling

@codeling codeling commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

Stack 1/4 for the 2.0.0 release (#250): this PR → #252 (B2) → #253 (B5) → #254 (B4, together with codeling/com_bfstop#25)

Problem

A blocked IP is allowed through to com_users' reset and remind views, so a locked-out user can still recover their account. The exemption (isPasswordRecoveryRequest()) checked only option and view:

  • B1 - block bypass: Joomla runs the task of a request and ignores the view when there is one. So option=com_users&view=reset&task=user.login logged in from a blocked address, as many times as the attacker liked. The reset page also hands out the form token the login needs. This is the same kind of hole as the unblock token bypass fixed earlier: isUnblockRequest() already requires an empty task.
  • B3 - recovery forms blocked: the forms of those views post a task, usually without view=reset (index.php?task=reset.request, remind.remind, reset.confirm, reset.complete). So the request that actually sends the reset mail could get blocked, depending on SEF and menu items.

Change

  • With no task, only view=reset / view=remind pass.
  • With a task, only the tasks of the recovery forms pass: reset.request, reset.confirm, reset.complete, remind.remind, user.remind.
  • The new requestedTask() reads the task the way Joomla's ComponentDispatcher does: lower case, controller.method with anything after a second dot ignored, or a bare method plus the controller parameter. B2 (Login Only block mode: recognise a login however its task is spelled (B2) #252) uses it too.

Tests

BlockedRequestTest adds:

  • testPasswordRecoveryFormsCanBeSubmitted
  • testPasswordRecoveryViewIsNoPassForOtherTasks, which covers view=reset&task=user.login, task=login&controller=user, task=user.LOGIN and task[]=…
  • confirm/complete layouts in testPasswordRecoveryStaysReachable

Both new tests fail on main and pass here. The full suite passes locally on Joomla 5.4.10 with MySQL 8.4 and with PostgreSQL 16. I couldn't test end to end in a browser (form token, SEF, menu items): that is H6 in #250.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AFJFMMUpch4LGexEiXN8Nc

A blocked IP address may reach com_users' reset and remind views, but the
exemption looked at option and view only. Joomla runs the task of a request,
not its view, so option=com_users&view=reset&task=user.login logged in from
a blocked address - and the reset view hands out the form token for it.
Without a task, only the two views pass now; with one, only the tasks of
the recovery forms. Those forms post a task, usually without view=reset
(index.php?task=reset.request), so before, the request which actually
sends the reset mail could get blocked.

The task is read the way Joomla's dispatcher does: in lower case, as
controller.method (anything after a second dot ignored) or as a method with
a "controller" parameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFJFMMUpch4LGexEiXN8Nc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants