TL;DR#

In February 2026 I audited bmcweb — the OpenBMC web server that runs on most modern BMCs from Intel, IBM, HPE, and NVIDIA. The audit produced four reportable findings: three pre-auth denial-of-service primitives on the HTTP path, and one mTLS authentication bypass. All four were disclosed privately to openbmc-security@lists.ozlabs.org on 2026-02-23. Joseph Reynolds (IBM, openbmc-security responder) responded the same day, indicating that “90 days is consistent with project timelines” but proposing to defer setting a fixed disclosure date until “BMCWeb developers come up with a fix and let affected members ask if they need more time.”

Two of the four were quietly patched on master on 2026-04-21, with a single GitHub Security Advisory published that day covering only the HTTP/1.1 bug. The HTTP/2 patch landed in the same minute but got no advisory. The mTLS bug got an unrelated case-sensitivity touch-up that did not address the reported issue. The fourth bug — the most severe of the four — remains unpatched in master at the time of writing.

I submitted the publicly disclosed HTTP/1.1 bug to Intel’s bug bounty program on Intigriti. Intel triage initially moved it to “Pending” with a possible $15,000 reward, then closed it five hours later as out-of-scope under an “open-source projects Intel contributes to” clause — despite the report being explicitly about Intel’s own firmware binary, still shipping unpatched from Intel’s Download Center. A maintainer-side CVE request, sent two days later, was declined: no CVE will be assigned for the public GHSA, future correspondence was redirected to public OpenBMC channels, and the embargo was declared effectively over.

This write-up documents the timeline, the four findings, and what the disclosure process looked like from the researcher’s side — not as a complaint, but because the gap between “publicly disclosed via GHSA” and “actually ingested by NVD/OSV/vendor PSIRTs” is wider than most people expect, and the pattern matters for other researchers reporting upstream vulnerabilities that ship in name-brand firmware.


Why bmcweb#

A Baseboard Management Controller (BMC) is an out-of-band management processor embedded on essentially every modern server motherboard. It boots before the host CPU, runs its own OS (typically OpenBMC, a Linux distribution), exposes a network management interface (IPMI, Redfish, KVM-over-IP, virtual media), and has full control over the host — power, firmware, console, and in many cases direct memory access. A compromised BMC is, for practical purposes, a compromised server: the operating system the customer thinks they own runs on top of it.

bmcweb is OpenBMC’s web server. It implements Redfish (the BMC management API), serves the management web UI, handles WebSocket/SSE streams for KVM and console, and is the front door for almost all remote management. It’s written in C++23 on Boost.Beast, single-threaded async event loop, hand-rolled HTTP parsing in several places, and supports HTTP/1.1 and HTTP/2.

bmcweb ships in:

  • Intel — Integrated BMC firmware on Server Board D40AMP, D50TNP, M50CYP, M50FCP families (Intel discontinued its public OpenBMC fork in 2024 but deployed hardware still runs it)
  • IBM — POWER9 and POWER10 systems
  • HPE — selected ProLiant lines
  • NVIDIA — BlueField 2/3 DPU BMC, DGX systems
  • Various ODMs — Wiwynn, Supermicro, Quanta, etc.

The single-threaded design makes any blocking operation a denial-of-service primitive: one slow request, one large allocation, one event-loop stall, and the entire management interface for a server (or, depending on deployment, an entire datacenter row) is offline. That’s the lens the audit used.

The four findings#

The full audit covered 35+ source files in seven phases (multipart parsing, auth/session, HTTP/1.1 connection handler, HTTP/2 connection handler, URL routing, Redfish core handlers, and the dependency graph that sits between bmcweb and D-Bus). Of roughly 50 distinct observations, four were judged reportable to the OpenBMC security list. Brief summaries:

CONN-F2 — Expect: 100-continue bypasses body_limit (CVSS 7.5)#

bmcweb sets the parser’s body limit on each request via body_limit(getContentLengthLimit()) at http/http_connection.hpp:658. The limit is 4 KB for unauthenticated requests, 30 MB for authenticated. For requests carrying Expect: 100-continue, the code in afterReadHeaders() (line 645–650) detects the expectation, writes the 100 Continue interim response, and returns from the handler — before reaching the line that would have applied the body limit. afterDoWrite() then resumes reading the body with the parser’s body limit still at boost::none (unlimited).

