Data Center

Aug 6, 2026

 · 

9

 min read

How to stop a BMC authentication bypass by blocking credential-store writes with Exein Photon

How to stop a BMC authentication bypass by blocking credential-store writes with Exein Photon

The BMC is the most privileged, least-watched part of a server. It runs below the operating system, stays powered when the host is off, and has full out-of-band control: power, sensors, storage, BIOS. Nothing running in the host OS can see what happens on it, because the BMC's job is precisely to manage the host from outside the host's own visibility.

That makes a BMC vulnerability different in kind from an application bug. When one is found, the fix has to travel through the vendor, then through every OEM that licensed the firmware, then through a qualification cycle, before it reaches a device an operator can flash. A BMC patch means a firmware reflash — not a rolling restart, not a hot-patch — and nobody reflashes a live data center fleet overnight. The realistic gap between "a critical CVE exists" and "my fleet is patched" runs months, sometimes longer than a year.

How to Stop CVE-2021-39296: OpenBMC Authentication Bypass Vulnerability

CVE-2021-39296 is what that gap looks like in practice. It's a CVSS 10.0 authentication bypass in phosphor-net-ipmid, the RMCP+ session daemon shipped in OpenBMC — the open-source BMC firmware stack that a large share of the industry's BMC vendors build their own firmware on top of. An attacker with network access to the BMC management port — a misconfigured firewall rule, a compromised jump host, nothing more — can open an IPMI session, skip the step that would prove they know a password, and still get the session treated as Administrator. From there, issuing a password-change command triggers a credential-store write that lands in /etc/shadow. From there it's a normal SSH login as root, with full out-of-band control of the machine. They fixed it in three weeks. Your fleet could still have been exposed for over a year. Google's security research team reported the bug on June 4, 2021, and the upstream fix landed three weeks later — before the CVE was even publicly disclosed on September 2, 2021. That speed didn't help everyone equally: IBM shipped a patched Power Systems firmware that same September, while Intel — building on the same OpenBMC code, on a different platform line — didn't ship its own fixed BMC firmware until February 14, 2023, seventeen months after disclosure. Same upstream fix, same open-source codebase, two OEMs on two completely different clocks — and every unpatched BMC in between was exposed to a walk-up compromise for however long its specific vendor took.

This post is the first in the Data Center series. It works through why this specific bypass is invisible to the tools operators already run, how an Exein Photon rule closes it at the kernel level without a firmware update, and — because a demo without honesty about its limits isn't worth publishing — what this approach does and doesn't cover.

What you'll need

  • A BMC Linux image with BPF LSM enabled (CONFIG_BPF_LSM=y, CONFIG_SECURITY_PATH=y, bpf present in /sys/kernel/security/lsm) — most OpenBMC-derived Yocto layers ship this by default
  • A photond binary, registered to an Exein Runtime platform account
  • netipmid (the phosphor-net-ipmid binary on this build) and ipmid, the RMCP+ session daemon and the D-Bus IPMI command handler that ships alongside it on OpenBMC-derived stacks
  • A raw RMCP+ client capable of opening a session and sending RAKP1 while skipping RAKP3 — standard ipmitool always completes the full RAKP1–RAKP3 handshake, so it won't reproduce this bypass on its own; the exploit needs a client built to stop short of proving it knows a password

Why this bypass doesn't show up on a dashboard

Start with why the obvious defenses don't catch this.

Network monitoring can't see it. RMCP+ is IPMI's session-establishment protocol, and it runs over UDP 623. A session opened with the auth-bypass bug produces traffic that is structurally identical to a normal, authorized IPMI session — same protocol, same port, same packet shapes. The traffic only becomes distinguishable after the password has already changed. If the BMC's management traffic is encrypted, as most vendor deployments now require, a network sensor has nothing to inspect at all. This is a bug in the daemon's session logic, not in the wire protocol, so nothing watching the wire will ever flag it.

