Overview
Recently, Rapid7 disclosed a vulnerability within Confluence that allowed a remote unauthenticated attacker to create a new administrative user account by bypassing the XWork SafeParameterFilter functionality. Our vulnerability research team decided to take a look at another Atlassian product, Atlassian Bamboo, to determine if a similar vulnerability existed within that application. In this post, we provide a description of the vulnerability within Confluence, as we felt that other write-ups did not explain the issue as thoroughly as we would have liked. Additionally, we discuss why Atlassian Bamboo was not vulnerable to this issue.
Understanding the Recent Confluence Vulnerability (CVE-2023-22515)
The purpose of this section is to answer some additional questions that we had after reading the analysis of the confluence vulnerability that Rapid7 published on AttackerKB. The article from Rapid7 stated that the vulnerability lies in incorrect filtering implemented within the SafeParametersInterceptor class. It later stated “that while the supplied parameter is identified and filtered via SafeParametersInterceptor.filterSafeParameters, this has no effect on the underlying base class ParametersInterceptor” (See Figure 1).
Figure 1: We needed to understand the root cause of the issue within SafeParametersInterceptor so that we could look for potential variants in other Atlassian products. The article from Rapid7 did not provide any further explanation of the root cause of the issue within SafeParametersInterceptor.
Unfortunately the article, while very useful and informative, never really delved further into what the core issue with SafeParametersInterceptor was which made it difficult for us to determine if Atlassian Bamboo suffered from the same or a similar issue within the application. Therefore, our goal is to supplement the useful information provided in the Rapid7 article by offering some additional analysis of the SafeParametersInterceptor and ParametersInterceptor classes.
NOTE: We recommend reading the Rapid7 blog post for an overview of the vulnerability. In the following section, we simply explain what the core issue with SafeParametersInterceptor was as we were still confused about this after reading the blog post.
SafeParametersInterceptor Analysis
We began our analysis of SafeParametersInterceptor by performing static code analysis of the SafeParametersInterceptor and ParametersInterceptor classes. Following our static code analysis, we performed a dynamic analysis using a debugger to confirm our hypothesis. We observed that processing HTTP parameters invokes the doIntercept function as a filtering mechanism.
We also noted that the doIntercept function within SafeParametersInterceptor first calls a function SafeParametersInterceptor.before, which invokes the function filterSafeParameters on line 117 and saves this to a local variable named parameters. If we invoke the server-info.action endpoint with a parameter name of “bootstrapStatusProvider.applicationConfig.setupComplete” and a value of “false”, filterSafeParameters will filter out the parameter name so the parameters object instantiated on line 117 will contain zero elements.
Under normal conditions, if the parameter name had not been filtered the application would loop through the parameters array in the for loop on line 136 and invoke setValue function on line 146. From analyzing the application using debugging we know the stack variable is of type OgnlValueStack. The OgnlValueStack.setValue function documentation states that the setValue function “attempts to set a property on a bean in the stack with a given expression using the default search order.” (See Figure 2 and Figure 3).
Figure 2: We observed that within the SafeParametersInterceptor class the doIntercept function first invoked the before function, which invoked the filtering parameter filtering and validation functionality, and subsequently invoked the doIntercept function within the ParametersInterceptor parent class.
Figure 3: The documentation for the OgnlStack.setValue function indicated that it attempted to invoke a setter on a Java bean object using a provided expression that defines the property to be set. It also passes the setter the value for the setter of that property.
At this point you may think that the system is secure as it properly identified that the provided input of “bootstrapStatusProvider.applicationConfig.setupComplete” was an unsafe OGNL expression. Furthermore, the SafeParametersInterceptor class properly filtered it. However, as shown in Figure 2, the doIntercept function in SafeParametersInterceptor also invokes the super.doIntercept function in the parent class, which is the ParametersInterceptor class.
ParametersInterceptor Analysis
Next, let’s review the code of the doIntercept function in ParametersInterceptor (See Figure 4) for potential vulnerabilities. We observe here that the doIntercept function retrieves the HTTP request parameters associated with the request on line 118 and then invokes the ParametersInterceptor.setParameters function with the previously read parameters. The interesting thing here is that these are the same parameters that SafeParametersInterceptor previously processed. After reading the parameters, the code in line 132 makes a call to ParametersInterceptor.setParameters.
Figure 4: We observed that within the ParametersInterceptor class the doIntercept function reads parameters from the ActionContext object passed to the doIntercept function.
If we dig into the ParametersInterceptor.setParameters function we observe that as it processes each parameter on line 181 a call is made to ParametersInterceptor.isAcceptableParameter (See Figure 5). This function doesn’t perform any security related filtering checks and simply checks that the parameter names are valid (this will be important later when we look at Atlassian Bamboo) (See Figure 6). Next, each parameter is processed in a loop with a call to newStack.setParameter (See Figure 5). From this, we can conclude that the call to super.doIntercept in SafeParametersInterceptor.doIntercept results in a complete circumvention of all the filtering performed in the SafeParametersInterceptor.before function. The understanding of how the security check fails at this step is crucial to the vulnerability chain and was what we were unable to understand from reading the Rapid7 explanation.
Figure 5: An analysis of the setParameters function indicated that the function simply leveraged user-supplied parameters to process as OGNL expressions without performing any sort of security-related filtering.
Figure 6: The isAcceptableParameter function does not perform any security related checks on the processed values.
Exploring Confluence with a Java Debugger
To start we placed a breakpoint on SafeParametersInterceptor.doIntercept and ran the following curl command, which exploits the vulnerability. Figure 7 shows the result of the call to filterSafeParameters–it returned a map containing zero objects, because SafeParametersInterceptor filtered the parameter we supplied.
curl -vk http://localhost:8090/server-info.action?bootstrapStatusProvider.applicationConfig.setupComplete=false
Figure 7: We observed that the parameters array is null as the filterSafeParameters function filters our input parameter of bootstrapStatusProvider.applicationConfig.setupComplete, which has a value of false .
As a result, the before function never executed its for loop, as the parameters array is empty and the before function returned. However, the super.doIntercept call on line 52 (See Figure B1) invoked the doIntercept method in the parent ParametersInterceptor class. We stepped through that function and observed that the original HTTP parameters are read again from the request (See Figure 8).
Figure 8: We observed that the ParametersInterceptor.doIntercept function invokes the retrieveParameters function, which reads the parameters from the HTTP request. This means that SafeParametersInterceptor doesn’t have any impact on the behavior of ParametersInterceptor.
After we invoked the setParameters function, our malicious parameter made it past the isAcceptableParameter function, which isn’t designed to perform any security checks to prevent malicious actions. Line 140 then added our malicious parameter to the acceptableParameter array (See Figure 9).
Figure 9: Our malicious parameter made it through the call to isAcceptableParameter in the ParameterInspector.setParameters function.
The process then created an iterator object to iterate through the acceptableParameters array, then processed each accepted parameter by invoking newStack.setParameter. As we mentioned previously, this logic essentially processes an OGNL expression to invoke an attacker-controlled setter without any input validation that SafeParameterInspector normally would apply (See Figure 10).
Figure 10: An attacker-controlled parameter name and value functioned as an input to setParameter, which allows an attacker to execute an attacker-controlled OGNL expression.
The call to setParameter resulted in a call to ApplicationConfig.setSetupComplete, which set the false value for theApplicationConfig.setupComplete variable. This ultimately allows an attacker to gain administrative access to the impacted Confluence instance (See Figure 11 and Figure 12).
Figure 11: The attacker can invoke the ApplicationConfig.setSetupComplete function to mark setupComplete as false to gain administrative access to the Confluence instance.
Figure 12: We observed that the call to setParameter in ParametersInspector.setParameters ultimately results in the compilation and execution of an OGNL expression. This in turn led to Java reflection invoking the setSetupComplete function to modify the application configuration.
Investigating a Potential Bamboo Chain
After developing a deeper understanding of the vulnerability in Confluence, we took a look at Atlassian Bamboo and started recreating the exploit chain. We specifically looked into the docker 9.3.4 image.
As in the Confluence chain, an interceptor checks the state of the Bamboo deployment to determine if it is already setup. We identified that the SetupCheckInterceptor (in atlassian-bamboo-web-9.3.4.jar) class performed the relevant check before proceeding with a setup action. The intercept method in SetupCheckInterceptor calls getCurrentStep and determines if the returned value is equal to complete. If the returned value is not equal to complete, the intercept method invokes the requested action and continues with the setup step to create the new admin account. Therefore the potential Bamboo chain must modify the currentStep value from “complete” for the interceptor to permit another setup action (see Figure 13).
Figure 13: The Bamboo SetupCheckInterceptor class.
The first mandatory entry point into the vulnerability chain is a public “.action” endpoint that exposes an object with relevant methods. The about.action extends the BambooActionSupport class and inherits many methods from its parent class (see Figure 14). An HTTP GET request to “/about.action” invokes the handler, and does not require authentication.
Figure 14: The AboutAction class extends BambooActionSupport.
AboutAction inherits methods from its parent class BambooActionSupport. The method getBootstrapManager is inherited from BambooActionSupport, as we see in Figure 15, and returns a BootstrapManager object.
Figure 15: The BambooActionSupport object inherits the getBootstrapManager method.
The BootstrapManager object inherits methods from its parent DefaultAtlassianBootstrapManager class. The getApplicationConfig method returns the ApplicationConfiguration object, as Figure 16 shows.
Figure 16: The BootstrapManager inherits the getApplicationConfig method.
ApplicationConfiguration implements the setCurrentSetupStep method as shown in Figure 17. The interceptor will permit requests to setup functionality if we modify this currentSetupStep value from “complete”.
Figure 17: The setCurrentSetupStep method in the ApplicationConfiguration class.
The final Bamboo chain to modify the currentSetupStep value results in a GET request like the following:
GET /about.action?bootstrapManager.applicationConfig.currentStep=test
The request will be translated to the following Java invocation chain that modifies the appropriate currentStep value.
getBootstrapManager().getApplicationConfig.setCurrentStep(test)
Why Bamboo is Not Vulnerable
In Bamboo the relevant intercept code resides within the two jars struts2-core-2.5.31-atlassian.jar and atlassian-xwork-core-2.5.30-struts-3.jar. The SafeParametersInterceptor class (in atlassian-xwork-core-2.5.30-struts-3.jar!/com/atlassian/xwork/interceptors/SafeParametersInterceptor.class) extends the ParametersInterceptor class (in struts2-core-2.5.31-atlassian-1.jar!/com/opensymphony/xwork2/interceptor/ParametersInterceptor.class). SafeParametersInterceptor does not override the doIntercept method so the doIntercept method that gets called is the one implemented in ParametersInterceptor, as we see in Figure 18.
Figure 18: The doIntercept Method in the ParametersInterceptor class.
doIntercept calls setParameters to process the input parameters. setParameters calls isAcceptableParameter to verify each parameter before adding the value to the processing stack (see Figure 19).
Figure 19: The setParameters method in the ParametersInterceptor class.
The isAcceptableParameter Method Override
A very important point to emphasize is that the Bamboo SafeParametersInterceptor class does override the isAcceptableParameter method. Remember that the Confluence interceptor does not override the isAcceptableParameter method, which means the system calls the default one that ParametersInterceptor implements. Furthermore, the Bamboo SafeParametersInterceptor isAcceptableParameter function appropriately checks for the @ParameterSafe annotation (see Figure 20). Because the logic filters the values appropriately, the malicious chain will fail at the isAcceptableParameter check.
Figure 20: The isAcceptableParameter method in the Bamboo SafeParametersInterceptor.
By attaching a debugger, sending the request to /about.action?bootstrapManager.applicationConfig.currentStep=test, and breaking at isAcceptableParameter (within the setParameter method), we can see in Figure 21 that the bootstrapManager parameter is about to be checked and it contains the expected value that we sent in the request.
Figure 21: The isAcceptableParameter method call about to be executed.
We let isAcceptableParameter execute and check the parameter. If we continue down to a breakpoint at the clearableStack assignment (Highlighted in Figure 21), the acceptableParameters has a length of 0, which confirms that the filtering worked as intended (See Figure 22).
Figure 22: After isAcceptableParameter executed, the acceptableParameters list remained empty.
After the safeParameter interceptor appropriately filtered out the exploit attempt, execution continued and the application provided the response to the original “about.action” request. Because the interceptor appropriately removes the parameters and consequently does not process any Ognl, Bamboo is not vulnerable to the same attack chain from CVE-2023-22515.
Conclusion
Taking time to analyze patched vulnerabilities during the vulnerability research process can provide opportunities to provide variant hunting for security issues in other products. Additionally, vendors often have not remediated security vulnerabilities properly, and sometimes it’s possible to bypass the fix.
In this case, we were not successful in identifying a similar vulnerability in Atlassian Bamboo. However, we decided to publish a blog post as we wanted to provide the deeper context our research uncovered for those who, like us, had questions regarding what exactly was wrong with SafeParametersInspector.