From 20edabb0f5035e0f54f965245b1f07e76d38ee9e Mon Sep 17 00:00:00 2001 From: David Maloney Date: Thu, 8 May 2014 13:16:12 -0500 Subject: [PATCH] mySQL Loginscanner with specs to match This season's colours for Loginscanner is MySQL with Unit Test Coverage applied to match. --- .../framework/login_scanner/mysql.rb | 77 +++++++++++++ .../framework/login_scanner/mysql_spec.rb | 108 ++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 lib/metasploit/framework/login_scanner/mysql.rb create mode 100644 spec/lib/metasploit/framework/login_scanner/mysql_spec.rb diff --git a/lib/metasploit/framework/login_scanner/mysql.rb b/lib/metasploit/framework/login_scanner/mysql.rb new file mode 100644 index 0000000000..a2277e3d97 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/mysql.rb @@ -0,0 +1,77 @@ +require 'metasploit/framework/tcp/client' +require 'rbmysql' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with MySQL Database servers. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class MySQL + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + def attempt_login(credential) + result_options = { + credential: credential + } + + # manage our behind the scenes socket. Close any existing one and open a new one + disconnect if self.sock + connect + + begin + ::RbMysql.connect({ + :host => host, + :port => port, + :read_timeout => 300, + :write_timeout => 300, + :socket => sock, + :user => credential.public, + :password => credential.private, + :db => '' + }) + rescue Errno::ECONNREFUSED + result_options.merge!({ + status: :connection_error, + proof: "Connection refused" + }) + rescue RbMysql::ClientError + result_options.merge!({ + status: :connection_error, + proof: "Connection timeout" + }) + rescue Errno::ETIMEDOUT + result_options.merge!({ + status: :connection_error, + proof: "Operation Timed out" + }) + rescue RbMysql::HostNotPrivileged + result_options.merge!({ + status: :connection_error, + proof: "Unable to login from this host due to policy" + }) + rescue RbMysql::AccessDeniedError + result_options.merge!({ + status: :failed, + proof: "Access Denied" + }) + end + + unless result_options[:status] + result_options[:status] = :success + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + + end + + end + end +end \ No newline at end of file diff --git a/spec/lib/metasploit/framework/login_scanner/mysql_spec.rb b/spec/lib/metasploit/framework/login_scanner/mysql_spec.rb new file mode 100644 index 0000000000..f70035a0e9 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/mysql_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/mysql' + +describe Metasploit::Framework::LoginScanner::MySQL do + let(:public) { 'root' } + let(:private) { 'toor' } + let(:pub_blank) { + Metasploit::Framework::LoginScanner::Credential.new( + paired: true, + public: public, + private: '' + ) + } + + let(:pub_pub) { + Metasploit::Framework::LoginScanner::Credential.new( + paired: true, + public: public, + private: public + ) + } + + let(:pub_pri) { + Metasploit::Framework::LoginScanner::Credential.new( + paired: true, + public: public, + private: private + ) + } + + subject(:login_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base' + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + + context '#attempt_login' do + + context 'when the attempt is successful' do + it 'returns a result object with a status of :success' do + ::RbMysql.should_receive(:connect).and_return "fake mysql handle" + expect(login_scanner.attempt_login(pub_pri).status).to eq :success + end + end + + context 'when the attempt is unsuccessful' do + context 'due to connection refused' do + it 'returns a result with a status of :connection_error' do + ::RbMysql.should_receive(:connect).and_raise Errno::ECONNREFUSED + expect(login_scanner.attempt_login(pub_pub).status).to eq :connection_error + end + + it 'returns a result with the proof containing an appropriate error message' do + ::RbMysql.should_receive(:connect).and_raise Errno::ECONNREFUSED + expect(login_scanner.attempt_login(pub_pub).proof).to eq "Connection refused" + end + end + + context 'due to connection timeout' do + it 'returns a result with a status of :connection_error' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::ClientError + expect(login_scanner.attempt_login(pub_pub).status).to eq :connection_error + end + + it 'returns a result with the proof containing an appropriate error message' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::ClientError + expect(login_scanner.attempt_login(pub_pub).proof).to eq "Connection timeout" + end + end + + context 'due to operation timeout' do + it 'returns a result with a status of :connection_error' do + ::RbMysql.should_receive(:connect).and_raise Errno::ETIMEDOUT + expect(login_scanner.attempt_login(pub_pub).status).to eq :connection_error + end + + it 'returns a result with the proof containing an appropriate error message' do + ::RbMysql.should_receive(:connect).and_raise Errno::ETIMEDOUT + expect(login_scanner.attempt_login(pub_pub).proof).to eq "Operation Timed out" + end + end + + context 'due to not being allowed to connect from this host' do + it 'returns a result with a status of :connection_error' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::HostNotPrivileged, "Host not privileged" + expect(login_scanner.attempt_login(pub_pub).status).to eq :connection_error + end + + it 'returns a result with the proof containing an appropriate error message' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::HostNotPrivileged, "Host not privileged" + expect(login_scanner.attempt_login(pub_pub).proof).to eq "Unable to login from this host due to policy" + end + end + + context 'due to access denied' do + it 'returns a result with a status of :failed' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::AccessDeniedError, "Access Denied" + expect(login_scanner.attempt_login(pub_pub).status).to eq :failed + end + + it 'returns a result with the proof containing an appropriate error message' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::AccessDeniedError, "Access Denied" + expect(login_scanner.attempt_login(pub_pub).proof).to eq "Access Denied" + end + end + end + end + +end \ No newline at end of file