Segmenting the management network isn't sufficient on its own. BMC management ports are supposed to be reachable — that's the entire point of out-of-band management. Operators do put them behind a jump host or a dedicated VLAN, and they should. But CVE-2021-39296 doesn't need internet exposure; it needs one misconfigured ACL rule or one compromised jump host inside that boundary, and boundaries get misconfigured. Segmentation reduces the attack surface. It doesn't cover the case where the attacker is already one hop inside it — which is exactly the scenario the CISA/NSA joint BMC hardening advisory and Eclypsium's ongoing BMC research (1, 2, 3) have documented as the realistic threat model.

Patching is the correct fix, and it's also the thing you can't do today. The upstream fix exists. Getting it onto a fleet requires the OEM to backport it, requalify the image, and the operator to schedule a maintenance window per device. That's the multi-month gap this post is about.

What's missing isn't detection of the network session — it's detection of what the session does once it's open. And what it does is unambiguous: a daemon whose entire job is talking IPMI over the network reaches into the credential store and rewrites it.

The rule: block the write, not the daemon

Two processes are in this exploit chain, not one. netipmid terminates the IPMI session and receives the "Set User Password" command, but it doesn't write the credential file itself — it hands off over D-Bus to ipmid, the process that actually owns the write. ipmid stages the new file at /etc/nshadow, then atomically renames it onto /etc/shadow. That rename is the commit: the point where the new password becomes real. The privilege separation the design intends is already there. The gap is that neither process can tell, by the time the write request reaches ipmid, whether the session that triggered it ever proved it knew a password.

The first rule we tried was the obvious one — block any process from opening /etc/shadow directly — and it failed for two separate reasons, both worth stating plainly rather than papering over. First, it's the wrong operation: every legitimate consumer of /etc/shadow, including pam_unix validating an SSH login, opens and reads the file; only a credential write renames a new file onto it. A rule keyed on open() can't tell a login from a password change, so it's either too noisy to use or has to fall back on a daemon-identity check to disambiguate — the same fragile crutch this design is trying to avoid. Second, we tried killing the process on that read instead of blocking the open, targeting phosphor-user-manager specifically. That also touches /etc/shadow during SSH's own PAM check, so the kill took down every SSH session before authentication finished. The fix broke the thing it was supposed to leave alone.

The rename is the operation that actually distinguishes the two cases. Nothing that legitimately reads /etc/shadow — PAM, unix_chkpwd, the SSH daemon itself — ever renames a file onto it; only a credential-store commit does that. That gives a rule with no daemon-identity check at all, because none is needed:

[[rules]]
name        = "Block: /etc/shadow overwrite (CVE-2021-39296)"
type        = "file_rename"
category    = "credential_access"
severity    = "critical"
description = "Block atomic commit of a new /etc/shadow — prevents unauthenticated IPMI password change from taking effect."
condition   = 'payload.destination.abs == "/etc/shadow"'
action      = "block"

Photon's lsm/path_rename hook evaluates the rename before it lands, independent of which process issues it — ipmid today, whatever helper a future BMC image routes the write through tomorrow. That's a broader invariant than "one binary can't touch one file": it says no process, named or not, gets to commit a new /etc/shadow outside a rename Photon isn't told to expect. It's a stronger property than an allow-list on a single daemon's identity, and it's the one that survives the next auth-bypass bug in this stack, not just this one.

There's a second signal earlier in the chain, and it's worth showing precisely because we chose not to enforce on it. netipmid maintains its own IPMI user cache at /var/lib/ipmi/ipmi_user.json, committed the same way — write to a temp file, then rename over the original:

[[rules]]
name        = "Alert: IPMI user database commit (CVE-2021-39296)"
type        = "file_rename"
category    = "credential_access"
severity    = "critical"
description = "netipmid committing IPMI user JSON cache — early indicator that a privileged IPMI operation triggered a credential update."
condition   = 'header.image.abs == "/usr/bin/netipmid" && payload.source.abs == "/var/lib/ipmi/ipmi_user.json_tmp" && payload.destination.abs == "/var/lib/ipmi/ipmi_user.json"'
action      = "alert"

