mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2026-10-28 14:08:45 -04:00
Make the login challenge nonce one-time (CWE-294) (#9334)
* auth: make the login challenge nonce one-time The per-login challenge nonce stored in the session was never consumed or cleared, so a captured challenge stayed replayable and survived logout (CWE-294). The client (p/scripts/extra.js) already fetches a fresh nonce for every attempt, so consuming it does not affect legitimate retries. Consume the nonce (unset it) as soon as it is read for verification in form login, reauthentication, password change and account self-deletion, and clear it in FreshRSS_Auth::removeAccess so logout invalidates any outstanding challenge. * auth: don't clear the nonce in removeAccess (breaks login) removeAccess() runs on every unauthenticated request via FreshRSS_Auth::init() -- including the login POST itself, before formLoginAction reads the nonce -- so clearing it there rejected every login with 'Invalid session'. Thanks @jamalkamaladdin for the measured catch. The one-time guarantee is preserved by consuming the nonce when it is read for verification (login/reauth/password change/self-deletion); logout no longer needs a separate clear.
This commit is contained in:
2 files changed
+4
No files matched your search
@@ -118,6 +118,7 @@ class FreshRSS_auth_Controller extends FreshRSS_ActionController {
|
||||
|
||||
if ($isPOST) {
|
||||
$nonce = Minz_Session::paramString('nonce');
|
||||
Minz_Session::_param('nonce', false); // One-time: consume the challenge so it cannot be replayed.
|
||||
$username = Minz_Request::paramString('username');
|
||||
$challenge = Minz_Request::paramString('challenge');
|
||||
$ip_address = Minz_Request::connectionRemoteAddress();
|
||||
@@ -215,6 +216,7 @@ class FreshRSS_auth_Controller extends FreshRSS_ActionController {
|
||||
if (Minz_Request::isPost()) {
|
||||
$username = Minz_User::name() ?? '';
|
||||
$nonce = Minz_Session::paramString('nonce');
|
||||
Minz_Session::_param('nonce', false); // One-time: consume the challenge so it cannot be replayed.
|
||||
$challenge = Minz_Request::paramString('challenge');
|
||||
if (!FreshRSS_FormAuth::checkCredentials(
|
||||
$username, FreshRSS_Context::userConf()->passwordHash, $nonce, $challenge
|
||||
|
||||
@@ -174,6 +174,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
|
||||
if ($challenge !== '') {
|
||||
$username = Minz_User::name();
|
||||
$nonce = Minz_Session::paramString('nonce');
|
||||
Minz_Session::_param('nonce', false); // One-time: consume the challenge so it cannot be replayed.
|
||||
|
||||
$newPasswordPlain = Minz_Request::paramString('newPasswordPlain', plaintext: true);
|
||||
$confirmPasswordPlain = Minz_Request::paramString('confirmPasswordPlain', plaintext: true);
|
||||
@@ -701,6 +702,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
|
||||
if ($self_deletion) {
|
||||
// We check the password if it’s a self-destruction
|
||||
$nonce = Minz_Session::paramString('nonce');
|
||||
Minz_Session::_param('nonce', false); // One-time: consume the challenge so it cannot be replayed.
|
||||
$challenge = Minz_Request::paramString('challenge');
|
||||
|
||||
$ok &= FreshRSS_FormAuth::checkCredentials(
|
||||
|
||||
Reference in new issue
Block a user