FlawOpen

flawopen.com/Teardowns/cve-2026-21589-atlassian-arbitrary-file-access

● CVE-2026-21589 · CVSS v4 9.3 · Critical
FlawOpen Security Research

CVE-2026-21589: Atlassian Data Center File Access

Atlassian’s advisory and the October 12 MS-ISAC report establish the risk and urgency, but they do not establish exploitation of a particular customer instance.[1], [4], [5], [10]

💡 Plain English Explainer (ELI5)

Imagine a library desk that accepts a shelf location from a visitor. The visitor cannot browse every shelf, but if they already know a particular book’s location, a broken boundary check may let the clerk fetch it from outside the public reading area. The fix is to keep every resolved request inside the approved area and to install the vendor’s product-specific patch.[1], [4]

Core Concepts & Subsystem Terms

Arbitrary file access
A request can read or otherwise access files outside the intended request scope. Atlassian describes this issue as access to specific files under the web application root, not unrestricted filesystem access.[1], [4]
Path traversal
Path components are interpreted in a way that crosses an intended directory boundary. Atlassian’s mitigation targets traversal markers and encoded variants in request URLs.[1], [4]
Data Center
Atlassian’s self-managed product deployment model. The advisory lists eight affected products; Atlassian says its Cloud products have been patched.[1]
Known-path prerequisite
The attacker must know the target file’s exact name and path in advance; the advisory says the flaw does not list or enumerate directories.[1]
CVSS 4.0
Atlassian rates this issue Critical, CVSS 4.0 9.3, using its own assessment.[1]
Router.unescapeSlashes()
The shared library converts the escaped path separator :: back to / before resource handling; this decoding boundary is central to watchTowr’s source analysis.[1], [4]
ResourceServingHelpers
The helper resolves a resource name and forwards the remaining relative path to createResourceWithRelativePath().[1], [4]
Crowd credential chain
A configuration-dependent follow-on: a readable Crowd properties file may expose an application password; network reachability and authorization determine whether it can be used.[4], [5]
CWE-22
FlawOpen’s editorial mapping for the path-traversal weakness: a path is not adequately constrained to its intended directory. Atlassian’s advisory does not assign a CWE.[7]

Framework mappings

  • FlawOpen’s conceptual mapping to MITRE CWE; no vendor or CVE record assigned this weakness ID to this research demonstration. CWE-22

Root Cause Analysis

watchTowr’s analysis of Atlassian’s shared atlassian-plugins-webresource library identifies the path-decoding boundary. Router.unescapeSlashes() converts :: into /; resource routes apply it to attacker-controlled path components before calling resource-serving handlers. The downstream lookup splits the resource name and passes the remaining path to createResourceWithRelativePath(), where the traversal check can be bypassed by the alternate separator representation. Researchers demonstrated reads of files inside the Tomcat application context on Jira, Confluence, and Bitbucket; Atlassian lists eight affected products. Their report says the traversal did not escape the Tomcat context. In a conditional chain, a readable WEB-INF/classes/crowd.properties may expose a plaintext Crowd application password; impact depends on the deployed configuration and whether Crowd is reachable and accepts that application credential. BleepingComputer reports that Previdian sensors saw exploitation attempts within hours of the public technical report and PoC. That is evidence of attempts against honeypots, not proof of successful access to customer systems. The exact vulnerable Java excerpts below are quoted from watchTowr’s report. The hardening example is a general containment pattern, not Atlassian’s unpublished patch source; install the product-specific vendor fix.[1], [4], [5]

Step-by-Step Attack Flow

Step 1

A shared decoder transforms the path

Atlassian’s web-resource router maps the alternate :: separator back to / before serving a resource. The decoding is part of normal resource routing, not authentication.[1], [4]

Step 2

Resource lookup receives the decoded path

The resource-serving helper walks path components and forwards the remaining relative path to the resource factory. The alternate separator can evade assumptions made by the traversal check.[4]

Step 3

Files within the application context become readable

