Path: blob/master/spec/integration/msfmcpd/rate_limiting_spec.rb
70330 views
# frozen_string_literal: true12require 'msf/core/mcp'3require 'webmock/rspec'45RSpec.describe 'Rate Limiting Integration' do6# Disable real HTTP connections for integration tests7before(:all) do8WebMock.disable_net_connect!(allow_localhost: false)9end1011after(:all) do12WebMock.allow_net_connect!13end1415let(:host) { 'localhost' }16let(:port) { 55553 }17let(:endpoint) { '/api/' }18let(:api_url) { "https://#{host}:#{port}#{endpoint}" }19let(:user) { 'test_user' }20let(:password) { 'test_password' }2122describe 'Rate Limiting Across Multiple Tool HTTP Requests' do23it 'enforces rate limit across multiple tool calls with HTTP requests' do24# Stub authentication endpoint25stub_request(:post, api_url)26.with(body: ['auth.login', user, password].to_msgpack)27.to_return(28status: 200,29body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,30headers: { 'Content-Type' => 'binary/message-pack' }31)3233# Stub module search endpoint34search_stub = stub_request(:post, api_url)35.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)36.to_return(37status: 200,38body: [{ 'fullname' => 'auxiliary/scanner/smb/smb_version' }].to_msgpack,39headers: { 'Content-Type' => 'binary/message-pack' }40)4142# Create rate limiter with low limit43limiter = Msf::MCP::Security::RateLimiter.new(requests_per_minute: 3, burst_size: 3)4445# Create authenticated client46client = Msf::MCP::Metasploit::MessagePackClient.new(47host: host,48port: port,49endpoint: endpoint50)51client.authenticate(user, password)5253# Create server context54server_context = {55msf_client: client,56rate_limiter: limiter57}5859# First 3 tool calls should succeed (within rate limit)603.times do61expect {62Msf::MCP::Tools::SearchModules.call(63query: 'smb',64limit: 10,65server_context: server_context66)67}.not_to raise_error68end6970# Verify exactly 3 HTTP requests were made (plus 1 for auth)71expect(search_stub).to have_been_requested.times(3)7273# 4th call should be rate limited before making HTTP request74result = Msf::MCP::Tools::SearchModules.call(query: 'smb', limit: 10, server_context: server_context)75expect(result.error?).to be true76expect(result.content.first[:text]).to match(/Rate limit exceeded/)7778# Verify still only 3 search requests (4th was blocked by rate limiter)79expect(search_stub).to have_been_requested.times(3)80end8182it 'applies global rate limit across different tools with HTTP calls' do83# Stub authentication84stub_request(:post, api_url)85.with(body: ['auth.login', user, password].to_msgpack)86.to_return(87status: 200,88body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,89headers: { 'Content-Type' => 'binary/message-pack' }90)9192# Stub search modules93search_stub = stub_request(:post, api_url)94.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)95.to_return(96status: 200,97body: [].to_msgpack,98headers: { 'Content-Type' => 'binary/message-pack' }99)100101# Stub db.hosts102hosts_stub = stub_request(:post, api_url)103.with(body: ['db.hosts', 'test_token', { workspace: 'default' }].to_msgpack)104.to_return(105status: 200,106body: { 'hosts' => [] }.to_msgpack,107headers: { 'Content-Type' => 'binary/message-pack' }108)109110limiter = Msf::MCP::Security::RateLimiter.new(requests_per_minute: 5, burst_size: 5)111client = Msf::MCP::Metasploit::MessagePackClient.new(112host: host,113port: port,114endpoint: endpoint115)116client.authenticate(user, password)117118server_context = {119msf_client: client,120rate_limiter: limiter121}122123# Make 3 search_modules calls1243.times do125Msf::MCP::Tools::SearchModules.call(126query: 'smb',127limit: 10,128server_context: server_context129)130end131132# Make 2 host_info calls1332.times do134Msf::MCP::Tools::HostInfo.call(135workspace: 'default',136server_context: server_context137)138end139140# 6th call (different tool) should be rate limited141result = Msf::MCP::Tools::SearchModules.call(query: 'http', limit: 10, server_context: server_context)142expect(result.error?).to be true143expect(result.content.first[:text]).to match(/Rate limit exceeded/)144145# Verify HTTP request counts146expect(search_stub).to have_been_requested.times(3)147expect(hosts_stub).to have_been_requested.times(2)148end149end150end151152153