Result: an unauthenticated client can send a POST with Expect: 100-continue and stream arbitrary amounts of data into std::string until the BMC runs out of memory. PoC at pocs/bmcweb/100-continue-dos.py reproduces a 2,560× bypass of the 4 KB unauthenticated limit (10 MB streamed before connection close).

H2-F1 — HTTP/2 path has no body limit at all (CVSS 7.5)#

The HTTP/1.1 path inherits its body limit from boost::beast’s parser. The HTTP/2 path does not use boost::beast for body assembly; it uses HttpBody::reader directly (defined in http/http_body.hpp). The put() method of that reader appends DATA frame bytes to value.str() with no size check whatsoever. Authentication runs in onRequestRecv, which is invoked on END_STREAM — i.e., after the full body has been received and buffered.

nghttp2 auto-replenishes flow control windows by default (bmcweb does not set NO_AUTO_WINDOW_UPDATE), so an attacker can send a POST over HTTP/2 and stream unlimited DATA frames before authentication is ever attempted. ALPN h2 is the default protocol path; no special headers or non-standard negotiation are required. The only requirement is an HTTP/2 client.

This is the most severe of the four findings. It is also the only one that remains unpatched on master at the time of writing.

H2-F2 — Trusted Content-Length passed to reserve() (CVSS 7.5)#

HttpBody::reader::init() reads the Content-Length header (parsed as uint64_t) and calls value.str().reserve(*contentLength). A single HEADERS frame with Content-Length: 999999999999 triggers std::bad_alloc from reserve() — and bmcweb’s single-threaded event loop has no exception handler that catches it at the right level. The process exits. One frame, no body, instant kill.

AUTH-F6 — mTLS UPN suffix matching unbounded (CVSS 6.8)#

The function that compares a client certificate’s UPN against the configured trust domain performs suffix matching by walking dot-separated labels without a minimum-label-depth guard. The consequence: a certificate with a UPN that is just a TLD (e.g. user@com) matches any hostname in that TLD (hostname.region.domain.com); a parent-domain certificate authenticates on any child-domain BMC.

mTLS is not the default authentication mode (CommonName is), and UPN mode requires specific configuration, so exploitability is config-dependent. But where deployed (which several large operators do), the bug is silent and total: anyone holding a *.com UPN cert can authenticate.

Technical detail intentionally light here — this finding has not yet had its public-channel disclosure on the OpenBMC mailing list. The level of code-path detail given for the other three findings will follow once that disclosure lands.

Disclosure timeline#

What follows is the full timeline as it played out, with the operative quotes left intact.

2026-02-23 — Reports sent, embargo terms set#

Three plaintext emails to openbmc-security@lists.ozlabs.org:

  1. CONN-F2 — Expect: 100-continue body limit bypass
  2. H2-F1 + H2-F2 — HTTP/2 body limit bypass + Content-Length reserve crash (combined; same root cause family)
  3. AUTH-F6 — mTLS UPN suffix matching

Each report included a working PoC, suggested fix, and CVSS vector. Joseph Reynolds (IBM, openbmc-security responder) ACK’d the reports the same day and forwarded them to Ed Tanous and Gunnar Mills as bmcweb OWNERS. I replied with two follow-up questions: CVE-assignment timeline, and embargo terms (proposing 90 days from initial report, or upon patch release, whichever came first). Joseph’s response, verbatim, was:

CVE assignment. I am unable to provide an expected timeline. The expectations are laid out in this webpage: https://github.com/openbmc/docs/blob/master/security/obmc-security-response-team-guidelines.md. The BMCWeb project maintainers may be able to provide more information. I recommend the BMCWeb team use [GitHub repository security advisories] as they have done in the past; see https://github.com/openbmc/bmcweb/security/advisories. This process can be used to write the CVE.

Coordinated disclosure. An embargo period followed by disclosure is consistent with the project’s guidelines. The project has used private Gerrit reviews to do the code reviews and share the fix with downstream vendors before the vulnerabilities become public — to give them time to create patches or fix packs. 90 days is consistent with project timelines, but can we let the BMCWeb developers come up with a fix and let affected members ask if they need more time — before setting a disclosure date?”

Two things to note. First, the embargo was not a firm 90-day clock — it was deferred to fix-readiness, with 90 days as a soft reference. Second, CVE assignment was explicitly deferred to “BMCWeb project maintainers” via GHSA-driven CVE requests, with no committed timeline. Both points become load-bearing later.

I agreed implicitly — there was no other option, and the framework was reasonable.