watchTowr demonstrated this in Jira, Confluence, and Bitbucket. The report states its tested traversal could not escape the Tomcat application context; do not generalize it to arbitrary host files.[1], [4]

Step 4

A configuration-dependent identity chain may follow

If a readable Crowd configuration contains the application password and Crowd is reachable with sufficient permissions, that credential may enable access to identity-management functions. Network allowlists and deployment settings affect this stage.[4], [5]

Step 5

Observed attempts raise urgency; they do not prove compromise

On October 12, the Center for Internet Security’s MS-ISAC advisory said there were reports of CVE-2026-21589 being exploited in the wild. It did not identify a victim or publish incident artifacts, so report this as an MS-ISAC-reported exploitation signal, not confirmed access to a named customer. Atlassian’s October 5 advisory said its investigation had not found evidence of exploitation in its Cloud products, which it said had been patched. That finding was scoped to Cloud and does not contradict MS-ISAC’s report about exploitation of the vulnerability; neither statement establishes what happened to a particular instance.[1], [10]

Source Code: Flaw vs. Secure Implementation

The vulnerable excerpt is the source-level path transform published by watchTowr for the affected web-resource library; it is not an exploit request. The lower example is a general Java defense, not Atlassian’s patch: traverse from a trusted root through SecureDirectoryStream handles, reject symbolic links at every component, and cap bytes read. The caller must supply a once-decoded relative path and a SecureDirectoryStream rooted in a trusted directory. Filesystem providers may not support SecureDirectoryStream, so fail closed if unavailable. Install the product-specific vendor fix. This example requires a trusted resource tree whose contents and ancestors are not writable by untrusted principals and that contains only regular files; it does not independently verify file type. The byte cap and bounded no-progress handling limit reads, but this is not a universal file-serving routine. The caller must provide the once-decoded relative path and retain a trusted root stream. Filesystem-provider support is optional; fail closed if SecureDirectoryStream is unavailable.[4], [8]

Source excerpt: com.atlassian.plugin.webresource.impl.http.Router.java

UNPATCHED FLAW
public static String unescapeSlashes(String string) {
    return string.replaceAll("::", "/");
}
HARDENED SECURE PATCH
static byte[] readUnder(SecureDirectoryStream<Path> root, Path relative, int maxBytes) throws IOException {
    if (relative.isAbsolute() || relative.getNameCount() == 0 || maxBytes < 0) {
        throw new SecurityException("Expected a relative path and non-negative limit");
    }
    List<SecureDirectoryStream<Path>> opened = new ArrayList<>();
    SecureDirectoryStream<Path> parent = root;
    try {
        for (int i = 0; i < relative.getNameCount() - 1; i++) {
            Path part = relative.getName(i);
            if (part.toString().isEmpty() || part.toString().equals(".") || part.toString().equals("..")) {
                throw new SecurityException("Invalid path component");
            }
            parent = parent.newDirectoryStream(part, LinkOption.NOFOLLOW_LINKS);
            opened.add(parent);
        }
        Path leaf = relative.getFileName();
        if (leaf.toString().isEmpty() || leaf.toString().equals(".") || leaf.toString().equals("..")) {
            throw new SecurityException("Invalid file name");
        }
        try (SeekableByteChannel channel = parent.newByteChannel(
                leaf, Set.of(StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS))) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ByteBuffer buffer = ByteBuffer.allocate(8192);
            int total = 0;
            int zeroReads = 0;
            while (true) {
                int count = channel.read(buffer);
                if (count == -1) break;
                if (count == 0) {
                    if (++zeroReads >= 3) throw new IOException("Resource read made no progress");
                    continue;
                }
                zeroReads = 0;
                if (count > maxBytes - total) throw new IOException("Resource exceeds size limit");
                out.write(buffer.array(), 0, count);
                total += count;
                buffer.clear();
            }
            return out.toByteArray();
        }
    } finally {
        for (int i = opened.size() - 1; i >= 0; i--) opened.get(i).close();
    }
}

Engineering & System Hardening Checklist

Sources