This one stays alert-only, and the reason is a useful data point about how far a behavioral rule can be pushed before it breaks the thing it's protecting. netipmid runs this same commit on a periodic background sync, unrelated to any attack — so blocking it doesn't just miss the exploit, it makes netipmid retry the failed rename in a loop that monopolizes its own processing thread, and the daemon stops answering session requests at all. An overly aggressive rule here creates its own denial of service. The real enforcement point stays downstream, on the shadow rename, where the invariant is unconditional and doesn't depend on telling a background sync apart from an attack.

Watching it block a real exploit attempt

This is a real capture, not a modeled one — run against a stock QEMU OpenBMC (scarthgap) target with the two rules above loaded, last verified 2026-06-21. The attacker opens an RMCP+ session (cipher suite 17 — negotiated but never exercised, since RAKP3 is skipped entirely), gets the session attached to user root at Administrator privilege on RAKP1 alone, and issues "Set User Password" against four IPMI user slots:

PHASE 1   — RMCP+ Open Session (no auth)
[+] BMC assigned session ID: 0xb0604993
PHASE 1b  — RAKP1 (username only, no password)
[+] RAKP2 received — netipmid attached 'root' (Admin) to session 0xb0604993
[*] Skipping RAKP3 — no password check needed for CVE-2021-39296
PHASE 1c  — Set Session Privilege Level → Admin
[+] Session privilege set to Administrator
PHASE 4Set Password (Privilege Escalation)
[*] Trying user 1 ...
    Enable user 1: cc=0x00
    Set password user 1: cc=0xcc    ← ipmid shadow rename blocked (EPERM)
[*] Trying user 2 ...
    Enable user 2: cc=0xff          ← user not configured (expected)
[!] Could not set password on any user (all returned non-zero cc).

cc=0xcc is IPMI's "Invalid data field in request" — the response code ipmid returns when its shadow rename fails with EPERM. The attacker's tooling can't distinguish "Photon blocked this" from "the operation failed" at the protocol level; there's no signal back to them that a defense is even present. On the defender side, Photon logs exactly two threat events for the whole attempt:

[RULE ENGINE (alert)] Alert: IPMI user database commit (CVE-2021-39296)
  image=/usr/bin/netipmid  src=/var/lib/ipmi/ipmi_user.json_tmp  dst=/var/lib/ipmi/ipmi_user.json

[RULE ENGINE (block)] Block: /etc/shadow overwrite (CVE-2021-39296)
  image=/usr/bin/ipmid  src=/etc/nshadow  dst=/etc/shadow

The first line is the early indicator — netipmid committing its own user cache, alert-only for the DoS reason above. The second is the enforcement: ipmid's rename onto /etc/shadow returns EPERM before the write lands. The password is never changed; a follow-up SSH attempt with the original credentials still works, and an attempt with the attacker's intended password fails. Shipped to the Exein platform, the block becomes a scored incident: credential-access category, critical severity, with the process, the exact source and destination paths, and the timestamp CRA Art. 14's evidence chain asks for — described below.

Note what the block rule does not need in order to fire: it doesn't parse RMCP+ session state, doesn't know whether the IPMI authentication that preceded the call was valid, and carries no CVE-specific signature or daemon identity at all. It fires the same way for CVE-2021-39296 as it would for an undisclosed variant of the same bug, or for a completely different bypass in a completely different daemon, as long as the outcome is the same: something trying to rename a new file onto /etc/shadow.

What this stops, and what it does not

This is a post-exploitation control, not an exploitation-stage fix — and that's a deliberate distinction, not a hedge. An authentication bypass like CVE-2021-39296 has almost no kernel-level footprint to intervene on: the vulnerable step is a missing check in a userspace daemon's session logic, not a memory-safety bug or a syscall Photon's LSM hooks can deny before it fires. Photon can't stop the bypass itself — the session still gets accepted under the vulnerable code path, and an attacker can still open it, issue privileged IPMI commands, and get logged doing so. What Photon can do is act on what that primitive is used for: the credential-store write is one specific privileged action reachable from the bypass, and Photon denies it unconditionally, regardless of which process performs it — ipmid today, anything else tomorrow — because the rule carries no daemon-identity check.

