POC详情: 72eebbe88400ed0e19b46dccd36c2bbb5aaedc46

来源
关联漏洞
标题: JetBrains TeamCity 安全漏洞 (CVE-2024-27198)
描述:JetBrains TeamCity是捷克JetBrains公司的一套分布式构建管理和持续集成工具。该工具提供持续单元测试、代码质量分析和构建问题分析报告等功能。 JetBrains TeamCity 2023.11.4之前版本存在安全漏洞,该漏洞源于存在身份验证绕过漏洞。
介绍
CVE-2024-27198: Authentication bypass in Jetbrain Teamcity leads to Remote code execution
======================
## Overview
> TeamCity is a Continuous Integration and Deployment server that provides out-of-the-box continuous unit testing, code quality analysis, and early reporting on build problems

The vulnerability appears in a library which allow attackers to access arbitrary  unauthenticated endpoints.

## Code Analysis

The vulnerable code is in `web-openapi.jar` library in `directory to the lib`

The class `jetbrains.buildServer.controllers.BaseController` is in charge of handling requests and response, yet improperly implemented. Let's see how the code look like:
```java
public abstract class BaseController extends AbstractController {
//////
public final ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {  
    try {  
        ModelAndView modelAndView = this.doHandle(request, response);  
        if (modelAndView != null) {  
            if (modelAndView.getView() instanceof RedirectView) {  
                modelAndView.getModel().clear();  
            } else {  
                this.updateViewIfRequestHasJspParameter(request, modelAndView);  
            }  
        }

```
The main purpose of `ModelAndView` method is to render the requested page to the UI. This is where the bug starts. Notice that the `updateViewIfRequestHasJspParameter` will be called if our request is not being redirected. In order to find the root cause of the vulnerability, we need to investigate deeper to understand. 
```java
private void updateViewIfRequestHasJspParameter(@NotNull HttpServletRequest request, @NotNull ModelAndView modelAndView) {  
    boolean isControllerRequestWithViewName = modelAndView.getViewName() != null && !request.getServletPath().endsWith(".jsp");  
    String jspFromRequest = this.getJspFromRequest(request);  
    if (isControllerRequestWithViewName && StringUtil.isNotEmpty(jspFromRequest) && !modelAndView.getViewName().equals(jspFromRequest)) {  
        modelAndView.setViewName(jspFromRequest);  
    }  
  
}
```
 
 This method is used to update the `ModelAndView` object's view name when it meets particular conditions. The `isControllerRequestWithViewName` will be `True` if `modelAndView` object has a name and the `path` in the url doesn't end with `.jsp`. For example, a url that satisfies those conditions is `http://localhost:8111/random_string` and an invalid one will be `http://localhost:8111/admin/admin.html` or `http://localhost:8111/admin.jsp`.  As discussed above, we mustn't access something that triggers redirection, usually will be pages that requires authorization. The program then will assign an variable named `jspFromRequest` which will call the method`getJspFromRequest()`. Let's now switch to that method, I'll explain the remaining code afterwards.  ***There's an `if` statement will check if `isControllerRequestWithViewName ` is true, result from the call to method`getJspFromRequest()` is not empty and the `modelAndView` object's name mustn't equal to the page we wanna request via***

```java
protected String getJspFromRequest(@NotNull HttpServletRequest request) { String  jspFromRequest  = request.getParameter("jsp"); return  jspFromRequest  == null || jspFromRequest.endsWith(".jsp") && !jspFromRequest.contains("admin/") ? jspFromRequest : null; }
``` 

This function will first retrieve the value of a request parameter called `jsp`.  The check ensures that `jsp` must ends with `.jsp` and mustn't contain `/admin`. Combining with the `if` statement above:
```java
if (isControllerRequestWithViewName && StringUtil.isNotEmpty(jspFromRequest) && !modelAndView.getViewName().equals(jspFromRequest)) {  
        modelAndView.setViewName(jspFromRequest);  
    }  
```
We will have overall picture of how the url looks like:

1. The path must neither trigger redirection as nor contains `.jsp`
2. The `jsp` request parameter mustn't equal to the `path`. For example, `/random?jsp=/random` will be invalid
3. Most importantly, `jsp` must end with `.jsp`

