CVE-2024-21683: RCE in Confluence Data Center and Server

Understanding Broken Object Level Authorization
Understanding Broken Object Level Authorization (BOLA) in API Security
October 30, 2024
Mitigating API Injection Attacks with Input Validation Technique
Mitigating API Injection Attacks with Input Validation Technique
November 5, 2024

October 30, 2024

CVE-2024-21683 is a Remote Code Execution (RCE) vulnerability discovered in Confluence Data Center and Server, a popular collaboration tool developed by Atlassian. Confluence is widely used for knowledge management, documentation, and team collaboration, both as a cloud service and as a self-hosted solution for enterprises. The vulnerability exists in versions of Confluence that use the RhinoLanguageParser class, which improperly exposes Java classes in the scripting environment, leading to potential RCE. The vulnerability is triggered when user-controlled input is evaluated within the Rhino scripting engine, allowing attackers to inject arbitrary code.

What is Confluence ?

Confluence is a web-based collaboration tool developed by Atlassian, used primarily for knowledge management, documentation, and team collaboration. It allows teams to create, share, and collaborate on content such as project documentation, meeting notes, and process guidelines in a centralized space. With features like real-time editing, task management, and integrations with other Atlassian tools like Jira, Confluence helps teams organize information, streamline workflows, and improve communication. It is available as both a cloud service and a self-hosted solution for enterprise use.

Lab Setup

For the lab setup, We would just need to download the vulnerable version and for this lab I am using 8.9.0:

  • Download the app

wget https://product-downloads.atlassian.com/software/confluence/downloads/atlassian-confluence-8.9.0.tar.gz

  • Extract the file

tar -xvzf atlassian-confluence-8.9.0.tar.gz

  • Add debugging option ( for linux )

# At the end of bin/setenv.sh add the following

CATALINA_OPTS=”-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:1337 $CATALINA_OPTS”

  • Add the debugging option ( for windows )

set CATALINA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:1337 %CATALINA_OPTS%

  • Run the app

sudo ./start-confluence.sh

Patch Diffing

For patch diffing, we will use 8.9.0 (which is vulnerable) and 8.9.1 (which is not vulnerable). As mentioned in this quick note, the changes were made in the RhinoLanguageParser class. The RhinoLanguageParser, when used with initStandardObjects, can lead to remote code execution if an attacker can control the script variable in the following call:

cx.evaluateString(scope, script, “ParserScript”, 0, (Object)null);

We can find the RhinoLanguageParser class under the confluence/WEB-INF/atlassian-bundled-plugins/com.atlassian.confluence.ext.newcode-macro-plugin-x.x.x.jar path. So, we obtained the two JAR files—one from the vulnerable version and one from the patched version—to compare the differences.

Inside IntelliJ, we create a new project, then go to File > Project Structure:

Next, select New Project Library and add the two JAR files.

Finally, click Apply and OK. After that, you can find the two files under External Libraries on the left side:

Select both files and press CTRL+D to perform a diff on the files.

When we inspect the RhinoLanguageParser class in both versions, we can confirm the changes:

The change was made by replacing the usage of initStandardObjects with initSafeStandardObjects.

The Analysis

Let’s analyze the functions used in the patch, Let’s go through both functions to understand what is happening in each.

