Skip to main content
When the vendor stopped supporting the hardware before the cipher suites were updated, the official path is gone. The hardware still works fine — you just have to meet it on its own terms.
Older enterprise server BMCs (out-of-band management controllers — Dell iDRAC 6, HPE iLO 3, Supermicro IPMI 1.5 era) shipped with web consoles designed around contemporary Java Web Start, ActiveX, and TLS cipher suites. A decade later, every modern browser refuses the plugins, every modern JRE refuses the unsigned jars, and every modern TLS stack refuses the ciphers. The chassis is still on the rack, still drawing power, still perfectly usable — and the official remote-management story is gone. This page captures the patterns that keep us working on this gear without a VGA monitor and without lowering the security posture of our daily-driver workstation.

Pattern 1 — Reverse-engineer the hidden HTTP API

Before fighting the Java applet, look at what its parent web UI is doing over plain HTTP. Open the BMC web UI in a modern browser, accept the self-signed cert, open the network panel, and watch. What you almost always find:
  • A login endpoint (often POST /data/login or similar) that returns session tokens plus a cookie
  • Combined “system summary” endpoints that batch a dozen sensor and state fields in one XML payload
  • A screen-capture endpoint the web UI hits every few seconds to refresh its tiny preview pane — this is the same VGA buffer the expensive Java applet would render at full resolution
That screen-capture endpoint is the big prize. The preview is small and refreshes slowly, but it returns a real PNG of whatever is on the host’s VGA output — POST, GRUB, BIOS, OS console, the lot. Two curl calls (trigger refresh, fetch PNG) replace the entire Java vKVM stack for monitoring purposes. A typical recipe:
# 1. Authenticate, capture the session cookie
curl -k --tlsv1 --tls-max 1.2 'https://<bmc>/data/login' \
  --data 'user=root&password=<password>' -c session.jar

# 2. Tell the BMC to refresh its internal preview buffer
TS=$(date +%s%3N)
curl -k --tlsv1 --tls-max 1.2 -b session.jar -X POST \
  -H 'X-Auto-Refresh-Header: 1' \
  "https://<bmc>/data?get=consolepreview[auto $TS]" -o /dev/null

# 3. Grab the rendered PNG
curl -k --tlsv1 --tls-max 1.2 -b session.jar \
  "https://<bmc>/path/to/screenshot.png?$(date +%s%3N)" -o screen.png
For active interaction (typing into BIOS, clicking through GRUB), the Java applet is still the only option. For monitoring — “is the installer running? did POST finish? what’s the panic message?” — the HTTP screenshot path wins every dimension: no Java install, no unsigned-jar dance, no TLS cipher negotiation, just curl and a PNG.

Why this is safe to publish

We are not publishing exploits. The endpoints are reachable to anyone already authenticated to the BMC. The vendor knows about them — their own web UI calls them. We are publishing how to discover that the endpoints exist so AI agents and homelab operators can build better monitoring tools instead of forcing brittle Java applets onto modern workstations.

Pattern 2 — Wrap the Java applet, do not replace it

When you do need interactive vKVM and the only path is the BMC’s unsigned Java applet:
  1. Don’t install Java on the host workstation. Put the whole legacy Java stack inside a single throwaway container.
  2. Inside that container, run a desktop environment streamed to the browser (e.g. linuxserver/webtop for an XFCE-in-browser experience).
  3. Install OpenWebStart, JRE 8, and a jarsigner-capable JDK headless into that container.
  4. Intercept the .jnlp download with a small wrapper script that:
    • Downloads each jar referenced by the JNLP
    • Strips any pre-existing signature blocks
    • Re-signs every jar with a container-local self-signed keystore
    • Rewrites the JNLP’s jar URLs to point at the locally re-signed copies on disk via file://
    • Hands the rewritten JNLP to javaws
  5. Register the wrapper as the system MIME handler for application/x-java-jnlp-file so the workflow is one-click from the BMC web UI inside the container.
The container can be destroyed and rebuilt cleanly. The host workstation never sees Java, never sees the self-signed keystore, never has to relax its TLS cipher policy. When the container is gone, the legacy attack surface is gone with it. Sharp edges to budget for:
  • BMC firmware updates can rotate the jar set; the wrapper’s jar-signing cache needs to be invalidated when that happens
  • JNLP files generated by legacy BMCs sometimes URL-encode spaces as + in the canonical-href attribute. Modern Java URL parsers round-trip-decode that to space and look for a file that doesn’t exist. The wrapper has to strip that attribute before passing to javaws.
  • The BMC’s video-encryption setting (often RC4) silently breaks the KVM connect on modern Java TLS stacks. If the BMC is on a trusted out-of-band VLAN, disable it.

