An exploration tool for (in)secure out-of-band management services.
OOBscan enumerates BMCs, serial-port servers, IP-KVMs, PDUs, and other out-of-band management surfaces for protocol exposures, pre-auth information leakage, default credentials, and known-vulnerable firmware. The BMC/IPMI coverage ships enabled by default and drives the "Lights Out" Black Hat USA 2026 research.
This pre-release is missing detailed checks for vulnerabilities that are still going through the vendor notification and disclosure process.
$ go build -o oobscan .scan is the flagship: point it at targets and it runs the full
identify -> checks -> creds pipeline. The remaining commands are focused tools
for working with scan results and for interacting with a single target.
# Scan and inventory
$ oobscan scan # comprehensive per-target pipeline (identify -> checks -> creds)
$ oobscan coverage # print the OOB device/vendor inventory + registered check map
$ oobscan version
# Work with scan results (read a scan's *.jsonl)
$ oobscan convert # normalize a corpus into oobscan JSONL; redact or audit it
$ oobscan replay # re-run the record-pure detection logic over a corpus; diffable table
$ oobscan report # build an interactive HTML exposure report (one or many corpora)
$ oobscan hashcrack # crack leaked IPMI RAKP hashes offline (SHA1/MD5/SHA256); --show / --import-pot
$ oobscan hashdump # export hashcat / John input files from RAKP hashes
$ oobscan explain # render a markdown findings guide from a scan
# Interact with a single target (authenticated / targeted operations)
$ oobscan ipmi # ipmitool-like IPMI command runner (mc/chassis/lan/fru/user/sol/raw/...)
$ oobscan redfish # authenticated Redfish OEM operations
$ oobscan xdp # Intel ASD / H3C RemoteXDP JTAG clientoobscan convert rewrites a corpus as native oobscan JSONL. It reads its own
output (migrating older records to the current rules) and the two runZero export
shapes (--format rzsql, --format rzservices), preserving every source
attribute and deriving the oobscan identity/findings/RAKP view on top. Two
transforms hang off the same pass: --redact, which renumbers addresses and
strips asset-identifying values to produce a shareable corpus, and --quality,
the contributor-facing QA audit for data-hygiene and coverage regressions
(formerly the scanaudit command).
oobscan replay <corpus> is the regression harness for that same logic. It
streams a corpus and re-evaluates everything that is a pure function of a stored
record -- the audit rules (contradiction detector, proof predicates,
silent-partial, binary-body, mangled-bytes, duplicate collapse), record
migration, and, on a runZero corpus, the finding emitters -- then prints one
tab-separated, key-sorted table. Build two binaries, replay the same corpus with
each, and diff the tables to see exactly which counts a change moved;
--hosts <key> prints the hosts behind one row so comm shows which hosts
gained or lost it, and --field <check[.field]> reports how much a field's value
actually varies. Nothing in replay opens a socket, so a change to a probe or a
live check cannot be measured this way -- that still needs a scan.
oobscan report -o <dir> [Name=]<file.jsonl> ... builds a self-contained,
offline-capable HTML report from one or more oobscan NDJSON corpora. (Point it
at oobscan output; run a runZero export through oobscan convert first.) Naming
two or more sources (Internal=a.jsonl External=b.jsonl) adds a cross-source
comparison that judges populations on prevalence (% of each source) while
showing absolute counts, so a small redacted internal fleet and a large internet
sweep compare cleanly. The report opens on 16:9, deck-ready overview slides
(arrow-navigable, presentable fullscreen, screenshottable to a 1920x1080 PNG);
scrollable, interactive detail views -- a per-category exposure heatmap, service
and port charts, findings, IPMI/RAKP, and a filterable per-host explorer whose
rows expand to full per-host findings and services -- sit under a per-source
menu. The payload is gzip-compressed in the page (decompressed via
DecompressionStream), so a 130k-host corpus is a ~12 MB bundle. --systems
caps the explorer rows (default 60k; aggregates always cover the whole corpus),
--potfile marks RAKP hashes already cracked in the pot, and a source produced
by convert --redact is detected and only its anonymized values are carried in.
Beyond the CLI, the scanner's building blocks -- GUID decoding, TLS shared-key
matching, and the per-protocol probes -- are also importable library packages
under pkg/; see Using OOBscan as a library.
scan takes targets as arguments or via -i (file / - for stdin): single IPs,
hostnames, CIDRs (10.0.0.0/24), hyphen ranges (10.0.0.1-20), host:port, or
an IPv6 link-local sweep (ff02::1%en0, see below).
It fans out one panic-isolated goroutine per host (default 512, -w) and runs
a fixed pipeline on each:
- identify -- probe IPMI/HTTP/TLS/Redfish/iLO/iDRAC/Lantronix/Moxa/..., fuse the
signals into a device class + vendor + product (
pkg/identify). - device-specific checks -- checks scoped to the identified vendor/class.
- cross-vendor / class checks -- including the classic exposures: outdated
libupnp via SSDP (CVE-2012-5958/5959), SNMP default community (
public), SSH banner fingerprint + default-credential login, and the Supermicro/PSBlockcleartext credential store on TCP/49152 (CVE-2014-8272). --ipmi-full(opt-in) -- walk the entire IPMI NetFn/Cmd/IPMB surface and run the slow Track B/C/D research sweeps (session-ID correlation, OEM NetFn reachability, IPMB satellite discovery). These are excluded from the default scan to keep it fast; the default runs the fast exposure checks only.--ipmi-null-priv(opt-in, implied by--ipmi-full) -- when Get Channel Auth Caps advertises an enabled null or anonymous account, open an RMCP+ session as it and measure the privilege it actually holds. The advertised bit is a configuration statement, not an impact statement: an account can be enabled yet clamped to privilege 0 with IPMI messaging off, unable to read sensors, FRU, the SEL, or even Get Device ID. Without the measurementipmi.authNull/ipmi.authAnonstay LOW; a measured USER privilege or above raises them.
The interactive view shows progress, finding counts, and active phases.
-v/--verbose adds per-target and per-check logging on stderr. Ctrl-C cancels
the scan, flushes buffered output, and exits.
Default credentials are tried automatically for BMCs: the leaked RAKP-2 hash is
cracked offline (no lockout risk) against the vendor-prioritized knowledge
base, with a capped online RAKP-3/4 fallback for BMCs that don't leak
(--disable-logins disables all active logins).
Output is a live lipgloss TUI on a terminal, while newline-delimited JSON is
written to a file by default (set with --output/-o, or auto-created under
~/.cache/oobscan as YYYYmmdd-<timestamp>.jsonl). Use --disable-output
to skip automatic file creation when --output is not set:
$ oobscan scan 10.0.0.0/24 # interactive TUI
$ oobscan scan -o out.jsonl 10.0.0.0/16 # NDJSON to a file
$ oobscan scan 10.0.0.0/16 # NDJSON auto file in cache dir
$ oobscan scan --disable-output 10.0.0.0/16 # no auto NDJSON file
$ oobscan scan --disable-logins 10.0.0.0/16 # passive-only (no active default login attempts)
$ oobscan scan --ipmi-full 10.0.0.22 # + full IPMI walk
$ oobscan scan -v 10.0.0.22 # -v/--verbose: per-target identity + progressA management NIC answers on its IPv6 link-local address even when it has no
routable address at all -- freshly racked, no DHCP, or a management VLAN with no
addressing -- so an IPv6 multicast target is treated as a sweep rather than a
host. oobscan scan ff02::1%en0 probes the all-nodes group on en0 and scans
every link-local address that answers; drop the zone (ff02::1) to sweep every
up, multicast-capable interface that has a link-local address:
$ oobscan scan ff02::1%en0 # sweep en0, then scan what answers
$ oobscan scan --link-local-wait 8s ff02::1%en0 # longer listen window (busy segment)
$ oobscan scan ff02::1 # sweep every eligible interface
$ oobscan ipmi ff02::1%en0 mc info # same sweep, ipmi subcommand
$ oobscan scan 'fe80::dead:beef%en0' # a link-local host directlyTwo probes go out per interface (pkg/discover): an ICMPv6 echo to the group,
which every IPv6 node answers and which carries the sweep, and an RMCP presence
ping + session-less Get Channel Auth Caps on UDP/623, which additionally proves
a responder is a BMC. Both are read-only. Nodes stagger and rate-limit replies to
multicast (RFC 4443), so the sweep spreads several probes across the listen
window (--link-local-wait, default 3s) -- raise it if a device is missed. On
platforms where ICMPv6 datagram sockets are restricted the sweep falls back to a
raw socket and needs root; a bad zone is reported rather than silently swept on
the default interface.
Discovered targets keep their zone (fe80::...%en0) through dialing, URLs, and
the host field of the NDJSON output.
Captured RAKP hashes are written into each result's rakp records: the
hashcat field holds the ready-to-crack hashcat -m 7300 line when one applies
(HMAC-SHA1); HMAC-MD5/SHA256 records omit it but keep the full reconstruction
material (SIDs, nonces, GUID, HMAC) for offline cracking with the
pkg/ipmi crack API.
--verbose logs to stderr only. It reports per-target progress without touching
the NDJSON stream or TUI.
Module path: github.com/runZeroInc/oobscan. The scanning pipeline and every
research capability is available as an importable package; the CLI is a thin
front-end over them. Highlights:
| Capability | Package | Entry points |
|---|---|---|
Offline RAKP crack + hashcat -m 7300 export |
pkg/ipmi |
CrackRAKP, (*RAKPResult).Hashcat |
| Credential defaults + permutation engine | pkg/creds |
Table, CredsFor, Permute |
| Concurrent target engine and result model | pkg/engine |
New, Run, Target, Finding |
| IPv6 link-local host discovery (multicast sweep) | pkg/discover |
LinkLocal, ResolveSpecs, IsSpec |
| IPv6-safe host:port and URL formatting (zones) | pkg/netaddr |
DialHostPort, URL, URLHostPort |
| BMC System GUID decoder (vendor taxonomy) | pkg/guid |
Decode, DecodeHex |
| Remotely-detectable BMC/webapp CVE knowledge base | pkg/check, pkg/ilo |
check.Register HTTP CVE checks, ilo CVE map |
| Default/shared TLS cert + SPKI shared-key matching | pkg/tlscert |
Inspect, ClassifyIdentity |
| Registered security-check registry | pkg/check |
check.Register, check.All |
| Device/vendor/product inventory | pkg/oob |
oob.Vendors, oob.ByVendor |
| Per-protocol probes | pkg/ipmi, pkg/redfish, pkg/ilo, pkg/idrac, pkg/tlscert, pkg/wsman, pkg/amt, pkg/snmp, pkg/ssdp, pkg/slp, pkg/lantronix, pkg/moxa |
package-level probe funcs |
Note: these packages are importable but are not a committed-stable public API map and stability notes, and
examples/for runnable snippets.
- New credential: add one row to
creds.Table(pkg/creds/table.go). - New check:
check.Register(...)from aninit()inpkg/check, with anApplicabilitynaming the target vendors/classes (orCrossVendor). The pipeline picks it up by applicability -- no wiring changes. - New device class: add the
DeviceClassconst (pkg/engine) and itsoob.Categorymapping (pkg/identify/class.go). - New protocol probe: add
pkg/<proto>/, then a collector inpkg/identify/collectors.gothat fuses its signals into identification.
| Protocol | Port(s) | Package |
|---|---|---|
| IPMI 1.5 / RMCP ASF ping | 623/udp | pkg/ipmi |
| IPMI 2.0 RMCP+ RAKP (multi-user/multi-cipher) | 623/udp | pkg/ipmi |
| Get Device ID + IANA manufacturer->vendor | 623/udp | pkg/ipmi |
| Get Channel Cipher Suites enumeration (paged) | 623/udp | pkg/ipmi |
| Supermicro USBAnywhere virtual media (CVE-2019-16649/50) | 623/tcp | pkg/ipmi |
| Default/shared TLS certificate + weak-key fingerprint | 443/tcp | pkg/tlscert |
| BMC HTTP/TLS/SSH vendor fingerprints (recog-derived) | 443/22/tcp | pkg/bmcfp |
| Redfish + AMI CVE-2023-34329 / CVE-2024-54085 / :9080 no-XFF bypass | 443/9080/tcp | pkg/redfish |
| Firmware-derived HTTP/CGI/RPC sweep | 80/tcp, 443/tcp | pkg/httpscan |
HPE iLO /xmldata |
443/tcp | pkg/ilo |
Dell iDRAC /data?get=fwVersion etc. |
443/tcp | pkg/idrac |
| WS-Management Identify | 443/5986/16993 | pkg/wsman |
| Intel AMT + CVE-2017-5689 | 16992/16993 | pkg/amt |
| SNMP v1/v2c sysDescr / sysObjectID | 161/udp | pkg/snmp |
| SSDP M-SEARCH | 1900/udp | pkg/ssdp |
| SLPv2 SrvRqst | 427/udp | pkg/slp |
| Lantronix Setup Protocol | 30718/udp | pkg/lantronix |
| Moxa DeviceSearch | 4800/udp | pkg/moxa |
| SSH (sshamble JSONL import) | 22/tcp | pkg/ssh |
Run oobscan coverage for the full vendor / product table and the registered
security-check map.
Implementation coverage is tracked in
COVERAGE.md.
Checks are organized around what an unauthenticated network client can learn or do against a BMC:
- Discovery -- RMCP ping, Get Channel Auth Caps, Get Device ID, paged cipher-suite enumeration, Get System GUID.
- Fingerprint -- vendor/product/firmware from the Device-ID IANA manufacturer
ID, the Get Channel Auth Caps OEM ID, the System GUID taxonomy (Dell service
tag / HPE part-country-serial / Supermicro board-MAC / UUIDv1 node ID), and
recog-derived HTTP/TLS/SSH signatures (
pkg/bmcfp). - Exposure -- cipher-suite 0 (advertise vs. live), weak/legacy cipher suites, RAKP-2 hash disclosure (CVE-2013-4786), "accepts any username", null/anonymous users, IPMI 1.5 plaintext/MD2, shared/default TLS keys, USBAnywhere, Redfish auth-bypass, and version-matched known CVEs.
- Weak credentials -- RAKP hash extraction across the default-username list, offline crack against vendor defaults plus a rules-driven permutation engine (case / leetspeak / word-doubling / year-suffix variants).
- Single static Go binary, zero runtime deps.
- Typed
check.Register(Check{...})registry; checks are selectable via--checksand--categories. - NDJSON output is the machine-readable source of truth.
- Single IPv6 targets are supported. CIDR and hyphen-range expansion is IPv4-only.
BSD 2-Clause. See LICENSE.