public static ScriptableObject initSafeStandardObjects(Context cx, ScriptableObject scope, boolean sealed) {

        if (scope == null) {

            scope = new NativeObject();

        } else if (scope instanceof TopLevel) {

            ((TopLevel) scope).clearCache();

        }

        scope.associateValue(LIBRARY_SCOPE_KEY, scope);

        new ClassCache().associate(scope);

        BaseFunction.init(scope, sealed);

        NativeObject.init(scope, sealed);

        Scriptable objectProto = ScriptableObject.getObjectPrototype(scope);

        Scriptable functionProto = ScriptableObject.getClassPrototype(scope, “Function”);

        functionProto.setPrototype(objectProto);

        if (scope.getPrototype() == null) {

            scope.setPrototype(objectProto);

        }

        NativeError.init(scope, sealed);

        NativeGlobal.init(cx, scope, sealed);

        NativeArray.init(scope, sealed);

        if (cx.getOptimizationLevel() > 0) {

            NativeArray.setMaximumInitialCapacity(200000);

        }

        NativeString.init(scope, sealed);

        NativeBoolean.init(scope, sealed);

        NativeNumber.init(scope, sealed);

        NativeDate.init(scope, sealed);

        NativeMath.init(scope, sealed);

        NativeJSON.init(scope, sealed);

        NativeWith.init(scope, sealed);

        NativeCall.init(scope, sealed);

        NativeScript.init(scope, sealed);

        NativeIterator.init(cx, scope, sealed);

        NativeArrayIterator.init(scope, sealed);

        NativeStringIterator.init(scope, sealed);

        NativeJavaObject.init(scope, sealed);

        NativeJavaMap.init(scope, sealed);

        boolean withXml = cx.hasFeature(6) && cx.getE4xImplementationFactory() != null;

        new LazilyLoadedCtor(scope, “RegExp”, “org.mozilla.javascript.regexp.NativeRegExp”, sealed, true);

        new LazilyLoadedCtor(scope, “Continuation”, “org.mozilla.javascript.NativeContinuation”, sealed, true);

        if (withXml) {

            String xmlImpl = cx.getE4xImplementationFactory().getImplementationClassName();

            new LazilyLoadedCtor(scope, “XML”, xmlImpl, sealed, true);

            new LazilyLoadedCtor(scope, “XMLList”, xmlImpl, sealed, true);

            new LazilyLoadedCtor(scope, “Namespace”, xmlImpl, sealed, true);

            new LazilyLoadedCtor(scope, “QName”, xmlImpl, sealed, true);

        }

        if ((cx.getLanguageVersion() >= 180 && cx.hasFeature(14)) || cx.getLanguageVersion() >= 200) {

            new LazilyLoadedCtor(scope, NativeArrayBuffer.CLASS_NAME, “org.mozilla.javascript.typedarrays.NativeArrayBuffer”, sealed, true);

            new LazilyLoadedCtor(scope, “Int8Array”, “org.mozilla.javascript.typedarrays.NativeInt8Array”, sealed, true);

            new LazilyLoadedCtor(scope, “Uint8Array”, “org.mozilla.javascript.typedarrays.NativeUint8Array”, sealed, true);

            new LazilyLoadedCtor(scope, “Uint8ClampedArray”, “org.mozilla.javascript.typedarrays.NativeUint8ClampedArray”, sealed, true);

            new LazilyLoadedCtor(scope, “Int16Array”, “org.mozilla.javascript.typedarrays.NativeInt16Array”, sealed, true);

            new LazilyLoadedCtor(scope, “Uint16Array”, “org.mozilla.javascript.typedarrays.NativeUint16Array”, sealed, true);

            new LazilyLoadedCtor(scope, “Int32Array”, “org.mozilla.javascript.typedarrays.NativeInt32Array”, sealed, true);

            new LazilyLoadedCtor(scope, “Uint32Array”, “org.mozilla.javascript.typedarrays.NativeUint32Array”, sealed, true);

            new LazilyLoadedCtor(scope, “Float32Array”, “org.mozilla.javascript.typedarrays.NativeFloat32Array”, sealed, true);

            new LazilyLoadedCtor(scope, “Float64Array”, “org.mozilla.javascript.typedarrays.NativeFloat64Array”, sealed, true);

            new LazilyLoadedCtor(scope, NativeDataView.CLASS_NAME, “org.mozilla.javascript.typedarrays.NativeDataView”, sealed, true);

        }

        if (cx.getLanguageVersion() >= 200) {

            NativeSymbol.init(cx, scope, sealed);

            NativeCollectionIterator.init(scope, “Set Iterator”, sealed);

            NativeCollectionIterator.init(scope, “Map Iterator”, sealed);

            NativeMap.init(cx, scope, sealed);

            NativePromise.init(cx, scope, sealed);

            NativeSet.init(cx, scope, sealed);

            NativeWeakMap.init(scope, sealed);

            NativeWeakSet.init(scope, sealed);

            NativeBigInt.init(scope, sealed);

        }

        if (scope instanceof TopLevel) {

            ((TopLevel) scope).cacheBuiltins(scope, sealed);

        }

        return scope;

    }

    public static ScriptableObject initStandardObjects(Context cx, ScriptableObject scope, boolean sealed) {

        ScriptableObject s = initSafeStandardObjects(cx, scope, sealed);

        new LazilyLoadedCtor(s, “Packages”, “org.mozilla.javascript.NativeJavaTopPackage”, sealed, true);

        new LazilyLoadedCtor(s, “getClass”, “org.mozilla.javascript.NativeJavaTopPackage”, sealed, true);

        new LazilyLoadedCtor(s, “JavaAdapter”, “org.mozilla.javascript.JavaAdapter”, sealed, true);

        new LazilyLoadedCtor(s, “JavaImporter”, “org.mozilla.javascript.ImporterTopLevel”, sealed, true);

        for (String packageName : getTopPackageNames()) {

            new LazilyLoadedCtor(s, packageName, “org.mozilla.javascript.NativeJavaTopPackage”, sealed, true);

        }

        return s;

    }

