Overview
Broken Object Level Authorization (BOLA) vulnerabilities allow an attacker to access or manipulate specific resources they should not be able to, by bypassing per-object access checks. CVE-2026-33884 describes a scenario in Statamic (a Laravel and Git powered CMS) where an authenticated Control Panel user with live preview access could use a shared live preview token to reach content the token was not intended for. This is classified under CWE-863: Incorrectly Implemented Access Control. In real-world Laravel apps, similar patterns arise when token-based gates are used without enforcing object-level authorization first, enabling access to restricted resources if a token is misused or leaked. The impact ranges from exposure of private posts to viewing or manipulating sensitive content, depending on what the resource represents. The fix in Statamic versions 5.73.16 and 6.7.2 demonstrates the necessity of binding access controls to individual resources and not relying on a global token. Laravel developers should take this as a reminder to enforce policies and colocate authorization with the resource being accessed. The vulnerability class manifests in Laravel when controllers gate access with broad tokens or route parameters before performing per-resource checks.
Affected Versions
5.x prior to 5.73.16; 6.x prior to 6.7.2
Code Fix Example
Laravel API Security Remediation
// Vulnerable pattern (global token gate, no per-object authorization)
<?php
class PreviewController {
public function livePreview($entryId)
{
$token = $_GET['live_preview_token'] ?? null;
$entry = \App\Models\Entry::findOrFail($entryId);
// Vulnerable: a global token grants access to any entry
if ($token === config('statamic.live_preview_token')) {
return response()->json($entry);
}
abort(403);
}
}
?>
// Fixed pattern (enforce per-object authorization and per-entry token binding)
<?php
class PreviewController {
public function livePreview(\App\Models\Entry $entry)
{
// Enforce object-level authorization first
$this->authorize('view', $entry);
$token = $_GET['live_preview_token'] ?? null;
if (!$token || !$this->isValidPreviewToken($token, $entry)) {
abort(403);
}
return response()->json($entry);
}
private function isValidPreviewToken($token, $entry): bool
{
$secret = config('statamic.live_preview_secret');
if (!$secret) return false;
// Per-entry token bound to a short expiry (e.g., 15 minutes)
$expiresAt = time() + 900; // 15 minutes
$expected = hash_hmac('sha256', $entry->id . ':' . $expiresAt, $secret);
return hash_equals($expected, $token);
}
}
?>