The same bypass reaches other privileged actions this rule doesn't touch. Once a session has Administrator privilege without ever proving a password, every IPMI command that role can issue is in scope — not just "Set User Password." Power-cycling the host, reading sensor data, reading and writing BIOS/UEFI, pivoting to the host over IPMI's KVM — none of these end in a rename onto /etc/shadow, so none of them are covered by this rule. Each of those is its own post-exploitation surface and needs its own rule if it's sensitive enough to warrant one; this post covers the credential-write case specifically, not the bypass's full blast radius.

The early-indicator rule is detection, not enforcement, and that's a deliberate trade-off, not an oversight. Blocking netipmid's own IPMI-user-cache rename looked like it would stop the attack a step earlier — it also stops the daemon's routine background sync, which sends netipmid into an EPERM retry loop that monopolizes its processing thread and makes it stop responding to sessions entirely. A rule that's too aggressive here becomes its own denial of service. The actual enforcement point is one step downstream, on the shadow rename, where the invariant holds unconditionally instead of needing to tell a background sync apart from an attack.

It requires the fleet to actually be running Photon before the exploit attempt, not after. This is a runtime control, not a forensic one. A BMC that gets compromised before the agent is deployed doesn't get retroactive protection.

The honest framing: this is a default-deny rule on the one filesystem operation that ever legitimately commits a new /etc/shadow, independent of which process performs it. It buys the operator the months between disclosure and a qualified firmware update — it does not replace shipping the patch.

Why this matters before September 11, 2026

Under the EU Cyber Resilience Act, the notification obligation for actively exploited vulnerabilities activates September 11, 2026. Article 14 gives a manufacturer two parallel clocks once it becomes aware of exploitation: for the vulnerability itself, a 24-hour early warning, a 72-hour follow-up notification, and a final report within 14 days of a fix becoming available; for a severe incident arising from it, the same 24h/72h cadence with a final report within one month. Both clocks start from the manufacturer's awareness — which means the manufacturer has to be able to detect the event in the first place, not just eventually patch it.

A blocked, logged, timestamped credential-access attempt is exactly the evidence that awareness clock runs on. Without runtime visibility at the BMC layer, "awareness" for a stealthy auth-bypass like this one may not happen until well after exploitation, which is its own compliance problem separate from the security one. The patch-window gap this post opened with isn't just an operational risk anymore — starting September 11, it's a regulatory clock the manufacturer has to be able to prove it was watching.

The takeaway

CVE-2021-39296 is a specific bug, but the shape of it — a network-facing daemon that can be tricked into writing somewhere only a privilege-separated component should ever write — is not specific to this CVE, this daemon, or even this year. A Photon rule that denies the daemon's binary identity access to the credential store closes the bug's outcome without waiting on the vendor's patch cycle, and keeps working against the next bug in the same shape. It doesn't replace patching. It buys back the months a real fleet needs to get there — and gives you the timestamped evidence to show you were watching while you waited.

To try the mechanism on your own OpenBMC-based BMC image, see the Exein Runtime documentation. You can also contact the Exein team for a demo.

Author

Lief Tang | Director, Solutions Engineering, APAC

Share this resource
By subscribing, you agree to Exein’s Privacy Policy.
Thank you for your interest.
Download
Your download will begin automatically. If it doesn’t,

click here to download it manually.
Oops! Something went wrong while submitting the form.
Subscribe to our newsletter
By subscribing, you agree to Exein’s Privacy Policy.
You’re subscribed
We’ll keep you updated with the latest from Exein.
Oops! Something went wrong while submitting the form.

Built by you, trusted by your customers, secured by Exein

No items found.
No items found.
No items found.