High severity7.5NVD Advisory· Published Jul 9, 2024· Updated Apr 15, 2026
CVE-2024-36676
CVE-2024-36676
Description
Incorrect access control in BookStack before v24.05.1 allows attackers to confirm existing system users and perform targeted notification email DoS via public facing forms.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
ssddanbrown/bookstackPackagist | < 24.05.1 | 24.05.1 |
Patches
2b5375114d35269af9e0dbdefRoutes: Added throttling to a range of auth-related endpoints
7 files changed · +109 −12
app/Access/Controllers/ForgotPasswordController.php+5 −0 modified@@ -6,6 +6,7 @@ use BookStack\Http\Controller; use Illuminate\Http\Request; use Illuminate\Support\Facades\Password; +use Illuminate\Support\Sleep; class ForgotPasswordController extends Controller { @@ -32,6 +33,10 @@ public function sendResetLinkEmail(Request $request) 'email' => ['required', 'email'], ]); + // Add random pause to the response to help avoid time-base sniffing + // of valid resets via slower email send handling. + Sleep::for(random_int(1000, 3000))->milliseconds(); + // We will send the password reset link to this user. Once we have attempted // to send the link, we will examine the response then see the message we // need to show to the user. Finally, we'll send out a proper response.
app/Access/Controllers/ResetPasswordController.php+3 −6 modified@@ -15,14 +15,11 @@ class ResetPasswordController extends Controller { - protected LoginService $loginService; - - public function __construct(LoginService $loginService) - { + public function __construct( + protected LoginService $loginService + ) { $this->middleware('guest'); $this->middleware('guard:standard'); - - $this->loginService = $loginService; } /**
app/App/Providers/RouteServiceProvider.php+4 −0 modified@@ -81,5 +81,9 @@ protected function configureRateLimiting(): void RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); }); + + RateLimiter::for('public', function (Request $request) { + return Limit::perMinute(10)->by($request->ip()); + }); } }
routes/web.php+6 −6 modified@@ -317,8 +317,8 @@ Route::get('/register/confirm/awaiting', [AccessControllers\ConfirmEmailController::class, 'showAwaiting']); Route::post('/register/confirm/resend', [AccessControllers\ConfirmEmailController::class, 'resend']); Route::get('/register/confirm/{token}', [AccessControllers\ConfirmEmailController::class, 'showAcceptForm']); -Route::post('/register/confirm/accept', [AccessControllers\ConfirmEmailController::class, 'confirm']); -Route::post('/register', [AccessControllers\RegisterController::class, 'postRegister']); +Route::post('/register/confirm/accept', [AccessControllers\ConfirmEmailController::class, 'confirm'])->middleware('throttle:public'); +Route::post('/register', [AccessControllers\RegisterController::class, 'postRegister'])->middleware('throttle:public'); // SAML routes Route::post('/saml2/login', [AccessControllers\Saml2Controller::class, 'login']); @@ -338,16 +338,16 @@ Route::post('/oidc/logout', [AccessControllers\OidcController::class, 'logout']); // User invitation routes -Route::get('/register/invite/{token}', [AccessControllers\UserInviteController::class, 'showSetPassword']); -Route::post('/register/invite/{token}', [AccessControllers\UserInviteController::class, 'setPassword']); +Route::get('/register/invite/{token}', [AccessControllers\UserInviteController::class, 'showSetPassword'])->middleware('throttle:public'); +Route::post('/register/invite/{token}', [AccessControllers\UserInviteController::class, 'setPassword'])->middleware('throttle:public'); // Password reset link request routes Route::get('/password/email', [AccessControllers\ForgotPasswordController::class, 'showLinkRequestForm']); -Route::post('/password/email', [AccessControllers\ForgotPasswordController::class, 'sendResetLinkEmail']); +Route::post('/password/email', [AccessControllers\ForgotPasswordController::class, 'sendResetLinkEmail'])->middleware('throttle:public'); // Password reset routes Route::get('/password/reset/{token}', [AccessControllers\ResetPasswordController::class, 'showResetForm']); -Route::post('/password/reset', [AccessControllers\ResetPasswordController::class, 'reset']); +Route::post('/password/reset', [AccessControllers\ResetPasswordController::class, 'reset'])->middleware('throttle:public'); // Metadata routes Route::view('/help/wysiwyg', 'help.wysiwyg');
tests/Auth/RegistrationTest.php+29 −0 modified@@ -203,4 +203,33 @@ public function test_registration_simple_honeypot_active() $resp = $this->followRedirects($resp); $this->withHtml($resp)->assertElementExists('form input[name="username"].text-neg'); } + + public function test_registration_endpoint_throttled() + { + $this->setSettings(['registration-enabled' => 'true']); + + for ($i = 0; $i < 11; $i++) { + $response = $this->post('/register/', [ + 'name' => "Barry{$i}", + 'email' => "barry{$i}@example.com", + 'password' => "barryIsTheBest{$i}", + ]); + auth()->logout(); + } + + $response->assertStatus(429); + } + + public function test_registration_confirmation_throttled() + { + $this->setSettings(['registration-enabled' => 'true']); + + for ($i = 0; $i < 11; $i++) { + $response = $this->post('/register/confirm/accept', [ + 'token' => "token{$i}", + ]); + } + + $response->assertStatus(429); + } }
tests/Auth/ResetPasswordTest.php+42 −0 modified@@ -4,11 +4,19 @@ use BookStack\Access\Notifications\ResetPasswordNotification; use BookStack\Users\Models\User; +use Carbon\CarbonInterval; use Illuminate\Support\Facades\Notification; +use Illuminate\Support\Sleep; use Tests\TestCase; class ResetPasswordTest extends TestCase { + protected function setUp(): void + { + parent::setUp(); + Sleep::fake(); + } + public function test_reset_flow() { Notification::fake(); @@ -75,6 +83,17 @@ public function test_reset_flow_shows_success_message_even_if_wrong_password_to_ ->assertSee('The password reset token is invalid for this email address.'); } + public function test_reset_request_with_not_found_user_still_has_delay() + { + $this->followingRedirects()->post('/password/email', [ + 'email' => 'barrynotfoundrandomuser@example.com', + ]); + + Sleep::assertSlept(function (CarbonInterval $duration): bool { + return $duration->totalMilliseconds > 999; + }, 1); + } + public function test_reset_page_shows_sign_links() { $this->setSettings(['registration-enabled' => 'true']); @@ -98,4 +117,27 @@ public function test_reset_request_is_throttled() Notification::assertSentTimes(ResetPasswordNotification::class, 1); $resp->assertSee('A password reset link will be sent to ' . $editor->email . ' if that email address is found in the system.'); } + + public function test_reset_request_with_not_found_user_is_throttled() + { + for ($i = 0; $i < 11; $i++) { + $response = $this->post('/password/email', [ + 'email' => 'barrynotfoundrandomuser@example.com', + ]); + } + + $response->assertStatus(429); + } + + public function test_reset_call_is_throttled() + { + for ($i = 0; $i < 11; $i++) { + $response = $this->post('/password/reset', [ + 'email' => "arandomuser{$i}@example.com", + 'token' => "randomtoken{$i}", + ]); + } + + $response->assertStatus(429); + } }
tests/Auth/UserInviteTest.php+20 −0 modified@@ -137,4 +137,24 @@ public function test_token_expires_after_two_weeks() $setPasswordPageResp->assertRedirect('/password/email'); $setPasswordPageResp->assertSessionHas('error', 'This invitation link has expired. You can instead try to reset your account password.'); } + + public function test_set_password_view_is_throttled() + { + for ($i = 0; $i < 11; $i++) { + $response = $this->get("/register/invite/tokenhere{$i}"); + } + + $response->assertStatus(429); + } + + public function test_set_password_post_is_throttled() + { + for ($i = 0; $i < 11; $i++) { + $response = $this->post("/register/invite/tokenhere{$i}", [ + 'password' => 'my test password', + ]); + } + + $response->assertStatus(429); + } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-pj36-fcrg-327jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-36676ghsaADVISORY
- github.com/BookStackApp/BookStack/commit/69af9e0dbdefd8c6c951e8afbe2bba141d454bebghsaWEB
- github.com/BookStackApp/BookStack/issues/4993nvdWEB
- github.com/BookStackApp/BookStack/releases/tag/v24.05.1nvdWEB
- www.bookstackapp.com/blog/bookstack-release-v24-05-1ghsaWEB
- www.bookstackapp.com/blog/bookstack-release-v24-05-1/nvd
News mentions
0No linked articles in our index yet.