Pattern 3 — Headless installs on hardware without VGA

For installing a modern OS onto a chassis where:
  • The Java vKVM is broken (see Pattern 2)
  • No VGA cable or monitor is available
  • No serial console cable is on hand
The path is:
  1. Build a custom installer ISO with an embedded answer file (Proxmox’s proxmox-auto-install-assistant, Debian preseed, RHEL kickstart — every distro has the equivalent). The answer file must specify network from DHCP and a known SSH key.
  2. Drop the ISO onto a multi-boot USB stick (Ventoy works well) and configure the stick’s bootloader to auto-pick the installer with a short timeout — no keyboard required.
  3. Use IPMI to force one-shot boot from removable media:
    ipmitool -I lanplus -H <bmc> -U root -P <pw> \
      chassis bootdev floppy options=persistent
    ipmitool -I lanplus -H <bmc> -U root -P <pw> chassis power cycle
    
    Note: in IPMI spec parlance, floppy means “USB stick” on modern hardware — the spec predates USB. cdrom routes to optical drives or virtual-CD subsystems (the latter is broken on a lot of legacy BMCs, which is why USB-boot is the reliable path).
  4. Monitor progress via the Pattern 1 screenshot loop — every minute, grab the PNG, compute its hash, alert on change. The installer takes 15-30 min on legacy USB 2.0; you’ll see GRUB, then the automated installer log, then a login prompt.
  5. Detect completion by polling SSH on the expected IP. When the port opens, the install succeeded.
This is the same shape as PXE-boot install, but it works without any network-side infrastructure (DHCP option 66/67, TFTP server, HTTP boot server). The USB stick is the entire delivery mechanism.

Pattern 4 — PXE-boot as the override of last resort

When USB-boot keeps falling through to the wrong OS on internal disks (legacy BMC bootdev=floppy options=persistent is silently one-shot on many firmwares — the override is consumed on the next boot, then BIOS falls through to the default disk priority), PXE is the reliable last-mile remote install path:
  1. Stand up dnsmasq in proxyDHCP mode on a host that already sits on the target VLAN (we use the existing healthy hypervisor). --dhcp-no-override --dhcp-range=<subnet>,proxy supplements the existing gateway’s DHCP with PXE options only, without conflicting. --port=0 disables the DNS half so it doesn’t clash with the site’s real resolver.
  2. Build a custom iPXE binary with an EMBED= script baked in. Most distro packages of iPXE do not auto-execute the DHCP-offered bootfile URL when chainloaded from BIOS PXE — iterating on dnsmasq config alone will never make iPXE chain. Embedding the script inside undionly.kpxe makes the chain deterministic, with no external fetch dependency.
  3. Boot kernel + initrd directly via iPXE, not the ISO via sanboot. sanboot of a large auto-install ISO is unreliable on pre-2010 server NICs and BMC video stacks. Extracting the kernel and initrd from the ISO (mount -o loop then copy boot/linux26 + boot/initrd.img) and serving them via HTTP is small (~70 MB), bursty but reliable.
  4. Pass the answer-file URL on the kernel cmdline. Modern Linux installers (Proxmox VE’s auto-installer, Debian preseed, RHEL kickstart) accept fetch URLs as kernel parameters. The installer’s first network-up step fetches the answer file over HTTP and proceeds unattended.
The IPMI side is still bootdev=pxe options=persistent plus a power-cycle. The “persistent” keyword is still ignored on legacy BMCs, but PXE doesn’t have the same fall-through-to-disk-OS problem that USB does — if the PXE server is reachable, the install proceeds; if not, the chassis simply hangs at “PXE boot failed”, which is observable from the BMC screen-capture and IPMI sensor data.

What we learned along the way

  • Reach for plain HTTP first. The fancier protocol — Java applet, vendor-specific SDK, OEM thick-client tool — exists to ship features the vendor wanted to sell. The actual data almost always flows over plain HTTP underneath. The plain HTTP path is older, simpler, and far more likely to still work when the vendor has moved on.
  • Isolate the legacy stack at the container boundary. When you do need to keep the legacy plugin alive, put it inside a container that can be docker compose down -v’d cleanly. Don’t pollute the host workstation with year-old Java and downgraded TLS ciphers.
  • The IPMI spec is old enough to vote. Read its glossary before trusting any field name. floppy is USB, cdrom is optical-or-vCD, pxe is BMC-mediated network boot. Mismatches between modern intuition and 1998-era terminology eat hours.
  • Document the API as you discover it. Each endpoint you reverse-engineer gets entered into a per-BMC API reference in the private companion docs. The next AI agent that needs to monitor this hardware should not have to rediscover the same endpoints from scratch.