2026-02-24 → 2026-02-27 — Two Gerrit changes go up, then sit#

  • 2026-02-24 03:08 UTC — Gunnar Mills uploaded Gerrit 87688: “Move body limit enforcement” (CONN-F2 fix). Reorders handleContentLengthError() and body_limit() to run before the Expect: 100-continue short-circuit. Diff is 7+/-7.
  • 2026-02-26 17:14 -0600 — Gunnar uploaded a second Gerrit change: “HTTP/2: Reject over limit Content-Length” (H2-F2 fix). Adds a check *contentLength > BMCWEB_HTTP_BODY_LIMIT before reserve(). The commit message itself flags it WIP: “Not sending RST_STREAM/413 based on how init() works today.”

Both changes then sat in private review for roughly two months. There were occasional rebase pings; no substantive review comments visible from my side of the embargo.

H2-F1 — the most severe of the four — received no Gerrit at all. AUTH-F6 received no Gerrit at all.

2026-04-10 — A misleading UPN patch#

af44b2bd “http: Make UPN domain matching case-insensitive” landed on master, along with 5d4a41fa “more UPN unit tests.” These touch the exact function I reported, isUPNMatch(), but they do not address the unbounded suffix matching. They make @COM match @com. The dot-walking logic that lets user@com match any *.com hostname is untouched. From the diff alone it looks like AUTH-F6 was triaged and fixed; it was not.

2026-04-21 17:30/17:31 UTC — Coordinated landing#

Two commits, one minute apart:

  • 17:30 UTC0b2049b0 “http: Move body limit enforcement” merged to master by Gunnar Mills (CONN-F2 fix).
  • 17:31 UTC62526bb0 “HTTP/2: Reject over limit Content-Length” merged to master by Ed Tanous (H2-F2 fix).

The same day, Ed published GHSA-p3gc-68x5-g9w3 — “100-continue not enforcing max payload size”, CVSS 7.5, patched in bmcweb 3.0.0. Coordinated patch-and-publish landing, very clean.

But:

  • The advisory covers only CONN-F2. H2-F2 was patched in the same minute but received no GHSA, no separate advisory, no mention.
  • cve_id on the GHSA is null. No CVE was requested at the time of publication.
  • H2-F1 and AUTH-F6 remain unpatched on master.

I did not receive a notification of the landing or the GHSA publication. I noticed it during an out-of-band check on 2026-05-05.

2026-05-05 — Status nudge, met with silence#

Eight days after the GHSA, with the 90-day soft reference date approaching (90 days from 2026-02-23 = 2026-05-24) and with one of the two patched bugs publicly disclosed but the other two findings still unpatched, I sent a status-check email to Ed and Joseph (Cc Gunnar) asking about:

  1. CVE assignment for the published GHSA
  2. GHSA plan for H2-F2 (silently patched same day, no advisory)
  3. Status on H2-F1 (still unpatched)
  4. Status on AUTH-F6 (not addressed by the 4/10 patches)
  5. Downstream submission timing (Intel, NVIDIA, IBM, HPE)

Nine days of silence. The last response from any OpenBMC contact had been Gunnar Mills on 2026-03-19, weeks before the patches landed.

2026-05-08 → 2026-05-12 — The Intel cycle#

I submitted CONN-F2 to Intel’s bug bounty program on Intigriti (INTEL-PFYGISXL). The submission was explicit: this is about Intel’s M50FCP BMC firmware binary 2.94-0, downloadable from Intel Download Center URL 775817, dated January 2026 — predating both the upstream fix and the GHSA publication. The binary contains the string bmcweb/1.0+gitAUTOINC+72fe2ef7fa-r0 from an Intel-controlled Jenkins host (leStreamPc_dsg-openbmc-ci_master). Six Intel-branded product pages still link this firmware. The scope preamble in Intel’s Intigriti program explicitly includes “out-of-band management solutions such as BMC, CSME or similar.”

