Skip to content

Commit a656cc0

Browse files
committed
Fix information disclosure when lockout enabled
When lockout is enabled, enhanced response error messages allow an attacker to enumerate valid usernames. Change adds new configuration option to disable username enumeration by returning the same error message for both invalid usernames and locked out accounts. The option is enabled by default. Issue: #240 Signed-off-by: Mitch Gaffigan <mitch.gaffigan@comcast.net>
1 parent 8e040c7 commit a656cc0

File tree

3 files changed

+21
-4
lines changed

3 files changed

+21
-4
lines changed

server/src/com/mirth/connect/model/PasswordRequirements.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class PasswordRequirements implements Serializable {
2929
private int gracePeriod;
3030
private int reusePeriod;
3131
private int reuseLimit;
32+
private boolean allowEnumeration;
3233

3334
public PasswordRequirements() {
3435
this.minLength = 0;
@@ -42,6 +43,7 @@ public PasswordRequirements() {
4243
this.gracePeriod = 0;
4344
this.reusePeriod = 0;
4445
this.reuseLimit = 0;
46+
this.allowEnumeration = false;
4547
}
4648

4749
public PasswordRequirements(int minLength, int minUpper, int minLower, int minNumeric, int minSpecial, int retryLimit, int lockoutPeriod, int expiration, int gracePeriod, int reusePeriod, int reuseLimit) {
@@ -145,4 +147,12 @@ public int getReuseLimit() {
145147
public void setReuseLimit(int reuseLimit) {
146148
this.reuseLimit = reuseLimit;
147149
}
150+
151+
public boolean getAllowEnumeration() {
152+
return allowEnumeration;
153+
}
154+
155+
public void setAllowEnumeration(boolean allowEnumeration) {
156+
this.allowEnumeration = allowEnumeration;
157+
}
148158
}

server/src/com/mirth/connect/server/controllers/DefaultUserController.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
public class DefaultUserController extends UserController {
4444
public static final String VACUUM_LOCK_PERSON_STATEMENT_ID = "User.vacuumPersonTable";
4545
public static final String VACUUM_LOCK_PREFERENCES_STATEMENT_ID = "User.vacuumPersonPreferencesTable";
46+
private static final String INCORRECT_CREDENTIALS_MESSAGE = "Incorrect username or password.";
4647

4748
private Logger logger = LogManager.getLogger(this.getClass());
4849
private ExtensionController extensionController = null;
@@ -292,6 +293,7 @@ public LoginStatus authorizeUser(String username, String plainPassword, String s
292293
boolean authorized = false;
293294
Credentials credentials = null;
294295
LoginRequirementsChecker loginRequirementsChecker = null;
296+
PasswordRequirements passwordRequirements = ControllerFactory.getFactory().createConfigurationController().getPasswordRequirements();
295297

296298
// Retrieve the matching User
297299
User validUser = getUser(null, username);
@@ -300,7 +302,11 @@ public LoginStatus authorizeUser(String username, String plainPassword, String s
300302
Digester digester = ControllerFactory.getFactory().createConfigurationController().getDigester();
301303
loginRequirementsChecker = new LoginRequirementsChecker(validUser);
302304
if (loginRequirementsChecker.isUserLockedOut()) {
303-
return new LoginStatus(LoginStatus.Status.FAIL_LOCKED_OUT, "User account \"" + username + "\" has been locked. You may attempt to login again in " + loginRequirementsChecker.getPrintableStrikeTimeRemaining() + ".");
305+
if (passwordRequirements.getAllowEnumeration()) {
306+
return new LoginStatus(LoginStatus.Status.FAIL_LOCKED_OUT, "User account \"" + username + "\" has been locked. You may attempt to login again in " + loginRequirementsChecker.getPrintableStrikeTimeRemaining() + ".");
307+
} else {
308+
return new LoginStatus(LoginStatus.Status.FAIL, INCORRECT_CREDENTIALS_MESSAGE);
309+
}
304310
}
305311

306312
loginRequirementsChecker.resetExpiredStrikes();
@@ -320,7 +326,6 @@ public LoginStatus authorizeUser(String username, String plainPassword, String s
320326
}
321327
}
322328

323-
PasswordRequirements passwordRequirements = ControllerFactory.getFactory().createConfigurationController().getPasswordRequirements();
324329
LoginStatus loginStatus = null;
325330

326331
if (authorized) {
@@ -383,12 +388,12 @@ public LoginStatus authorizeUser(String username, String plainPassword, String s
383388
}
384389
} else {
385390
LoginStatus.Status status = LoginStatus.Status.FAIL;
386-
String failMessage = "Incorrect username or password.";
391+
String failMessage = INCORRECT_CREDENTIALS_MESSAGE;
387392

388393
if (loginRequirementsChecker != null) {
389394
loginRequirementsChecker.incrementStrikes();
390395

391-
if (loginRequirementsChecker.isLockoutEnabled()) {
396+
if (loginRequirementsChecker.isLockoutEnabled() && passwordRequirements.getAllowEnumeration()) {
392397
if (loginRequirementsChecker.isUserLockedOut()) {
393398
status = LoginStatus.Status.FAIL_LOCKED_OUT;
394399
failMessage += " User account \"" + username + "\" has been locked. You may attempt to login again in " + loginRequirementsChecker.getPrintableStrikeTimeRemaining() + ".";

server/src/com/mirth/connect/server/util/PasswordRequirementsChecker.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public class PasswordRequirementsChecker implements Serializable {
5757
private static final String PASSWORD_LOCKOUT_PERIOD = "password.lockoutperiod";
5858
private static final String PASSWORD_REUSE_PERIOD = "password.reuseperiod";
5959
private static final String PASSWORD_REUSE_LIMIT = "password.reuselimit";
60+
private static final String PASSWORD_ALLOW_ENUMERATION = "password.allowEnumeration";
6061

6162
private static PasswordRequirementsChecker instance = null;
6263

@@ -88,6 +89,7 @@ public PasswordRequirements loadPasswordRequirements(PropertiesConfiguration sec
8889
passwordRequirements.setLockoutPeriod(securityProperties.getInt(PASSWORD_LOCKOUT_PERIOD, 0));
8990
passwordRequirements.setReusePeriod(securityProperties.getInt(PASSWORD_REUSE_PERIOD, 0));
9091
passwordRequirements.setReuseLimit(securityProperties.getInt(PASSWORD_REUSE_LIMIT, 0));
92+
passwordRequirements.setAllowEnumeration(securityProperties.getBoolean(PASSWORD_ALLOW_ENUMERATION, false));
9193

9294
return passwordRequirements;
9395
}

0 commit comments

Comments
 (0)