Cache Poisoning in Laravel
How Cache Poisoning Manifests in Laravel
Cache poisoning in Laravel occurs when untrusted user input is used to construct cache keys or when cache poisoning vulnerabilities in Laravel's cache drivers are exploited. This can lead to serving malicious or incorrect content to other users.
In Laravel applications, cache poisoning typically manifests through several specific patterns:
- Dynamic cache keys with user input: Using user-controlled parameters directly in cache keys without validation
- Untrusted cache tagging: Leveraging user input for cache tags that can be manipulated
- Response cache poisoning: Poisoning cached HTTP responses with malicious content
- Database query result poisoning: Caching results of queries that include user input without proper sanitization
A common Laravel-specific vulnerability involves the Cache::remember() method. Consider this vulnerable pattern:
public function getUserProfile($username)
{
// Vulnerable: $username comes directly from URL
return Cache::remember("user_profile_{$username}", 3600, function() use ($username) {
return User::where('username', $username)->first();
});
}
An attacker could craft a username like admin or use special characters to manipulate cache keys, potentially causing other users to receive cached data intended for different users.
Another Laravel-specific scenario involves cache tags. Laravel allows tagging cached items for group invalidation:
Cache::tags(['user', $userId])->put('profile', $data);
If $userId comes from user input and isn't validated, an attacker could manipulate cache tags to affect other users' cached data or cause cache stampedes.
Response caching with middleware presents another attack vector:
protected $except = ['*'];
public function handle($request, $next)
{
$response = $next($request);
// Vulnerable: caching based on unvalidated request parameters
if ($request->has('cache')) {
Cache::put("response_{$request->path()}", $response->getContent(), 300);
}
return $response;
}
This allows attackers to poison the cache with malicious content that gets served to other users.
Laravel-Specific Detection
Detecting cache poisoning in Laravel requires examining both code patterns and runtime behavior. Here's how to identify these vulnerabilities:
Code Analysis
Search your Laravel codebase for these patterns:
# Find dynamic cache keys with user input
grep -r "Cache::remember" app/ --include="*.php" | grep -E "(request|input|post|get)"
# Find cache tagging with unvalidated input
grep -r "Cache::tags" app/ --include="*.php" | grep -E "(request|input|post|get)"
# Find response caching with user parameters
grep -r "Cache::put" app/Http/Middleware/ --include="*.php" | grep -E "(request|input|post|get)"
Runtime Detection with middleBrick
middleBrick's black-box scanning approach is particularly effective for detecting cache poisoning vulnerabilities in Laravel applications. The scanner tests unauthenticated endpoints and analyzes cache-related behaviors without requiring access credentials.
Key detection capabilities include:
- Testing cache key manipulation through parameter fuzzing
- Analyzing cache tag usage patterns for injection vulnerabilities
- Checking for response cache poisoning through content manipulation
- Verifying cache isolation between users and sessions
To scan your Laravel API with middleBrick:
npm install -g middlebrick
middlebrick scan https://yourapi.com/api/users/1
The scanner will test for cache poisoning by attempting to manipulate cache keys and tags, then verify if poisoned content is served to other users or sessions.
Manual Testing Steps
Perform these manual tests to verify cache poisoning vulnerabilities:
# Test cache key manipulation
curl -s "https://yourapi.com/api/user/profile?username=admin" > cache1.json
curl -s "https://yourapi.com/api/user/profile?username=admin%27%20OR%201=1" > cache2.json
# Compare responses for cache poisoning
diff cache1.json cache2.json
# Test cache tag manipulation
curl -s "https://yourapi.com/api/cache-test?tag=valid" > tag1.json
curl -s "https://yourapi.com/api/cache-test?tag=../etc/passwd" > tag2.json
Laravel-Specific Remediation
Fixing cache poisoning in Laravel requires both code changes and architectural considerations. Here are specific remediation strategies:
Input Validation and Sanitization
Always validate user input before using it in cache operations:
public function getUserProfile($username)
{
// Validate username format
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
throw new ¡InvalidArgumentException('Invalid username format');
}
// Use hashed cache keys for sensitive data
$cacheKey = 'user_profile_' . hash('sha256', $username);
return Cache::remember($cacheKey, 3600, function() use ($username) {
return User::where('username', $username)->first();
});
}
Safe Cache Tagging
Implement secure cache tagging:
public function cacheUserContent($userId, $content)
{
// Validate userId as integer
$userId = filter_var($userId, FILTER_VALIDATE_INT);
if ($userId === false) {
throw new ¡InvalidArgumentException('Invalid user ID');
}
// Use predefined tags instead of user input
$safeTags = ['user_content', 'user_' . $userId];
Cache::tags($safeTags)->put("user_content_{$userId}", $content, 3600);
}
Secure Response Caching
Implement proper response caching with validation:
class SecureResponseCacheMiddleware
{
public function handle($request, $next)
{
$response = $next($request);
// Only cache GET requests without sensitive parameters
if ($request->isMethod('get') && !$request->has('no_cache')) {
$cacheKey = 'response_' . hash('sha256', $request->url());
Cache::put($cacheKey, $response->getContent(), 300);
}
return $response;
}
}
Cache Isolation
Implement cache isolation to prevent cross-user data leakage:
public function getPrivateData($userId, $dataId)
{
// Validate both IDs
$userId = filter_var($userId, FILTER_VALIDATE_INT);
$dataId = filter_var($dataId, FILTER_VALIDATE_INT);
if ($userId === false || $dataId === false) {
throw new ¡InvalidArgumentException('Invalid IDs');
}
// Use composite key with user isolation
$cacheKey = "private_data_{$userId}_{$dataId}";
return Cache::remember($cacheKey, 1800, function() use ($userId, $dataId) {
return PrivateData::where('user_id', $userId)
->where('id', $dataId)
->firstOrFail();
});
}
Cache Driver Security
Configure your cache driver securely:
// config/cache.php
return [
'default' => 'redis',
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'prefix' => env('CACHE_PREFIX', 'app_cache_'),
],
],
];
Add cache security middleware to your app/Http/Kernel.php:
protected $middleware = [
// ... other middleware
\App\Http\Middleware\SecureCacheMiddleware::class,
];