Triage cycle:

  • 5/8 20:45 — Triager mobley requested feedback: GitHub link not in scope, asked for documentation that bmcweb ships in M50FCP firmware via Intel Download Center.
  • 5/8 20:57 — I replied with Download Center URL, six Intel-branded product pages, the build path string + Jenkins host, the date math showing the binary predates the upstream fix, and a re-statement of the scope preamble.
  • 5/11 21:06mobley: “Kindly provide a detailed POC video.”
  • 5/12 08:33 — I replied with poc-100continue.mp4 showing both flows side-by-side: normal POST (body_limit enforced → 413) and POST with Expect: 100-continue (10 MB streams freely → 2,560× bypass). Python reproducer mirrored the exact code path.
  • 5/12 18:24mobley: “We will pass your findings along to the company for further review.” Standard acceptance message.
  • 5/12 18:26 — Status changed: Triage → Pending. Possible bounty: $15,000. Scope accepted, PoC accepted, Intel reviewing.
  • 5/12 23:21bot_intel (five hours later): Out of scope. Closing message:

“Intel PSIRT has determined this report is out of scope — Open-Source Projects which Intel contributes to may fall out of Scope for bounty rewards. Please contact the open-source project maintainer directly.”

The submission had passed triage scoping, the PoC was accepted, the possible bounty was set at $15,000, and then a separate clause was invoked five hours later to close it $0. The clause as worded is effectively a permanent escape hatch for any upstream bug regardless of how the binary actually ships.

2026-05-14 17:31 UTC — Tight CVE-only nudge to Ed#

The Intel rejection partly hinged on the absence of a CVE-anchored advisory: there was nothing in NVD, OSV, or the GitHub global Advisory Database tying the GHSA to Intel’s binary. So I sent a single-question nudge to Ed, Cc Joseph and Gunnar:

“Could you click ‘Request CVE’ on the advisory? It’s a one-click flow on the GHSA page, MITRE allocates a CVE-2026 ID, and NVD/OSV pull it within a day or two. That unblocks vendor disclosure for everyone who shipped the vulnerable bmcweb.”

I confirmed beforehand that none of the downstream propagation paths had picked up GHSA-p3gc-68x5-g9w3:

  • NVD: keyword search “bmcweb 100-continue” → zero results
  • OSV.dev: “Bug not found” for the GHSA ID
  • GitHub global Advisory Database: not listed (only the older CVE-2022-3409 and CVE-2024-31916 show up under openbmc/bmcweb)
  • Distro trackers (Red Hat, Ubuntu, Debian, SUSE): no pickup
  • Vendor PSIRTs: nothing

Without a CVE, the GHSA is effectively a private artefact published on a single GitHub page.

2026-05-14 18:51 UTC — Ed declines#

One hour and twenty minutes later, Ed replied. Joseph and Gunnar were Cc’d. Neither commented. Three points, verbatim:

On the CVE: “As far as I’m aware, there will be no CVE assigned for this.”

On the Intel out-of-scope: “I do not control Intel’s open source policy, nor am I familiar with their process or ID designations. You will need to work with the appropriate people at Intel if you believe their products are impacted. Intel also has maintainers on the OpenBMC project and can raise issues about the above security report if they choose to.”

On further direct contact: “This bug is no longer embargoed, and the fix is on master. There’s no longer a reason to send direct messages to the security responders. If you believe further action is needed, please use the normal project communication channels (mailing list, discord, gerrit reviews).”

That ends the private disclosure track. CVE: not coming. Intel: my problem to chase. Future correspondence on the remaining two unfixed findings (H2-F1, AUTH-F6): public channels only.

Ed’s “no longer embargoed, the fix is on master” framing is, in fairness, consistent with Joseph’s original 2/23 framework (embargo runs until fix-readiness, then disclosure). The 4/21 patch landing was the operative event, not the 5/24 calendar date. What was not consistent with the original framework was the absence of a CVE — Joseph’s 2/23 reply explicitly anticipated CVE assignment via the GHSA “Request CVE” flow, with the bmcweb maintainers driving it. That step was skipped, and Ed’s 5/14 reply confirms it will remain skipped.

What this looks like at the end#

Status as of 2026-05-14, ten days before the original calendar embargo expiry:

Finding Master patch GHSA CVE Status
CONN-F2 0b2049b0 (2026-04-21) ✅ GHSA-p3gc-68x5-g9w3 ❌ won’t be assigned Disclosed publicly via GHSA only
H2-F1 Unfixed, public channels only
H2-F2 62526bb0 (2026-04-21) Silently patched, no advisory
AUTH-F6 ❌ (only unrelated case-insensitivity touch-up) Unfixed, public channels only

Observations#

I’ll keep this part short, because the timeline above is the substance.

