rate limit test

This commit is contained in:
Andras Bacsai
2025-10-28 15:18:28 +01:00
parent 6d6f3e9de7
commit ae9f348458
4 changed files with 104 additions and 213 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+58
View File
@@ -0,0 +1,58 @@
<?php
it('tests login rate limiting with different IPs like the Python script', function () {
// Create a test route that mimics login behavior
// We'll directly test the rate limiter behavior
$baseUrl = '/login';
$email = 'grumpinout+admin@wearehackerone.com';
// First, get a CSRF token by visiting the login page
$loginPageResponse = $this->get($baseUrl);
$loginPageResponse->assertSuccessful();
// Extract CSRF token using regex similar to Python script
preg_match('/name="_token"\s+value="([^"]+)"/', $loginPageResponse->getContent(), $matches);
$token = $matches[1] ?? null;
expect($token)->not->toBeNull('CSRF token should be found');
// Test 14 login attempts with different IPs (like the Python script does 1-14)
$results = [];
for ($i = 1; $i <= 14; $i++) {
$spoofedIp = "198.51.100.{$i}";
$response = $this->withHeader('X-Forwarded-For', $spoofedIp)
->post($baseUrl, [
'_token' => $token,
'email' => $email,
'password' => "WrongPass{$i}!",
]);
$statusCode = $response->getStatusCode();
$rateLimitLimit = $response->headers->get('X-RateLimit-Limit');
$rateLimitRemaining = $response->headers->get('X-RateLimit-Remaining');
$results[$i] = [
'ip' => $spoofedIp,
'status' => $statusCode,
'rate_limit' => $rateLimitLimit,
'rate_limit_remaining' => $rateLimitRemaining,
];
// Print output similar to Python script
echo 'Attempt '.str_pad($i, 2, '0', STR_PAD_LEFT).": status=$statusCode, RL=$rateLimitLimit/$rateLimitRemaining\n";
// Add a small delay like the Python script (0.2 seconds)
usleep(200000);
}
// Verify results
expect($results)->toHaveCount(14);
// Check that we got responses for all attempts
foreach ($results as $i => $result) {
expect($result['status'])->toBeGreaterThanOrEqual(200);
expect($result['ip'])->toBe("198.51.100.{$i}");
}
});
-167
View File
@@ -1,167 +0,0 @@
<?php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
uses(\Tests\TestCase::class);
test('login rate limiter uses real IP not spoofable headers', function () {
// Get the rate limiter for login
$limiter = RateLimiter::limiter('login');
// Create a mock request with X-Forwarded-For header (attempt to spoof)
$request = Request::create('/login', 'POST', [
'email' => 'test@example.com',
'password' => 'password',
]);
// Set spoofed header
$request->headers->set('X-Forwarded-For', '10.0.0.99');
// Set the real IP (REMOTE_ADDR)
$request->server->set('REMOTE_ADDR', '192.168.1.1');
// Get the limit from the rate limiter
$limit = $limiter($request);
expect($limit)->toBeInstanceOf(Limit::class);
// The key should be based on email + REMOTE_ADDR, not X-Forwarded-For
// We can't directly inspect the key, but we can verify the behavior
// by checking that the same REMOTE_ADDR is rate limited regardless of X-Forwarded-For
// Reset rate limiter for this test
RateLimiter::clear('test@example.com192.168.1.1');
// Make 5 attempts with different X-Forwarded-For headers but same REMOTE_ADDR
for ($i = 1; $i <= 5; $i++) {
$testRequest = Request::create('/login', 'POST', [
'email' => 'test@example.com',
'password' => 'wrong',
]);
$testRequest->headers->set('X-Forwarded-For', "10.0.0.{$i}");
$testRequest->server->set('REMOTE_ADDR', '192.168.1.1');
$available = RateLimiter::attempt(
'test@example.com192.168.1.1',
5,
function () {},
60
);
if ($i < 5) {
expect($available)->toBeTrue();
}
}
// 6th attempt should be rate limited
$sixthRequest = Request::create('/login', 'POST', [
'email' => 'test@example.com',
'password' => 'wrong',
]);
$sixthRequest->headers->set('X-Forwarded-For', '10.0.0.6');
$sixthRequest->server->set('REMOTE_ADDR', '192.168.1.1');
$available = RateLimiter::attempt(
'test@example.com192.168.1.1',
5,
function () {},
60
);
expect($available)->toBeFalse();
// Cleanup
RateLimiter::clear('test@example.com192.168.1.1');
});
test('forgot-password rate limiter uses real IP not spoofable headers', function () {
// Get the rate limiter for forgot-password
$limiter = RateLimiter::limiter('forgot-password');
// Create a mock request with X-Forwarded-For header
$request = Request::create('/forgot-password', 'POST', [
'email' => 'test@example.com',
]);
$request->headers->set('X-Forwarded-For', '10.0.0.99');
$request->server->set('REMOTE_ADDR', '192.168.1.2');
$limit = $limiter($request);
expect($limit)->toBeInstanceOf(Limit::class);
// Reset for test
RateLimiter::clear('192.168.1.2');
// Make 5 attempts
for ($i = 1; $i <= 5; $i++) {
$testRequest = Request::create('/forgot-password', 'POST');
$testRequest->headers->set('X-Forwarded-For', "10.0.0.{$i}");
$testRequest->server->set('REMOTE_ADDR', '192.168.1.2');
$available = RateLimiter::attempt(
'192.168.1.2',
5,
function () {},
60
);
if ($i < 5) {
expect($available)->toBeTrue();
}
}
// 6th attempt should fail
$available = RateLimiter::attempt(
'192.168.1.2',
5,
function () {},
60
);
expect($available)->toBeFalse();
// Cleanup
RateLimiter::clear('192.168.1.2');
});
test('different REMOTE_ADDR IPs are rate limited separately', function () {
// Reset
RateLimiter::clear('test@example.com192.168.1.3');
RateLimiter::clear('test@example.com192.168.1.4');
// Make 5 attempts from first IP
for ($i = 1; $i <= 5; $i++) {
$available = RateLimiter::attempt(
'test@example.com192.168.1.3',
5,
function () {},
60
);
expect($available)->toBeTrue();
}
// First IP should be rate limited now
$available = RateLimiter::attempt(
'test@example.com192.168.1.3',
5,
function () {},
60
);
expect($available)->toBeFalse();
// Second IP should still have attempts available
$available = RateLimiter::attempt(
'test@example.com192.168.1.4',
5,
function () {},
60
);
expect($available)->toBeTrue();
// Cleanup
RateLimiter::clear('test@example.com192.168.1.3');
RateLimiter::clear('test@example.com192.168.1.4');
});