Skip to content

Commit 7cb9fa3

Browse files
committed
Fix bug in lock code
1 parent 0204878 commit 7cb9fa3

3 files changed

Lines changed: 34 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
44
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
55

6+
## [2.4.1] - 2020-05-18
7+
8+
### Fixed
9+
10+
Fix a security issue where a user could brute-force a password based on
11+
differing responses that are returned from the site when the incorrect password
12+
is entered versus the correct password.
13+
14+
This comes with a slight change in behavior to minimize differences between the
15+
code paths of a correct vs incorrect password: The "attempt" time is always
16+
bumped in the DB no matter if it was the right or wrong password when being
17+
rejected for locking.
18+
619
## [2.4.0] - 2020-02-07
720

821
### Added

lock/lock.go

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,7 @@ func (l *Lock) Init(ab *authboss.Authboss) error {
3939

4040
// BeforeAuth ensures the account is not locked.
4141
func (l *Lock) BeforeAuth(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
42-
user, err := l.Authboss.CurrentUser(r)
43-
if err != nil {
44-
return false, err
45-
}
46-
47-
lu := authboss.MustBeLockable(user)
48-
if !IsLocked(lu) {
49-
return false, nil
50-
}
51-
52-
ro := authboss.RedirectOptions{
53-
Code: http.StatusTemporaryRedirect,
54-
Failure: "Your account is locked. Please contact the administrator.",
55-
RedirectPath: l.Authboss.Config.Paths.LockNotOK,
56-
}
57-
return true, l.Authboss.Config.Core.Redirector.Redirect(w, r, ro)
42+
return l.updateLockedState(w, r, true)
5843
}
5944

6045
// AfterAuthSuccess resets the attempt number field.
@@ -74,35 +59,41 @@ func (l *Lock) AfterAuthSuccess(w http.ResponseWriter, r *http.Request, handled
7459
// AfterAuthFail adjusts the attempt number and time negatively
7560
// and locks the user if they're beyond limits.
7661
func (l *Lock) AfterAuthFail(w http.ResponseWriter, r *http.Request, handled bool) (bool, error) {
62+
return l.updateLockedState(w, r, false)
63+
}
64+
65+
// updateLockedState exists to minimize any differences between a success and
66+
// a failure path in the case where a correct/incorrect password is entered
67+
func (l *Lock) updateLockedState(w http.ResponseWriter, r *http.Request, wasCorrectPassword bool) (bool, error) {
7768
user, err := l.Authboss.CurrentUser(r)
7869
if err != nil {
7970
return false, err
8071
}
8172

73+
// Fetch things
8274
lu := authboss.MustBeLockable(user)
8375
last := lu.GetLastAttempt()
8476
attempts := lu.GetAttemptCount()
8577
attempts++
8678

87-
nowLocked := false
79+
if !wasCorrectPassword {
80+
if time.Now().UTC().Sub(last) <= l.Modules.LockWindow {
81+
if attempts >= l.Modules.LockAfter {
82+
lu.PutLocked(time.Now().UTC().Add(l.Modules.LockDuration))
83+
}
8884

89-
if time.Now().UTC().Sub(last) <= l.Modules.LockWindow {
90-
if attempts >= l.Modules.LockAfter {
91-
lu.PutLocked(time.Now().UTC().Add(l.Modules.LockDuration))
92-
nowLocked = true
85+
lu.PutAttemptCount(attempts)
86+
} else {
87+
lu.PutAttemptCount(1)
9388
}
94-
95-
lu.PutAttemptCount(attempts)
96-
} else {
97-
lu.PutAttemptCount(1)
9889
}
9990
lu.PutLastAttempt(time.Now().UTC())
10091

10192
if err := l.Authboss.Config.Storage.Server.Save(r.Context(), lu); err != nil {
10293
return false, err
10394
}
10495

105-
if !nowLocked {
96+
if !IsLocked(lu) {
10697
return false, nil
10798
}
10899

lock/lock_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@ func TestBeforeAuthAllow(t *testing.T) {
7272
harness := testSetup()
7373

7474
user := &mocks.User{
75+
Email: "test@test.com",
7576
Locked: time.Time{},
7677
}
78+
harness.storer.Users["test@test.com"] = user
7779

7880
r := mocks.Request("GET")
7981
r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))
@@ -94,8 +96,10 @@ func TestBeforeAuthDisallow(t *testing.T) {
9496
harness := testSetup()
9597

9698
user := &mocks.User{
99+
Email: "test@test.com",
97100
Locked: time.Now().UTC().Add(time.Hour),
98101
}
102+
harness.storer.Users["test@test.com"] = user
99103

100104
r := mocks.Request("GET")
101105
r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user))

0 commit comments

Comments
 (0)