Rodauth 2.47.0 Released #514
jeremyevans
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Rodauth 2.47.0 has been released!
Security Fixes to Address Reported Vulnerabilities
There were a large number of reported vulnerabilities that are fixed
in this version. While none are as critical as the webauthn_login
vulnerability fixed in 2.46.0, this release does fix some serious
vulnerabilities. The vulnerabilities are separated by type:
Thank you to Joshua Rogers of AISLE Research
for privately reporting these vulnerabilities.
Timeline (all times UTC):
security findings.
started.
findings and initial analysis.
additional security findings.
findings and initial analysis of second report.
begins on the 2.47.0 release.
Security Issues Not Requiring Race Conditions or Privileged Information
The return to path configuration methods would previously allow
redirecting to remote URLs, most simply by making a request to
a path that started with //. The related configuration methods
(both of which return false by default) are:
The confirm_password feature implicit works by returning to
requested locations, so it was also vulnerable.
All of these now check that the return to path starts with /
but not //, ensuring that it should be considered a local
path and not a remote URL.
Additionally, only the login_return_to_requested_location? feature
implemented checks for request method (only allow if original
request used GET) and path size (only allow if under 2K bytes).
These checks have been moved from the login feature to the base
feature, and are now applied to all three cases. Additionally,
two configuration methods are added to configure this support:
return to.
valid. By default it does the / but not // check and the path
size check.
This change can break applications that are designed to redirect
to remote URLs, such as applications that operate as an
authentication gateway for other domains. Such applications should
use the valid_return_to_path? configuration method appropriately
for their needs.
The json feature previously used a content type regexp that was
not anchored to the beginning of the content-type header. This
allowed using application/json as a type parameter to another type
that was allowed without a CORS preflight request. As Rodauth allows
JSON requests without CSRF by default (thinking that they would
be require a CORS preflight request and therefore the CSRF is
unnecessary), this resulted in Rodauth being open to CSRF
attacks in some cases if the json feature was enabled, the
check_csrf was not used explicitly to require CSRF checks,
and the json_request_content_type_regexp was not used
explicitly with a properly anchoring regexp.
Whether the application is actually vulnerable to CSRF depends
on the session implementation. With Roda's sessions plugin,
and other session implementations that use SameSite=Lax, CSRF
attacks are limited to same-site-cross-origin attacks. For
endpoints that don't require a logged in session, such as the
login endpoint, the SameSite protection doesn't apply, and this
could be used to force login into an attacker-controlled account.
This issue is fixed by anchoring the default
json_request_content_type_regexp with \A, preventing the ability
to have application/json as a type parameter be considered a json
request. Cross origin requests that Rodauth would treat as JSON
requests now require a CORS preflight request.
Rodauth's documentation was updated to point out potential session
fixation problems when using session implementations based on
Rack::Session::Abstract::Persisted. If you are using a session
implementation based on Rack::Session::Abstract::Persisted, and
cannot switch to another approach, you should use the clear_session
configuration method to force a new session:
Security Issues Not Requiring Race Conditions
The jwt feature now removes the Authorization response header
if the request is not a POST request or it doesn't accept
JSON. When using the jwt_refresh feature, this fixes the
ability to get a new access token by submitting a non-POST
request to the jwt_refresh route.
Additionally, the jwt_refresh feature no longer allows decoding
expired JWTs for non-POST requests. Previously, decoding of
expired JWTs was allowed during requests to the jwt_refresh path
regardless of the request method used.
Previously, this issue could be abused to continually get new
access tokens if an attacker already had a expired access token
but no refresh token.
The otp feature now prevents otp code reuse during the drift
window. Previously, it was possible to replay an otp code during
the drift window. In order for this to be exploitable, the
attacker must have access to an already used but otherwise valid
OTP code. While not technically a race condition, the window in
which this could be exploited would be limited to 30 seconds by
default. Note that preventing reuse during the drift window
requires rotp 4 (released in March 2018).
Adding a webauthn authenticator after using the webauthn login
feature no longer marks the session as being two factor
authenticated. In order for this to be exploitable, the
application would need to restrict some endpoints to only sessions
with two factor authentication. If a user wanted to exploit this,
they could easily setup an actual separate factor, access what
they want, and then remove the separate factor, so this bug in
general allowed security policy violations but not actual
security vulnerabilities.
Related to this bug fix, attempts to update a session to being
two factor authenticated by using an authentication type that
was used as the original factor will now raise a Rodauth::Error.
A jwt_refresh token created using the old hmac secret instead of
the current hmac secret was previously not deleted from the
database during logout, allowing it to be used until it expired
for creating new refresh tokens. This is only exploitable if the
attacker gains access to a refresh token created using the old
hmac secret after it was used during logout, and only then until
the refresh token expires.
When using the password_pepper feature and using the
password_maximum_bytes configuration method to set a maximum size
for the password, the length of the password pepper was not
included in the calculation, allowing the actual password given to
the hash algorithm to be longer than password_maximum_bytes. When
using the bcrypt hash algorithm with a password_maximum_bytes of
72 (the bcyrpt limit), this could allow long bcrypt passwords to
be set in such as way as that some or all of the pepper bytes are
ignored. In general, a 72 byte bcrypt password is probably secure
enough the pepper adds no significant additional security, but it
is possible there are cases where the pepper would be needed for
security, and the user should provide a shorter bcrypt password
to accomodate the pepper. In order to exploit this feature (use
a password that would be truncated by bcrypt), an attacker must
have access to either change the password or login with the
password.
Note that Rodauth does not set password_maximum_bytes by default,
but in Rodauth 3, password_maximum_bytes will default to 72 if
bcrypt is used as the password hash algorithm.
Non-json requests to the POST endpoints for the email token
features will no longer consider a token submitted as a parameter.
These features are:
These features all have the same expected non-JSON workflow:
session, redirects to same path with no parameter
action
In these workflows, the token should never been provided as a
parameter to the POST request. For non-json requests, the POST
endpoints will no longer consider tokens submitted as a parameter,
protecting them somewhat against cases where the application would
be vulnerable to CSRF attacks.
In order to exploit this issue, an attacker must have access to a
current valid token, and the application must be vulnerable to CSRF.
It's possible this change could break custom workflows. The
allow_param_fallback_for_session_param? configuration method
has been added, which can be set to true if you need to support
tokens as parameters to POST endpoints in non-JSON workflows.
When using the webauthn_login feature and a passwordless webauthn
login, adding or removing a webauthn authenticator is no longer
allowed if the account is setup for two factor authentication but
the session is not two factor authenticated. Previously, you could
add or remove a webauthn authenticator in this case, but that goes
against Rodauth's general principle of requiring two factor
authentication for account changes if the account is setup for two
factor authentication. In order to exploit this, an attacker would
need access to a valid webauthn authenticator or a valid session
for a passwordless account that has been setup for two factor
authentication.
Race Conditions Not Requiring Privileged Information
The otp and sms_codes features both allowed concurrent
authentication requests for the same account to bypass the limit
on allowed failures, allowing them more guesses than would otherwise
be allowed. The features now use transactions and FOR UPDATE queries
to enforce specified limits.
In general, while this can increase the risk of an account
compromise, the maximum number of concurrent requests is still
low enough to make a blind guess approach successful. Trying
to exploit this is also very noisy, and an unsuccesful attack
will result in otp or sms lockout. Additionally, attackers can
only attempt this attack after they have already partially
compromised the acccount (such as getting single factor access
but not multifactor access). This is only a feasible exploit
approach if you have limited the number of potential values to
more than the number the allowed failures but less than number
of concurrent failing requests before triggering lockout.
Race Conditions Requiring Privileged Information
When setting a new password, handle cases where there was a
concurrent password change by reporting an error, instead of
silently succeeding. This issue is only exploitable if an
attacker has the ability to change the password.
set_password now raises a Rodauth::SetPasswordFailure exception
in this case, and callers now handle this exception:
again.
(during hash cost/algorithm change): handled as an invalid
password, since the password has changed.
Note that the change password security requires that the user
provide the existing password. If password modifications do not
require the current password provided, than the change cannot
be protected against.
All features that used email tokens were subject to race conditions
for concurrent requests using those tokens. These features are:
In order to be exploitable, an attacker must have access to the
related token, and must choose to use them during a concurrent
request by the victim.
These issues were all addressed by expanding transaction scope and
using FOR UPDATE on the query to retrieve the token to serialize the
access.
In addition to this fix, these features leaked tokens into the
session in some cases. The features have been updated to delete
the tokens appropriately from the session.
The recovery_codes feature was vulnerable to concurrent use of the
same recovery code. When removing the recovery code during the
recovery code authentication process, Rodauth now checks that a
single row was deleted, and returns false if that isn't the case.
This issue was only exploitable if an attacker had access to a
valid recovery code, and chose to authenticate with it during a
concurrent request by the victim with the same code.
The jwt_refresh feature was vulnerable to concurrent use of
the refresh token. It was also fixed by expanding transaction
scope and using FOR UPDATE. This issue was only exploitable if
an attacker had access to the same refresh token as the victim,
and chose to refresh the token during a concurrent request by
the victim.
The verify_account feature was updated to only verify the
account if the account was still in an unverified state during
the query to verify it. This could only be exploited if an
attacker had a valid verify account token, and would require the
account have a concurrent account status change. Potentially,
the previous behavior could allow a concurrent account close
during the grace period (with the verify_account_grace_period
feature) to be overridden by the account verification.
Other Security Fixes
The single_session feature now treats a session as invalid if the
session does not have a single session key, but a single session
key for the account exists in the database. This indicates that
the single_session feature was added after the session was created,
and that there was another session created afterward (for the
database to have a session). In this case, it appears there is
another, more recent session, so the current session should be
considered invalid.
The password_grace_period now prevents extending the grace period
by using the change password feature and attempting to change the
password to the current password.
Other Improvements
Cases where Rodauth could use either the account_id or the session
value have been consolidated to use an approach where the account_id
is used if there was an attempt to load an account, and the
session_value is used only if there was no attempt to load an
account. That is safer the approach of using the session_value if
the account_id is not available (even if there was an attempt to
load an account), or worse, using the session_value if available
instead of the account_id (the cause of the webauthn_login
vulnerability fixed in 2.46.0). There were two cases where the
session_value was used in preference to the account_id:
webauthn_verify_account: Unlikely webauthn_login, this doesn't
appear to have been exploitable unless you have the verify account
token, in which case you don't need an exploit to control the
account.
active_sessions: This could potentially be used to remove, clear,
or set active session records for an attacker controlled account.
It does not allow an attacker to remove, clear, or set active
session records for an account they do not control. Since users
often have the ability to remove, clear, or set their own active
session records, this does not appear to have been exploitable.
Rodauth now warns or raises if you attempt to load an account
multiple times in the same instance during a Rodauth route. Rodauth
now records the reason the account was loaded. If an account was
loaded multiple times for the same reason (e.g. both the from
session value), Rodauth warns, since this is inefficient but
unlikely to be a security issue. If an account was loaded for a
different reason than the original load, Rodauth raises a
Rodauth::Error, as this can result in a security issue due to
potential account confusion.
This found a couple of duplicate retrieval issues inside Rodauth,
which this release fixes.
If this account reloading prevention causes problems in your
application, is probably better to fix the underlying issues, but
you can use the new account_reload configuration method to allow
account reloads by having it return nil and not call super.
The security fix in 2.46.0 could result in require_authentication
failing in applications using the webauthn_login feature. The
new approach of using the account_id if an account is loaded,
instead of basing the account_id/session_value decision on the
current route, should fix this issue.
The jwt_cors feature now sets the Vary response header to Origin
so that caches will not return a response for one origin if there
is a request for different origin. As Rodauth's JSON API only
supports POST requests, it is unlikely a response would be cached,
so this is done as a best practice and not to prevent an actual
vulnerability. A jwt_cors_vary_header configuration method has been
added to use to configure the value of the Vary header.
The verify_login_change feature now disallows verification for
unverified or closed accounts. Unverified accounts should verify
themselves before changing their login, and there is no reason
to change the login for a closed account.
The README received substantial updates to explain how Rodauth
handles some security aspects, as well as recommended configuration
methods to use to improve security.
Backwards Compatibility
Rodauth now warns if you are loading a feature that should be
using a configuration method for security reasons, but the
configuration method is not used. The following configuration
methods have such warnings:
domain: By default, this uses request.host, which comes from
Rack, and is insecure by default because it tries to determine
the host from the request using potentially attacker-provided
information. In any case where you will be sending emails, not
setting domain is likely to result in a serious security issue
in the application.
hmac_secret: Rodauth has significant security benefits from
setting an hmac secret for the features that can use it but
do not require it.
already_logged_in: For backwards compatibility, Rodauth allows
already authenticated sessions to access endpoints that are
designed to be used by unauthentication sessions. This hightens
the risk of account confusion attacks. It's strongly recommended
that this configuration method be used to halt or redirect, in
order to prevent potential account confusion attacks (doing so
prevented the webauthn_login vulnerability fixed in 2.46.0).
If you are not loading a feature that uses the configuration
method, Rodauth will not emit a warning for the method. If you
want to avoid a particular warning and use the default value,
you can call the configuration method with a block containing
super(). Alternatively, to change the behavior for all of these
configuration warnings, you can use the new
missing_recommended_configuration(msg) configuration method.
In Rodauth 3, these missing recommended configuration warnings
will change to raised exceptions by default.
External features that set the @account instance variable should
switch to using the new set_account internal method. If the
feature is designed to reload accounts, it can set
@account_retrieval_type to nil before calling set_account.
Best,
Jeremy
All reactions