The “open-source projects we contribute to” clause is, in practice, an escape hatch for upstream vulnerabilities that ship in vendor firmware. It can be invoked even when the submission is explicitly about a vendor-controlled binary still distributed by the vendor. The clause doesn’t distinguish between a researcher reading the upstream repo and a researcher running the vendor’s downloadable firmware in QEMU. As long as the bug exists somewhere upstream, the clause applies. Researchers should price this risk in before investing time on the upstream-to-vendor pipeline.

A GHSA without a CVE has near-zero downstream propagation. NVD doesn’t ingest GHSAs that lack a CVE ID. OSV.dev doesn’t pick them up reliably. Distro security trackers don’t see them. Most vendor PSIRTs scan CVE feeds, not GHSA pages. The GHSA exists, but only on the bmcweb security tab. From a fix-actually-reaches-customers perspective, this matters: the M50FCP binary is still on Intel’s Download Center, dated January 2026, still vulnerable, and there is no CVE-anchored signal that would prompt Intel (or any other downstream BMC vendor) to ship a patched firmware on a schedule.

Coordinated patch-and-publish that omits an in-scope finding is a worse outcome than a slow patch. H2-F2 landed in the same minute as CONN-F2; both fixes were reviewed during the same embargo; only one got an advisory. The asymmetry is invisible to anyone reading the GHSA. A reader who applies the bmcweb 3.0.0 patches because of the published advisory gets the H2-F2 fix as a side effect, but doesn’t know what was actually fixed.

The 4/10 UPN patches are an example of how triage can look like resolution. Two commits touched the exact function I reported and added unit tests. From the outside, AUTH-F6 looked closed. It is not closed. The dot-walking suffix logic is untouched; what changed is case sensitivity. This is a small thing on its own, but it’s the kind of thing a downstream auditor reading the master branch would treat as evidence the bug was addressed.

The maintainer is doing nothing wrong. Ed has no obligation to assign a CVE, no budget to coordinate with Intel, no time to keep an email thread alive after a fix lands. The OpenBMC security responders are unpaid; the project has no security engineer. This entire process worked the way it worked because nobody whose job includes downstream signal propagation was in the loop. The structural problem is that “upstream patches, vendor ships” is the assumed model and it’s not actually how BMC firmware reaches customers.

What I’d do differently#

For my own files, since this is what the post-mortem produces:

  1. Don’t lean on the upstream CVE as the anchor for the vendor submission. Frame the vendor submission as “your firmware binary on your download page is unpatched” from the first message. The upstream GHSA, if it exists, is a supporting reference, not the load-bearing argument.
  2. Ask the CVE question up front, in the initial disclosure email. Don’t wait until the patch lands. “Will a CVE be requested when the GHSA is published?” is a reasonable embargo-terms question and the answer determines downstream strategy.
  3. Don’t combine findings in a single email if they want separate advisories. H2-F1 and H2-F2 were combined because they share a root cause (missing body_limit in HTTP/2). One got a fix and silent merge; the other got nothing. Filing them as two reports might have surfaced H2-F1 separately during triage.
  4. For BMC bugs specifically, consider going to the silicon vendor first. The upstream-then-vendor path looks tidy but it relies on the silicon vendor’s BB program accepting upstream-origin bugs. The Intel clause says they don’t. Other vendors (NVIDIA’s paid program is invite-only; HPE has no paid BB) have similar friction. The “upstream patches, vendor ships, researcher submits to vendor” pipeline is more fragile than it looks.

What’s left#

  • H2-F1 is the most severe of the four findings, unfixed on master, and now has no private disclosure channel. Options: public Gerrit patch + mailing-list post (per Ed’s redirect), or another vendor submission framed as binary-distribution-only. The former is faster; the latter has higher expected value but the same OOS risk that closed CONN-F2.
  • AUTH-F6 is the same situation at a lower severity. Probably write-up only.
  • Intel pushback is a low-EV option. The clause is structural, not case-specific; Intel’s bot has the final word and the upstream maintainer won’t advocate. Skip unless it can be done in under an hour.

The PoCs (CONN-F2, H2-F1, H2-F2) are in pocs/bmcweb/. They are self-contained Python reproducers. They are now safe to release because the embargo is over by the maintainer’s own statement, and CONN-F2 is publicly disclosed via GHSA.


Authored by binreaper, 2026-05-14. Draft for review. The verbatim quotes from Ed Tanous’s 2026-05-14 reply are reproduced with the original sender’s words intact; the rest of the email thread was not substantively quoted to keep the focus on the public-fact statements that bear on the disclosure outcome.