>TeamCity provides a REST API for integrating external applications and creating script interactions with the TeamCity server. It allows accessing resources via URL paths.
>You can start working with the REST API by opening the  `http://<TeamCity Server host>:<port>/app/rest/server`  URL in your browser: this page gives several pointers to explore the API.

Teamcity offers a REST API which allows us to access sensitive resources if we can bypass authentication. In this case, for example, we will try to access to `/app/rest/server`.

![unauthenticated_request](https://github.com/HPT-Intern-Task-Submission/CVE-2024-27198/blob/main/images/unauthenticated_request.png)

As usual, the server will redirect us to `/login.html` when we request to `/app/rest/server`. Let's construct a perfect URL to bypass this restriction. First, our path can be anything as long as it returns `404` status code or even `200` such as `login.html`. Next, we will use `jsp` to request to `/app/rest/server`, yet it must end with `.jsp`.  In this situation, there're even 2 tricks that we can use to bypass this check. We can use semicolon `;` as a parameter delimiter: `/app/rest/server;.jsp`, this time the `.jsp` will be treated as a second parameter. The second bypass is to use  URI fragment `#`: `/app/rest/server%23.jsp`. What comes behind the URI fragment won't be used for routing, it's just used for navigation in a page. Note that the character needed to be URL-encoded, otherwise, the browser will ignore it first.

![unauthenticated_request_bypass.png](https://github.com/HPT-Intern-Task-Submission/CVE-2024-27198/blob/main/images/unauthenticated_request_bypass.png)

With this technique, we can even create new user with ***Admin Privilege***.  Teamcity documentation said that we can create new user via this endpoint `/app/rest/users`. 

![create_new_user](https://github.com/HPT-Intern-Task-Submission/CVE-2024-27198/blob/main/images/create_new_user.png)

Let's go to admin panel and check if there's any new user created.

![confirmed](https://github.com/HPT-Intern-Task-Submission/CVE-2024-27198/blob/main/images/confirmed.png)

As expected, a new user with ***Admin Privilege*** is created. With admin privilege, we can fully control the server. However, we can go even further than that by gaining remote code execution. This CVE is vulnerable to all versions before `2023.11.4` but this RCE is only possible to version prior `2023.11`. There's an undocumented endpoint `/app/rest/debug/processes` which allow user with admin privilege to execute arbitrary command. We will send a POST request with 2 request parameters in the request URL. For Windows, it will be `?exePath=cmd.exe&params=/c%20[our command here]` and with Linux it will be `?exePath=/bin/sh&params=-c%20[our command here]`. I'm running Teamcity on Windows, so the full path will  be `/app/rest/debug/processes?exePath=cmd.exe&params=/c%20whoami`

![failed_attempt](https://github.com/HPT-Intern-Task-Submission/CVE-2024-27198/blob/main/images/failed_attempt.png)

The server return **403** error saying that the request lacks of `csrf token`. Teamcity documentation also provide us the endpoint to retrieve the token, which is `/authenticationTest.html?csrf`.

After retrieving the token, we will add it the the request header `X-TC-CSRF-Token` or the`tc-csrf-token` HTTP parameter, here I choose `X-TC-CSRF-Token`.

![done](https://github.com/HPT-Intern-Task-Submission/CVE-2024-27198/blob/main/images/done.png)

After supplying the `csrf token`, we successfully execute command remotely, giving us full control over the server. 

In this CVE, the security flaw didn't lie in the input validation, yet the logic of the code. This application is really complicated that developers will somehow make mistakes. We also learn a new technique to bypass authentication. I hope you will learn something useful from this analysis. Happy hacking!!!

文件快照

[4.0K] /data/pocs/72eebbe88400ed0e19b46dccd36c2bbb5aaedc46 ├── [4.0K] images │   ├── [ 59K] confirmed.png │   ├── [156K] create_new_user.png │   ├── [ 93K] done.png │   ├── [101K] failed_attempt.png │   ├── [132K] unauthenticated_request_bypass.png │   └── [ 55K] unauthenticated_request.png └── [7.8K] README.md 1 directory, 7 files
神龙机器人已为您缓存
备注
    1. 建议优先通过来源进行访问。
    2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
    3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。