In the unpatched version 8.9.0, the function initStandardObjects initializes the scripting environment with direct access to Java classes through constructors like Packages, getClass, JavaAdapter, and JavaImporter. This approach allows JavaScript code to dynamically interact with Java classes and packages, potentially leading to severe vulnerabilities such as Remote Code Execution (RCE). By exposing Java classes in the scripting context, an attacker can exploit this to access sensitive system resources, execute arbitrary Java methods, or even manipulate the underlying operating system via Java APIs. Specifically, the use of LazilyLoadedCtor to load Java objects directly into the scope introduces a major attack vector, making it possible for malicious scripts to instantiate Java objects or invoke system-level operations. On the other hand, the patched version 8.9.1 , initSafeStandardObjects, mitigates these risks by sandboxing the scripting environment and limiting exposure to critical components. In the patched implementation, the scope is strictly controlled by initializing only essential native JavaScript objects like NativeArray, NativeString, and NativeError, while excluding dangerous Java interop features. The LazilyLoadedCtor in this version only loads safe JavaScript objects such as RegExp and typed arrays, without exposing the Java class access previously allowed. Furthermore, the patched version enhances security by isolating the scope with a LIBRARY_SCOPE_KEY, controlling the prototype chain to avoid prototype pollution attacks, and clearing caches to prevent persistent vulnerabilities between executions. Additionally, optimizations like limiting array capacities (e.g., a cap of 200,000 elements) ensure that memory-related attacks are less likely. By removing direct Java access and tightening object exposure, the patched version effectively closes the critical vulnerability in the unpatched code, creating a safer scripting environment while still supporting native JavaScript functionality.

So, Basically When a user add a new langauage and upload a file under /admin/plugins/newcode/configure.action

Initializing the Rhino Scripting Context

// Code Part 1: Initializing the Rhino Scripting Context

Scriptable scope = cx.initStandardObjects();

The first step involves calling the initStandardObjects() method, which sets up the Rhino scripting context. This function is responsible for preparing the environment in which JavaScript-like code will be executed. The initStandardObjects() method not only initializes the standard JavaScript objects (arrays, strings, etc.) but also extends the scope with bindings that allow direct interaction with Java classes and methods. This extension is achieved through the addition of constructs like “Packages”, “getClass”, “JavaAdapter”, and “JavaImporter”, which allow access to Java runtime components directly from the script.

initStandardObjects() Implementation

public static ScriptableObject initStandardObjects(Context cx, ScriptableObject scope, boolean sealed) {

    ScriptableObject s = initSafeStandardObjects(cx, scope, sealed);

    // Adding Java access points to the Rhino context

    new LazilyLoadedCtor(s, “Packages”, “org.mozilla.javascript.NativeJavaTopPackage”, sealed, true);

    new LazilyLoadedCtor(s, “getClass”, “org.mozilla.javascript.NativeJavaTopPackage”, sealed, true);

    new LazilyLoadedCtor(s, “JavaAdapter”, “org.mozilla.javascript.JavaAdapter”, sealed, true);

    new LazilyLoadedCtor(s, “JavaImporter”, “org.mozilla.javascript.ImporterTopLevel”, sealed, true);

    // Iterating through package names and binding them to the scope

    String[] var4 = getTopPackageNames();

    int var5 = var4.length;

    for(int var6 = 0; var6 < var5; ++var6) {

        String packageName = var4[var6];

        new LazilyLoadedCtor(s, packageName, “org.mozilla.javascript.NativeJavaTopPackage”, sealed, true);

    }

    return s;

}

This code demonstrates how the initStandardObjects() method introduces Java components to the scripting context. The function starts by calling initSafeStandardObjects(), which sets up basic, non-hazardous JavaScript objects like arrays and strings. However, it then adds multiple entries using LazilyLoadedCtor, which binds Java classes and packages to the scope. These bindings expose Java runtime features directly within the scripting environment, which can be exploited to execute arbitrary Java commands. For example, constructs like “Packages” and “getClass” provide access to Java classes, making it possible to invoke methods such as Runtime.getRuntime().exec(), leading to remote command execution.

// Code Part 2: Building the Script String

