-
Notifications
You must be signed in to change notification settings - Fork 43
Fix information disclosure when lockout enabled #241
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,7 @@ | |
| public class DefaultUserController extends UserController { | ||
| public static final String VACUUM_LOCK_PERSON_STATEMENT_ID = "User.vacuumPersonTable"; | ||
| public static final String VACUUM_LOCK_PREFERENCES_STATEMENT_ID = "User.vacuumPersonPreferencesTable"; | ||
| private static final String INCORRECT_CREDENTIALS_MESSAGE = "Incorrect username or password."; | ||
|
|
||
| private Logger logger = LogManager.getLogger(this.getClass()); | ||
| private ExtensionController extensionController = null; | ||
|
|
@@ -292,6 +293,7 @@ public LoginStatus authorizeUser(String username, String plainPassword, String s | |
| boolean authorized = false; | ||
| Credentials credentials = null; | ||
| LoginRequirementsChecker loginRequirementsChecker = null; | ||
| PasswordRequirements passwordRequirements = ControllerFactory.getFactory().createConfigurationController().getPasswordRequirements(); | ||
|
|
||
| // Retrieve the matching User | ||
| User validUser = getUser(null, username); | ||
|
|
@@ -300,7 +302,11 @@ public LoginStatus authorizeUser(String username, String plainPassword, String s | |
| Digester digester = ControllerFactory.getFactory().createConfigurationController().getDigester(); | ||
| loginRequirementsChecker = new LoginRequirementsChecker(validUser); | ||
| if (loginRequirementsChecker.isUserLockedOut()) { | ||
| return new LoginStatus(LoginStatus.Status.FAIL_LOCKED_OUT, "User account \"" + username + "\" has been locked. You may attempt to login again in " + loginRequirementsChecker.getPrintableStrikeTimeRemaining() + "."); | ||
| if (passwordRequirements.getAllowUsernameEnumeration()) { | ||
| return new LoginStatus(LoginStatus.Status.FAIL_LOCKED_OUT, "User account \"" + username + "\" has been locked. You may attempt to login again in " + loginRequirementsChecker.getPrintableStrikeTimeRemaining() + "."); | ||
| } else { | ||
| return new LoginStatus(LoginStatus.Status.FAIL, INCORRECT_CREDENTIALS_MESSAGE); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Are timing attacks possible? Since the goal of this PR is to prevent enumeration, does this early return (skipping the db credential lookup and digester match) open up the possibility of timing-based attacks? It is understood that you did not create this situation.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re: Timing attacks, absolutely an issue. I recommend a different issue and PR. |
||
| } | ||
| } | ||
|
|
||
| loginRequirementsChecker.resetExpiredStrikes(); | ||
|
|
@@ -320,7 +326,6 @@ public LoginStatus authorizeUser(String username, String plainPassword, String s | |
| } | ||
| } | ||
|
|
||
| PasswordRequirements passwordRequirements = ControllerFactory.getFactory().createConfigurationController().getPasswordRequirements(); | ||
| LoginStatus loginStatus = null; | ||
|
|
||
| if (authorized) { | ||
|
|
@@ -383,12 +388,12 @@ public LoginStatus authorizeUser(String username, String plainPassword, String s | |
| } | ||
| } else { | ||
| LoginStatus.Status status = LoginStatus.Status.FAIL; | ||
| String failMessage = "Incorrect username or password."; | ||
| String failMessage = INCORRECT_CREDENTIALS_MESSAGE; | ||
|
|
||
| if (loginRequirementsChecker != null) { | ||
| loginRequirementsChecker.incrementStrikes(); | ||
|
|
||
| if (loginRequirementsChecker.isLockoutEnabled()) { | ||
| if (loginRequirementsChecker.isLockoutEnabled() && passwordRequirements.getAllowUsernameEnumeration()) { | ||
| if (loginRequirementsChecker.isUserLockedOut()) { | ||
| status = LoginStatus.Status.FAIL_LOCKED_OUT; | ||
| failMessage += " User account \"" + username + "\" has been locked. You may attempt to login again in " + loginRequirementsChecker.getPrintableStrikeTimeRemaining() + "."; | ||
|
|
||
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 is what I meant by naming the property with its function and including a warning about a potential consequence of changing the value rather than naming the property after the potential consequence and leaving people to wonder about what it actually does.
I don't think we need to worry about not being able to put the warning on setting the values via environment variables, because the only way people would discover that the property exists in the first place is by looking at the mirth.properties file or reading external documentation about the properties, both of which would include the warning.
If you still want to keep the default value of false, then I think
allowdetailedautherrorsis already enough to give users pause. Using "allow" rather than another word like "show" already implies it is being intentionally withheld. Combined with the warning, it should be sufficiently scary.If another aspect of preventing user enumeration requires an option in mirth.properties, rather than being always implemented, then I don't think it should share functionality with this property, but should be its own setting, also with an accurate description of what it is doing that a user might be concerned with besides preventing user enumeration. The desire to be scary can't mask the true functionality of the setting.
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.
I have no further edits planned to the name. If this is blocking, feel free to take ownership of the PR.