Path: blob/master/spec/integration/msfmcpd/tool_execution_search_spec.rb
70330 views
# frozen_string_literal: true12require 'msf/core/mcp'3require 'webmock/rspec'45RSpec.describe 'Tool Execution End-to-End - Search Modules' 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 'Module Search Integration with HTTP' do23it 'executes module search through complete HTTP request flow' 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 endpoint with realistic response34# Note: The MessagePackClient returns the unpacked response directly,35# so we need to return an array, not a hash with 'modules' key36search_stub = stub_request(:post, api_url)37.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)38.to_return(39status: 200,40body: [41{42'name' => 'ms17_010_eternalblue',43'fullname' => 'exploit/windows/smb/ms17_010_eternalblue',44'type' => 'exploit',45'rank' => 'excellent',46'disclosuredate' => '2017-03-14',47'description' => 'MS17-010 EternalBlue SMB Remote Windows Kernel Pool Corruption'48},49{50'name' => 'smb_version',51'fullname' => 'auxiliary/scanner/smb/smb_version',52'type' => 'auxiliary',53'rank' => 'normal',54'description' => 'SMB Version Detection'55}56].to_msgpack,57headers: { 'Content-Type' => 'binary/message-pack' }58)5960# Create rate limiter61limiter = Msf::MCP::Security::RateLimiter.new(requests_per_minute: 60, burst_size: 10)6263# Create authenticated client64client = Msf::MCP::Metasploit::MessagePackClient.new(65host: host,66port: port,67endpoint: endpoint68)69client.authenticate(user, password)7071# Create server context72server_context = {73msf_client: client,74rate_limiter: limiter75}7677# Execute search through complete stack78result = Msf::MCP::Tools::SearchModules.call(79query: 'smb',80server_context: server_context81)8283# Verify HTTP request was made84expect(search_stub).to have_been_requested.once8586# Verify MCP response structure87expect(result).to be_a(MCP::Tool::Response)88expect(result.content).to be_an(Array)89expect(result.content.first[:type]).to eq('text')9091# Verify data transformation occurred correctly92data = result.structured_content[:data]93expect(data).to be_an(Array)94expect(data.length).to eq(2)95expect(data.first[:fullname]).to eq('exploit/windows/smb/ms17_010_eternalblue')96expect(data.first[:type]).to eq('exploit')9798# Verify metadata99metadata = result.structured_content[:metadata]100expect(metadata[:query]).to eq('smb')101expect(metadata[:total_items]).to eq(2)102expect(metadata[:returned_items]).to eq(2)103end104105it 'handles empty search results through complete HTTP flow' do106# Stub authentication107stub_request(:post, api_url)108.with(body: ['auth.login', user, password].to_msgpack)109.to_return(110status: 200,111body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,112headers: { 'Content-Type' => 'binary/message-pack' }113)114115# Stub search with empty results116search_stub = stub_request(:post, api_url)117.with(body: ['module.search', 'test_token', 'nonexistent_module_xyz'].to_msgpack)118.to_return(119status: 200,120body: [].to_msgpack,121headers: { 'Content-Type' => 'binary/message-pack' }122)123124limiter = Msf::MCP::Security::RateLimiter.new(requests_per_minute: 60, burst_size: 10)125client = Msf::MCP::Metasploit::MessagePackClient.new(126host: host,127port: port,128endpoint: endpoint129)130client.authenticate(user, password)131132server_context = {133msf_client: client,134rate_limiter: limiter135}136137# Execute search138result = Msf::MCP::Tools::SearchModules.call(139query: 'nonexistent_module_xyz',140server_context: server_context141)142143# Verify HTTP request was made144expect(search_stub).to have_been_requested.once145146# Verify empty results handled correctly147expect(result).to be_a(MCP::Tool::Response)148expect(result.structured_content[:data]).to eq([])149expect(result.structured_content[:metadata][:total_items]).to eq(0)150expect(result.structured_content[:metadata][:returned_items]).to eq(0)151end152153it 'applies pagination correctly through HTTP request' do154# Stub authentication155stub_request(:post, api_url)156.with(body: ['auth.login', user, password].to_msgpack)157.to_return(158status: 200,159body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,160headers: { 'Content-Type' => 'binary/message-pack' }161)162163# Stub search with multiple results164search_stub = stub_request(:post, api_url)165.with(body: ['module.search', 'test_token', 'scanner'].to_msgpack)166.to_return(167status: 200,168body: [169{ 'fullname' => 'auxiliary/scanner/http/http_version', 'type' => 'auxiliary', 'name' => 'http_version' },170{ 'fullname' => 'auxiliary/scanner/smb/smb_version', 'type' => 'auxiliary', 'name' => 'smb_version' },171{ 'fullname' => 'auxiliary/scanner/ssh/ssh_version', 'type' => 'auxiliary', 'name' => 'ssh_version' },172{ 'fullname' => 'auxiliary/scanner/ftp/ftp_version', 'type' => 'auxiliary', 'name' => 'ftp_version' }173].to_msgpack,174headers: { 'Content-Type' => 'binary/message-pack' }175)176177limiter = Msf::MCP::Security::RateLimiter.new(requests_per_minute: 60, burst_size: 10)178client = Msf::MCP::Metasploit::MessagePackClient.new(179host: host,180port: port,181endpoint: endpoint182)183client.authenticate(user, password)184185server_context = {186msf_client: client,187rate_limiter: limiter188}189190# Execute search with pagination191result = Msf::MCP::Tools::SearchModules.call(192query: 'scanner',193limit: 2,194offset: 1,195server_context: server_context196)197198# Verify HTTP request was made199expect(search_stub).to have_been_requested.once200201# Verify pagination applied correctly (offset=1, limit=2 means items 2 and 3)202expect(result).to be_a(MCP::Tool::Response)203data = result.structured_content[:data]204expect(data.length).to eq(2)205expect(data.first[:fullname]).to eq('auxiliary/scanner/smb/smb_version')206expect(data.last[:fullname]).to eq('auxiliary/scanner/ssh/ssh_version')207208# Verify pagination metadata209metadata = result.structured_content[:metadata]210expect(metadata[:limit]).to eq(2)211expect(metadata[:offset]).to eq(1)212expect(metadata[:total_items]).to eq(4)213expect(metadata[:returned_items]).to eq(2)214end215end216end217218219