require 'openssl' module Metasploit module Framework module Aws module Client USER_AGENT = "aws-sdk-ruby2/2.6.27 ruby/2.3.2 x86_64-darwin15" include Msf::Exploit::Remote::HttpClient # because Post modules require these to be defined when including HttpClient def register_autofilter_ports(ports=[]); end def register_autofilter_hosts(ports=[]); end def register_autofilter_services(services=[]); end def hexdigest(value) if value.nil? || !value.instance_of?(String) print_error "Unexpected value format" return nil end digest = OpenSSL::Digest.new('SHA256') if value.respond_to?(:read) chunk = nil chunk_size = 1024 * 1024 # 1 megabyte digest.update(chunk) while chunk = value.read(chunk_size) value.rewind else digest.update(value) end digest.hexdigest end def hmac(key, value) if key.nil? || !key.instance_of?(String) || value.nil? || !value.instance_of?(String) print_error "Unexpected key/value format" return nil end OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value) end def hexhmac(key, value) if key.nil? || !key.instance_of?(String) || value.nil? || !value.instance_of?(String) print_error "Unexpected key/value format" return nil end OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value) end def request_to_sign(headers, body_digest) if headers.nil? || !headers.instance_of?(Hash) || body_digest.nil? || !body_digest.instance_of?(String) return nil, nil end headers_block = headers.sort_by(&:first).map do |k, v| v = "#{v},#{v}" if k == 'Host' "#{k.downcase}:#{v}" end.join("\n") headers_list = headers.keys.sort.map(&:downcase).join(';') flat_request = [ "POST", "/", '', headers_block + "\n", headers_list, body_digest].join("\n") [headers_list, flat_request] end def sign(creds, service, headers, body_digest, now) date_mac = hmac("AWS4" + creds.fetch('SecretAccessKey'), now[0, 8]) region_mac = hmac(date_mac, datastore['Region']) service_mac = hmac(region_mac, service) credentials_mac = hmac(service_mac, 'aws4_request') headers_list, flat_request = request_to_sign(headers, body_digest) doc = "AWS4-HMAC-SHA256\n#{now}\n#{now[0, 8]}/#{datastore['Region']}/#{service}/aws4_request\n#{hexdigest(flat_request)}" signature = hexhmac(credentials_mac, doc) [headers_list, signature] end def auth(creds, service, headers, body_digest, now) headers_list, signature = sign(creds, service, headers, body_digest, now) "AWS4-HMAC-SHA256 Credential=#{creds.fetch('AccessKeyId')}/#{now[0, 8]}/#{datastore['Region']}/#{service}/aws4_request, SignedHeaders=#{headers_list}, Signature=#{signature}" end def body(vars_post) pstr = "" vars_post.each_pair do |var, val| pstr << '&' unless pstr.empty? pstr << var pstr << '=' pstr << val end pstr end def headers(creds, service, body_digest, now = nil) now = Time.now.utc.strftime("%Y%m%dT%H%M%SZ") if now.nil? headers = { 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8', 'Accept-Encoding' => '', 'User-Agent' => USER_AGENT, 'X-Amz-Date' => now, 'Host' => datastore['RHOST'], 'X-Amz-Content-Sha256' => body_digest, 'Accept' => '*/*' } headers['X-Amz-Security-Token'] = creds['Token'] if creds['Token'] sign_headers = ['Content-Type', 'Host', 'User-Agent', 'X-Amz-Content-Sha256', 'X-Amz-Date'] auth_headers = headers.select { |k, _| sign_headers.include?(k) } headers['Authorization'] = auth(creds, service, auth_headers, body_digest, now) headers end def print_hsh(hsh) return if hsh.nil? || !hsh.instance_of?(Hash) hsh.each do |key, value| vprint_status "#{key}: #{value}" end end def print_results(doc, action) response = "#{action}Response" result = "#{action}Result" resource = /[A-Z][a-z]+([A-Za-z]+)/.match(action)[1] if doc["ErrorResponse"] && doc["ErrorResponse"]["Error"] print_error doc["ErrorResponse"]["Error"]["Message"] return nil end idoc = doc.fetch(response) if idoc.nil? || !idoc.instance_of?(Hash) print_error "Unexpected response structure" return {} end idoc = idoc[result] if idoc[result] idoc = idoc[resource] if idoc[resource] if idoc["member"] idoc["member"].each do |x| print_hsh x end else print_hsh idoc end idoc end def call_api(creds, service, api_params) vprint_status("Connecting (#{datastore['RHOST']})...") body = body(api_params) body_length = body.length body_digest = hexdigest(body) begin res = send_request_raw( 'method' => 'POST', 'data' => body, 'headers' => headers(creds, service, body_digest) ) if res.nil? print_error "#{peer} did not respond" else Hash.from_xml(res.body) end rescue => e print_error e.message end end def call_iam(creds, api_params) api_params['Version'] = '2010-05-08' unless api_params['Version'] call_api(creds, 'iam', api_params) end def call_ec2(creds, api_params) api_params['Version'] = '2015-10-01' unless api_params['Version'] call_api(creds, 'ec2', api_params) end def call_sts(creds, api_params) api_params['Version'] = '2011-06-15' unless api_params['Version'] call_api(creds, 'sts', api_params) end end end end end