SAP Security Note 3759854 | CVSS 3.0 base score 7.6 (CVSS:3.0/AV:N/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:H) | Component MFG-MII | SAP Security Patch Day, 11 August 2026
At a glance
CVSS:3.0/AV:N/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:HSummary
SAP Manufacturing Integration and Intelligence stores catalog content in its database and then materialises that content as real files under the servlet container web root. Before the August 2026 patch, the file system path used for that materialisation was derived from the object name supplied by the caller of a save request and was passed to java.io.FileOutputStream with no canonicalisation and no check that the resolved location remained inside the content root. A user who already holds the content development and SSCE change authorisations could therefore cause the platform to create directories and write attacker-supplied bytes at a location of their choosing on the application server. The patch adds a containment check in front of every file write and delete on that path.
This issue was reported to SAP by an external researcher, who is credited in the SAP note. The description below is derived from the shipped pre-patch and post-patch code.
Affected products and versions
SAP Manufacturing Integration and Intelligence, runtime software component XMII, delivered in the software component archive XMII02.
The archive manifests give the precise build boundary of the two levels examined:
| Software component | Archive | keycounter |
keylocation |
pr_updateversion |
|
|---|---|---|---|---|---|
| Vulnerable | XMII 15.5 SP2 patch 0 | XMII02_0-70006251.SCA |
1000.15.5.2.0.20250508041720 |
MAIN_MII1552V_C |
MII1552V.05080417 |
| Fixed | XMII 15.5 SP2 patch 13 | XMII02P_13-70006251.SCA |
1000.15.5.2.13.20260731081708 |
MAIN_MII1552P_C |
MII1552P.07310816 |
The fix is present at patch level 13. Intermediate patch levels were not examined, so the authoritative list of affected patch levels is the one in SAP Note 3759854.
The administration software component MII_ADMIN (archive MIIADMIN02) shipped a patch in the same window, from 15.5 SP2 patch 0 to patch 6, but plays no part in this issue. The only five classes in that component that reference SSCE at all are byte identical before and after.
Technical detail
Entry point
Two servlets reach the vulnerable save. com/sap/xmii/servlet/SSCECatalog is the SSCE front door. It reads Mode, ObjectName and Content from the request, applies a small command blacklist to the content but only when the object name carries a .jsp extension, and forwards everything to the Catalog servlet (lines 40 to 61). That class is byte identical in the pre-patch and post-patch trees, so the fix is not there.
com/sap/xmii/servlet/Catalog dispatches Mode=Save to handleSave at line 191, and handleSave reads the three request parameters and calls into the shared save logic:
// Catalog.java:945-969 (unchanged by this patch)
private void handleSave(final HttpServletRequest req, ...) throws Throwable {
...
XMLHandler.outputXMLDoc(CatalogUtil.doSave(req, sClass, sObjectName, sContent, ...), (Writer)os);
Authorisation required
CatalogUtil.doSave is declared at line 708 and begins with a platform level check, UserSecurityHelper.checkFileSystemWriter(user) (CatalogUtil.java:711). The SSCE Dashboard branch then adds a dedicated UME action check:
// CatalogUtil.java:854-858 (unchanged by this patch)
else if (sClass.equalsIgnoreCase("SSCEDashboard")) {
...
if (!user.hasPermission(new UMEPermissionActionManager("SSCE", "change"))) {
throw new UserSecurityException("USER_DOES_NOT_HAVE_ROLE", (Object)user.getName());
}
This is the code level basis for the high privilege precondition and matches SAP’s PR:H rating. The flaw is not reachable anonymously.
The input under attacker control
The tainted value is the object name. Between the request and the file system it passes through exactly one normalising routine, FileConstants.checkName (FileConstants.java:179-208), called from Catalog.handleSave at line 956 and from doSave at line 729. It only corrects the file extension to match the declared class. It performs no path filtering.
saveDashboard (declared at CatalogUtil.java:952, called from lines 866, 869 and 873 inside the SSCE Dashboard branch) then derives four artifact names from that object name using plain substring arithmetic and hands each one to the content manager:
// CatalogUtil.java:957-958 (unchanged by this patch)
ContentManager.save(sObjectName, decodedMetadata.getBytes("UTF-8"), user, notify.booleanValue(), info, true);
final String htmlFileName = sObjectName.substring(0, sObjectName.lastIndexOf("."));
with the further derivations at lines 962 and 971.
Path to the sink
ContentManager.save (ContentManager.java:222) calls checkPath at line 223, and checkPath is worth reading closely, because it does not reject a badly shaped path, it repairs one:
// ContentManager.java:512-518 (unchanged by this patch)
public static String checkPath(final String fullpath) {
final String[] parts = fullpath.split("/");
if (PathBasic.isWebPath(parts) || PathBasic.isMetaPath(parts)) { return fullpath; }
final int idx = fullpath.indexOf("/");
final String newpath = fullpath.substring(0, idx) + "/" + "WEB" + fullpath.substring(idx);
The structural gate it satisfies is a single segment comparison:
// PathBasic.java:101-102 (unchanged by this patch)
public static boolean isWebPath(final String[] splitURL) {
return splitURL.length >= 2 && splitURL[1].equals("WEB");
}
Nothing after the second segment is ever inspected. save then routes to add or update, both of which re-check only isWebPath/isMetaPath, write the bytes to the database, and finish with a notification:
// ContentManager.java:134-136 and 166-168 (unchanged by this patch)
if (notify) {
sendNotificationMessage(new String[] { fullpath }, true);
}
The true argument is what makes this synchronous rather than a cluster round trip:
// ContentManager.java:494-502 (unchanged by this patch)
ServiceUpdateMessageSender.getInstance().sendMessage("CONTENT", "updateFileSystem", map);
if (blowoutLocal) { updateFileSystem(fullpaths); }
updateFileSystem converts the object name into a file system relative path and, pre-patch, used it directly:
// ContentManager.java:338-348, PRE-PATCH
public static final void updateFileSystem(final String[] fullpaths) {
for (int i = 0; i < fullpaths.length; ++i) {
final Path path = new Path(fullpaths[i]);
final String filepath = path.getWebPath();
try {
DBFileContent dbFile = FileManager.selectContent(fullpaths[i]);
if (dbFile == null) { deleteFile(filepath); }
else { writeFile(dbFile, filepath); }
getWebPath is produced by a single string substitution on the project prefix, so any segments beyond it survive untouched (PathBasic.java:144-146). The sink itself resolves that string against the web root by concatenation and creates whatever directories are missing:
// ContentManager.java:422-442 PRE-PATCH (identical body post-patch, at line 449)
final String filename = WebInfo.getRealPath(filepath);
final String path = FileConstants.getPath(filename);
final File fPath = new File(path);
createDirectory(fPath);
...
fos = new FileOutputStream(filename);
fos.write(dbFile.getBytes());
WebInfo.getRealPath is a bare concatenation onto the stored web root (WebInfo.java:80-83), which is assembled at WebInfo.java:64 as installdir + .../xapps~xmii~ear/servlet_jsp/XMII/root/.
The missing control
There was no canonicalisation of the candidate path and no check that it resolved inside the content root. The only path validation in the chain, isWebPath, inspects one fixed segment and is trivially satisfied because checkPath inserts that segment when it is absent.
One further point matters operationally. updateFileSystem is also the handler for the cluster message of the same name:
// ServiceHandlerBean.java:388-397 (unchanged by this patch)
if (function.equals("updateFileSystem")) {
...
else { ContentManager.updateFileSystem(paths); }
The materialisation therefore happens on every node in the cluster, not only on the node that accepted the request.
What the patch changes
Exactly one file in the runtime changes for this issue. updateFileSystem gains a guard as its first action per path, and a new method implements the guard.
// ContentManager.java:340-344, POST-PATCH
public static final void updateFileSystem(final String[] fullpaths) throws Exception {
for (int i = 0; i < fullpaths.length; ++i) {
final Path path = new Path(fullpaths[i]);
final String filepath = path.getWebPath();
checkForDirectoryTraversal(filepath);
// ContentManager.java:361-383, POST-PATCH (literal sequences elided)
public static void checkForDirectoryTraversal(final String path) throws Exception {
ContentManager.LOG.debug("Checking diectory traversal");
if (path.contains(...)) { // parent directory sequences plus one double encoded form
ContentManager.LOG.error("Directory Traversal detected; blocking file blowout");
throw new Exception("Directory Traversal detected; blocking blowout of files");
}
final String basePath = WebInfo.getRealPath("CM");
final java.nio.file.Path canonicalBase = Paths.get(basePath, ...).toRealPath(LinkOption.NOFOLLOW_LINKS);
...
boolean safe = candidate.startsWith(canonicalBase);
...
safe = canonicalPath.startsWith(new File(basePath).getCanonicalPath());
Two java.nio.file imports (LinkOption and Paths) are added at the head of the file, and updateFileSystem gains throws Exception on its signature.
Three properties of the fix are worth noting. The guard sits before the database lookup, so it covers the delete branch as well as the write branch. It sits in the method that both the local call and the cluster message handler use, so it covers remote nodes. And it anchors containment to the canonical real path of the CM content root rather than to string inspection alone, using two independent resolutions (java.nio.file and java.io.File) that must both agree.
The input handling was not changed. SSCECatalog, CatalogUtil, PathBasic and WebInfo are byte identical before and after. This is containment at the sink, not sanitisation at the source, which is the correct choice here but means the guard is the single point of protection.
Verification for defenders
1. Confirm the component version. The relevant component is XMII, not the MII administration component. In the SAP NetWeaver Administrator, open the Java system information or component information view and read the version of sap.com/XMII. The fixed build examined here reports release 15.5, service level 2, patch level 13, with build timestamp 20260731081708. Anything below the patch level named in Note 3759854 for your support package should be treated as affected. Do not rely on the About dialog string alone; the internal support package constant in the runtime jar moves from 15.5.1 to 15.5.2 in this build but is not referenced by any code in the shipped component, so it is a build marker rather than a reliable patch indicator.
2. Confirm every node in the cluster is patched. Because the file system write is driven by a cluster message handled independently on each node, a partially patched cluster still performs the unguarded write on the nodes that lag. Check the component version on every server node, not just one.
3. Review who holds the required authorisations. Reaching the vulnerable code requires both the general MII content development authorisation and the UME action change in the SSCE action group. Enumerate the roles that carry that action and confirm the assignment list matches the set of people who genuinely design SSCE dashboards. On most production systems that set should be small or empty; developers rarely need it on a production instance at all.
4. Confirm the guard is live from the log. On a patched system the guard emits a debug line before every materialisation and an error line when it blocks. Raise the log level for location com.sap.xmii.Illuminator.content.ContentManager to debug on one node, save any ordinary SSCE Dashboard through the product user interface, and confirm the entry Checking diectory traversal appears. Its presence proves the corrected class is the one running, and it requires no crafted input. The misspelling is SAP’s and is what the log actually contains.
Detection
There is usable log evidence, but it is limited and it is on the defensive side of the patch.
On a patched system, any blocked attempt produces an error entry from com.sap.xmii.Illuminator.content.ContentManager reading Directory Traversal detected; blocking file blowout. Presence of that message confirms the guard is active and also flags an actual attempt. It is worth alerting on.
On an unpatched system there is no dedicated message, because nothing was checked. Three indirect sources exist:
ContentManagerlogsWriting file <absolute path>at debug level immediately before the write (ContentManager.java:426). Where debug logging for that location was enabled, historical entries whose absolute path does not sit under the.../XMII/root/CM/tree are direct evidence of a write outside the content root.updateFileSystemlogsUnable to process file <object name>at error level when the write throws. Object names in those entries that contain parent directory segments are suspicious regardless of whether the write succeeded.- The catalog itself records the object name in the MII database before the file is written, and that record persists even if the file write failed. Reviewing stored content object names for parent directory segments covers attempts that left no file behind.
On the file system, look for unexpected files under the servlet container root of the MII application outside the CM content directory, particularly files whose timestamps cluster with catalog save activity. Note that the write path creates missing parent directories on the way, so unexplained new directories are as much of a signal as the files themselves.
References
- SAP Security Note 3759854: https://me.sap.com/notes/3759854
- CVE-2026-44763
- SAP Security Patch Day, August 2026
- Affected component: XMII (SAP MII runtime), SAP MII 15.5
- Fix present in XMII 15.5 SP2 patch level 13, build 20260731081708
This is one of six SAP MII vulnerabilities corrected on the August 2026 patch day. The full cycle, including the other 19 notes, is covered in the SAP Security Patch Day August 2026 advisory.
Custom code carries the same defect classes
Missing authorization checks, injection into dynamically assembled calls and unvalidated file paths are the recurring findings in customer ABAP as well as in vendor code. The RedRays ABAP Code Scanner reads your own objects and reports them at the line that causes them.
This 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.