String script = this.parserScript + “\n” + scriptString + “\nSyntaxHighlighter.readBrushes();”;

In this line, a new script string is constructed by concatenating three parts: the predefined parserScript, the user-supplied scriptString, and the validation function “SyntaxHighlighter.readBrushes();”. Here, parserScript is loaded from a file (languageParser.js), which contains core parsing logic for languages. It is generally safe on its own, but becomes dangerous when combined with scriptString. The scriptString is the user-provided input and is not validated before being appended to the script. This input can contain arbitrary JavaScript code, making it the primary vector for injection attacks. The lack of validation allows attackers to insert malicious code that can access the exposed Java objects, leading to RCE.

// Code Part 3: Evaluating the Script in the Rhino Context

cx.evaluateString(scope, script, “ParserScript”, 0, null);

The script constructed in the previous step is now evaluated using the evaluateString() method of the Rhino context. This is the most critical point of the vulnerability, where the injected script is executed in the context of the initialized environment. The evaluateString() function takes the scope, which includes the Java extensions introduced earlier, and executes the concatenated script. Since the scope contains unrestricted access to Java classes (due to the bindings added by initStandardObjects()), any injected code in scriptString can interact with Java classes directly. For example, an attacker could craft a payload like:

getClass().forName(“java.lang.Runtime”).getMethod(“exec”).invoke(null, “malicious_command”);

This payload exploits the exposed Java classes to execute system commands, leading to remote code execution on the server. The presence of such Java components in the scripting context, combined with the lack of input validation, creates a severe security risk.

// Code Part 4: Validation of ‘brushName’

Object nameObj = scope.get(“brushName”, scope);

if (!(nameObj instanceof ConsString)) {

    throw new InvalidLanguageException(“newcode.language.parse.no.brush.name”, new Object[0]);

}

After the script execution, the code attempts to validate the outcome by checking the brushName variable in the scope. The call scope.get(“brushName”, scope) retrieves the variable set by the script, and the subsequent check ensures that brushName is a ConsString (a specific type of JavaScript string). If brushName is not present or not of the expected type, an InvalidLanguageException is thrown. However, this validation step is ineffective for preventing RCE, as it only checks the script’s result and does not affect the execution of potentially malicious code. By the time this validation occurs, any injected code in scriptString would have already been executed, making the validation too late to prevent exploitation.

Exploitation

The exploit is on github by W01fh4cker.

Mitigation

Mitigation table for the affected and fixed versions:

Affected VersionsFixed Versions
8.9.08.9.1
8.8.0 to 8.8.18.9.1
8.7.0 to 8.7.28.9.1
8.6.0 to 8.6.28.9.1
8.5.0 to 8.5.8 LTS8.9.1 or 8.5.9 LTS recommended
8.4.0 to 8.4.58.9.1 or 8.5.9 LTS recommended
8.3.0 to 8.3.48.9.1 or 8.5.9 LTS recommended
8.2.0 to 8.2.38.9.1 or 8.5.9 LTS recommended
8.1.0 to 8.1.48.9.1 or 8.5.9 LTS recommended
8.0.0 to 8.0.48.9.1 or 8.5.9 LTS recommended
7.20.0 to 7.20.38.9.1 or 8.5.9 LTS recommended or 7.19.22 LTS
7.19.0 to 7.19.21 LTS8.9.1 or 8.5.9 LTS recommended or 7.19.22 LTS
7.18.0 to 7.18.38.9.1 or 8.5.9 LTS recommended or 7.19.22 LTS
7.17.0 to 7.17.58.9.1 or 8.5.9 LTS recommended or 7.19.22 LTS
Any earlier versions8.9.1 or 8.5.9 LTS recommended or 7.19.22 LTS

Conclusion

CVE-2024-21683 represents a critical security risk in Confluence Data Center and Server, allowing attackers to achieve RCE through unsafe exposure of Java classes in the Rhino scripting environment. The vulnerability is due to the flawed implementation of the initStandardObjects() function, which provides direct access to Java components, bypassing typical security measures. The patched versions, effectively addresses this issue by limiting the scope to safe JavaScript objects and removing access to dangerous Java classes. It is imperative for users of Confluence to upgrade to the fixed version immediately or apply recommended workarounds. This analysis underscores the importance of secure coding practices, rigorous input validation, and proper sandboxing to prevent such critical vulnerabilities.

References

Discover more from SecureLayer7 - Offensive Security, API Scanner & Attack Surface Management

Subscribe now to keep reading and get access to the full archive.

Continue reading

Enable Notifications OK No thanks