> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jacobpevans.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Legacy BMC tactics

> Reverse-engineering hidden HTTP endpoints, wrapping unsigned Java applets, and headless installs on enterprise hardware whose vendor moved on a decade ago.

> 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:

```bash theme={null}
# 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:

   ```bash theme={null}
   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.

## Related

* [Infrastructure overview](/infrastructure/overview) — the modern hardware
  story this hardware is being added to.
* [Conventions / no scripts](/conventions/no-scripts) — the bias toward
  one-liners over shell scripts; the workarounds above are kept to that
  bar where possible.
