SAP Security Note 3759472 | CVSS 3.0 base score 9.8 (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) | Component BC-CST-MS | SAP Security Patch Day, 8 September 2026
At a glance
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HSummary
The SAP message server keeps one process-global record of where the ASCS gateway lives. Any peer that can open a TCP connection to a message server port can write that record. No credentials stand in the way, because the protocol defines none at this layer. The registration opcode MS_ASCS_GW_LOGON, opcode 82, reaches its handler in MsSProcOpcode with no source validation. Once the supplied values parse, the server stores them in the global record, marks that record valid, and pushes the new endpoint to every subscribed internal client. CWE-306, missing authentication for a critical function.
The handler performs exactly four operations on the wire data: it parses the values into the global structure gAscsGw, sets gAscsGwValid to 1, broadcasts the endpoint, replies OK, and does nothing else. On kernels up to and including PL71 no logoff cleanup exists, so the poisoned entry outlives the connection that created it and stays until the message server restarts. With no duplicate check anywhere, the last writer wins, including over the genuine gateway’s own registration.
This advisory does not establish a code-execution impact. Lab testing on kernel 9.16 PL50 reproduced the registration remotely and without credentials. The entry survived the disconnect. The demonstrated chain ends at a poisoned, broadcast and persistent registry entry. An xref scan covered gwrd, disp+work, icman, sapwebdisp and enq_replicator in PL71 and PL100, and it confirmed that gwrd’s trusted-IP array does not draw from this record. The known consumers are the push to internal clients, the gateway monitor display, and MS_ASCS_GW_STATUS towards the ABAP layer, and through that opcode the ABAP layer retrieves the gateway host, its port and its SSL port. Those three values are what a poisoned record feeds into application code.
Affected products and versions
The vulnerable code sits in msg_server, the SAP NetWeaver and S/4HANA ASCS message server, kernel branch 9.16, source tree //bas/916_REL/src/krn/ms/. Three levels came under the disassembler: PL50 on a live lab system, plus PL71 and PL100 taken from the corresponding kernel packages. PL50 and PL71 carry the vulnerable handler. PL100 carries the correction, which puts the fix somewhere between PL72 and PL100. Those three levels are the whole of the sample here, so this states what was observed rather than the definitive release list. SAP Note 3759472 is authoritative for the affected releases and the required patch levels, and FAQ Note 3776167 accompanies it.
The registry itself is new attack surface, arriving with kernel 9.16 and the S/4HANA 2023 central gateway architecture. Nothing older was examined here, and Note 3759472 governs those releases. Per the note the correction ships as a kernel hotfix. SAP provides no workaround.
Both message server listeners answer on every interface by default. The external port sapms<SID> (36xx, for example 3601) and the internal port rdisp/msserv_internal (39xx, for example 3901) both bind 0.0.0.0. Either one is enough.
Technical detail
The feature
Kernel 9.16 added a discovery mechanism for the gateway integrated in the ASCS instance. That component publishes its endpoint in the message server, meaning its host, its internal and SSL ports, and its process id. The message server hands that endpoint to any component that asks through MS_ASCS_GW_STATUS, and pushes it to every subscribed internal client through MsSSndAscsGwInfo. The record is a directory the rest of the kernel trusts.
Reachability
A message server frame carries a fixed-size header. When the frame targets the server itself, an opcode block follows and selects the handler. One header field is a client-type byte that the peer sets about itself, and the server later reads that byte to decide whether the connection is an internal component entitled to pushed topology updates. Another field selects the opcode and routes the frame into the dispatcher below.
Connections open with a login frame that carries a name and no credentials. The one gate anywhere on this path restricts which opcodes the server accepts before login. Opcode 82 sits outside that pre-login anonymous set, which means a name-only login always precedes the registration. That ordering is a sequencing rule, not an authentication boundary. No ACL check stands in front of this opcode in the path examined here, and the message server ACL file ms/acl_info limits which hosts reach the port and stops there.
Dispatch happens in MsSProcOpcode, in the PL71 binary:
; r15 = per-connection receive buffer
movzx ebx, byte ptr [r15+<hdr>] ; opcode selector
cmp bl, <max> ; bounded switch
ja def_unknown_opcode ; out of range, rejected
lea r11, jpt_opcode_table
jmp rcx ; case 82 = MS_ASCS_GW_LOGON
This advisory redacts absolute addresses, along with the offsets into the receive buffer and into the connection structure. None of that is needed to see the point.
Unused selector numbers fall through to the default branch and never reach a handler. Opcode 82 has a live case. MsIOpcodeText names the neighboring entries MS_ASCS_GW_LOGON, MS_ASCS_GW_STATUS and MS_ASCS_GW_KEEPALIVE, so the registry has a publish, a read and a keep-alive operation.
The defect
The handler is short enough to publish in full. This is the complete case body in PL71, msxxserv.c:
; case 82 (MS_ASCS_GW_LOGON), PL71
mov esi, [rbp+<len>] ; payload length
lea rdi, [r15+<payload>] ; the peer's value bytes
lea rdx, _ZL7gAscsGw ; process-global gateway record
call MsAscsGwReadPayload ; parse straight into the global
test al, al
jnz loc_accept ; parse ok, so accept
The accept path then commits and distributes the result:
loc_accept:
xor edi, edi ; msadm = NULL, so broadcast
mov cs:_ZL12gAscsGwValid, 1 ; record now valid
call _ZL16MsSSndAscsGwInfoP7MSADM_s
mov ebx, 52h ; reply opcode
Only a parse failure rejects the frame. That branch returns rc -14, which makes a malformed payload the single thing the server turns away before the fix. There is no client-type or logon-state gate for this opcode, no port-of-arrival check, no source-host check, and no duplicate-registration check. On PL71 and below nothing cleans up when the registering client disconnects, so gAscsGwValid takes the value 1 and never clears outside the logon path. The string ASCS gateway logoff does not exist in PL50 or PL71 at all.
Why the parser does not save it
The parser is careful work. It walks a small table of tagged fields and reads one field per value the gateway publishes: the internal port, the external port, the process id, and a raw address blob. Each reader has a fixed width and checks its length, so the parser is memory-safe. This is a trust bug, not a memory-corruption bug. Every field travels verbatim from the wire into the global record, on the assumption that only the real ASCS gateway would send this opcode. With no staging copy in between, that record becomes the answer the message server gives to every component that asks.
Why the nearby check does not help
A check does exist nearby, and it guards something else. The subscription flag that decides who receives pushed updates comes from the client-type byte of the login frame:
; MsSLoginClient, PL71
mov r11, [r15+<frame>] ; the login frame
movzx eax, byte ptr [r11+<type>] ; client-type byte, self-declared
mov [r15+<flag>], al ; subscription flag
A connection is internal because it says it is. On the external port a login claiming internal status does draw MSEACCESSDENIED, error number 236, so that gate is real. What it gates is classification and not registration, and it rests entirely on network reachability, since the internal port binds all interfaces. Frames carrying opcode 82 are accepted on either port.
The genuine caller looks identical
The legitimate ASCS gateway sends this same opcode from its collect thread, where GwThread2Collect calls MsIAscsGwLogon. It fills the frame with its own gw_port_no and gw_port_ssl_no values and its own getpid(). Every one of those is a value the sender chose.
At the point of dispatch the genuine registration and a fabricated one are indistinguishable. Reaching the defect requires only TCP access to a message server port and the ability to speak the wire protocol.
What the patch changes
PL100 retrofits the missing gates. MsSProcOpcode grows from 14,296 to 14,916 instructions, and the new calls sit only inside the guard for case 82: NiLocalCheck, NiHostToAddr, NiMyHostName and MsSDisconnect. MsSAdmDisconnect picks up the gateway logoff cleanup, and MsSSndAscsGwInfo changes structure. Five new trace strings in msxxserv.c describe the outcome of each new path:
%s: ASCS gateway is already logged on (C%d, hdl %d, %s). Reject new logon from %s
%s: an attempt to log on as ASCS gateway is not made on the internal port (C%d, hdl %d, %s). Reject new logon from %s
%s: an attempt to log on as ASCS gateway is not made from the ASCS host (C%d, hdl %d, %s). Reject new logon from %s
%s: ASCS gateway logon from %s (C%d, hdl %d)
%s: ASCS gateway logoff (C%d, hdl %d, %s)
The __LINE__ values place the source-host gate at msxxserv.c lines 12410 and 12437, which makes it one contiguous new block:
; PL100 case 82 guard, excerpt
cmp dword ptr [r13+0], 1
jz short loc_check_local
cmp cs:_ZL28ssl_4_internal_communication, 1 ; mTLS on internal links
jz loc_exempt ; exempt path
loc_check_local:
mov rdi, r15 ; the connection
call NiLocalCheck ; peer on the local host?
test al, al
jnz loc_proceed ; local, so proceed
lea rsi, aSAnAttemptToLo ; "not made from the ASCS host"
mov dword ptr [rbp+<rc>], 0FFFFFFF2h ; rc -14, reject
Local registration still succeeds, as it must, since the genuine gateway runs on the ASCS host. In the lab PL100 refused a remote registration and logged the source-host message above. The duplicate gate closes takeover of a live entry; the internal-port requirement closes the external port as an avenue.
The mTLS exemption, keyed on ssl_4_internal_communication, states the intended trust model plainly: authentication for this opcode is meant to come from network-level trust, meaning the local host or mutual TLS. The pre-fix code made exactly that assumption without enforcing it.
Verification for defenders
Start with the kernel patch level of the ASCS instance, that is the level of the running kernel package that ships msg_server. On branch 9.16 anything at or below PL71 carries the vulnerable handler, and PL100 carries the correction. Where between PL72 and PL100 the fix first landed was not determined here, so Note 3759472 remains authoritative for the level your release requires. SAP Note 3747649, CVE-2026-44756, corrects a separate unauthenticated flaw in the ICM and the standalone Web Dispatcher of the same kernel. It shipped on the same patch day, so plan one kernel patch window and check both required levels together.
The binary settles the question without test traffic. Search the msg_server executable for the literal the correction introduced, for example with strings msg_server | grep -F 'ASCS gateway logoff'. A corrected binary contains ASCS gateway logoff. The PL50 and PL71 binaries do not contain that string at all. That result came out of the binaries rather than out of the note. Match the exact phrase and not the substring ASCS gateway, since an unpatched binary still carries the MsAscsGwReadPayload: ASCS gateway: ... trace text.
Checking the running system
Then look at the entry that is currently registered. The gateway monitor display shows the registered ASCS gateway, with its host and its process id. Compare both against the real ASCS gateway of that system. A host outside that instance, or a process id that matches no running gateway, means another peer wrote the record. This check sends no test traffic, and it works on an unpatched system.
Restricting exposure and clearing a rogue entry
Check exposure next. Confirm which networks can reach tcp/36xx and tcp/39xx. Until the kernel carries the correction, restrict both to ASCS and application server hosts at the network layer. These are component trust channels, not user-facing services, and a restrictive rule there rarely disturbs operation.
On an unpatched system that may have taken a rogue registration, restart the message server. Below the fixed level there is no logoff handler, and the entry otherwise stays in memory indefinitely.
Detection
The dev_ms trace of the ASCS instance is where a registration shows up. On the PL50 lab system a successful remote registration logged MsAscsGwReadPayload: ASCS gateway: host/PID = <host>/<pid>, port int/ext = <a>/<b>, and on the PL71 sandbox the dispatcher logged a matching MsSProcOpcode: MS_ASCS_GW_LOGON ... ok line. Expect one or both, depending on kernel level and trace level, and grep the trace for the opcode name. Alert on any such line whose host field does not carry the ASCS host name.
There is also the login that had to come first. A name-only login always precedes the registration, which puts the client name the attacker chose on the connection before the opcode arrives. Where the trace level records that login, correlate the name with the registration by connection number.
On a corrected kernel the same trace carries ASCS gateway logon from <ip>. Treat a foreign address there as an incident. The three rejection messages record blocked attempts, which is early warning that someone is probing this opcode.
An unpatched system has one tell that surfaces in operations instead of in a trace: components that make an anonymous request for the gateway status begin receiving MSOP_ACCESS_DENIED on a system where nobody deployed a gateway architecture. Treat that as evidence of a registration nobody made on purpose.
At the network level, watch for connections to tcp/36xx or tcp/39xx from addresses outside the ASCS and application server ranges. The entry persists after the client disconnects, so one short-lived connection does the whole job. Alert on the first connection from an unexpected source rather than on volume.
References
- SAP Security Note 3759472, the security note itself
- SAP Note 3776167, the FAQ that accompanies Note 3759472
- CVE-2026-58240 in the CVE catalog
- SAP Security Patch Day September 2026, the full cycle this note shipped in
- SAP Note 3747649, CVE-2026-44756, the Extended Passport flaw corrected in the same kernel on the same day
This advisory describes the vulnerability and the correction as observed in the shipped kernel binaries.
This is one of four HotNews notes corrected on the September 2026 patch day. The full cycle, including the other 19 notes, is covered in the SAP Security Patch Day September 2026 advisory.
Know which of your systems is still on a vulnerable level
Kernel flaws like this one are closed by a patch level, not by a configuration change, so the work is knowing where you stand. The RedRays Security Platform inventories your SAP hosts, reports missing SAP Security Notes per system and tracks the gap until it closes.
See the RedRays Security PlatformThis advisory summarises publicly relevant facts about an SAP Security Note that SAP has already released, together with an analysis of the shipped correction. It is not a substitute for the note itself; customers should always refer to the original note in the SAP Support Portal for authoritative guidance on affected releases and required patch levels. No exploit code is published. RedRays is an independent SAP security vendor and is not affiliated with SAP SE.




