Products
| Product | |
| 1 | H3C HDM Family-A G3 |
| 2 | H3C HDM Family-A G5 |
CVE
CVE-2026-65197Executive summary #
H3C HDM Family-A verifies the RSA signature of a firmware image against a public key it reads from the mutable file /conf/public.pem. That file is writable by an authenticated Redfish client through the UpdateService.UploadSignKey action, which stores the uploaded body verbatim after validating only that the multipart filename ends in .pem. An administrator, or any account with firmware-update privilege, can therefore replace the trust anchor with their own key, sign a malicious image with the matching private key, and have it pass verification. Family-A has no hardware root-of-trust (no PFR) to catch the substituted key.
This issue is an instance of CWE-667 (Improper Locking), has an estimated CVSSv3.1 score of CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:N (3.1, Low), and current SSVC vectors of "Exploitation: PoC" and "Technical Impact: Total."
Technical details #
The Family-A firmware verification routine is VerifyImageSign in usr/local/lib/libflash.so.2.23.0. It selects the public-key file with a two-step access() probe and uses whichever exists, checking /conf/public.pem first and only falling back to the image-embedded key at /tmp/public.pem:
<i>/* libflash.so.2.23.0 VerifyImageSign (site 0x1c088) */</i>
iVar5 = access("/conf/public.pem", 0); <i>/* ptr 0x1c2a4 -> 0x20ab0 */</i>
pcVar6 = "/conf/public.pem";
if ((iVar5 == 0) ||
(iVar5 = access("/tmp/public.pem", 0), pcVar6 = "/tmp/public.pem", iVar5 == 0)) {
iVar7 = VerifySignatureWithDigest(pcVar6, digest, ...); <i>/* PEM_read_RSA_PUBKEY + EVP_PKEY_verify */</i>
}
The file /conf/public.pem is written by the authenticated Redfish action UpdateService.UploadSignKey, implemented in usr/local/redfish/redfish/upload-file.lua (lines 48-62) and routed from extensions/routes/update_service_routes.lua:
elseif action == "UpdateService.UploadSignKey" then
if req_args.pem_file ~= nil then
...
local parsed_body = req_args.pem_file[1][1]
local fname = req_args.pem_file[1]["content-disposition"]["filename"]
if not string.match(fname, '(.-%.pem)$') then <i>-- only a ".pem" filename-suffix check</i>
... InvalidSignKeyFormat
else
local f = io.open(tmp_name .. "public.pem", "w")
f:write(parsed_body) <i>-- attacker body, unvalidated</i>
...
os.execute("cp ".. tmp_name .. "public.pem " .. "/conf/") <i>-- -> /conf/public.pem</i>
The only validation is the filename suffix; the PEM body is written verbatim and copied into the persistent /conf JFFS2 partition, so the substitution survives the subsequent update. There is no check that the uploaded key is an authorized vendor key.
Upload a replacement public key through the OEM action. The shipped handler returns HTTP 204 and writes the supplied body to /conf/public.pem:
curl -sk -X POST \
-H "X-Auth-Token: $TOKEN" \
-F '[email protected];filename=x.pem' \
https://example.lan/redfish/v1/UpdateService/Actions/Oem/UpdateService.UploadSignKey
HTTP/1.1 204 No Content
Sign an HDM image with the matching private key and submit it through the normal update flow. VerifyImageSign selects /conf/public.pem before the image-embedded /tmp/public.pem, so the attacker signature passes the same verification path used for vendor images.
On HDM2, FLASHER_GetRSAPubKey reads /conf/flasher/RSA1024_public.pem and /conf/flasher/RSA4096_public.pem, but the UploadSignKey action does not ship. No equivalent network write path is established for HDM2.
Attacker value #
Anyone with the firmare-update privilege (typically, administrator) can flash a persistent firmware implant or downgrade to a known-vulnerable version. The installed image becomes the running BMC firmware and controls the platform-management functions exposed to the host. This vulnerability is primarily useful to an attacker seeking persistent access after obtaining firmware-update privileges through other means.
Credit #
This issue was discovered by HD Moore with the help of AI tools. Disclosure was coordinated by Tod Beardsley.
Timeline #
- 2026-07-18: Initial findings discovered and documented
- 2026-07-21: CVE-2026-65197 reserved
- 2026-08-05: Disclosed to CISA (VU#415695)
- 2026-08-28: CISA opened case VU#759570
- 2026-10-13: Public disclosure