TSDProxy: 백엔드 서비스로 전달된 내부 프록시 인증 토큰을 통해 관리 API 에스컬레이션이 가능합니다.
내부 프로세스별 인증 토큰을 모든 프록시 백엔드 서비스에 전달하는 TSDProxy에서 취약점이 발견되었습니다. identityHeaders가 활성화되면(기본값), tsdproxy는 사용자 ID 헤더와 함께 모든 업스트림 HTTP 요청에 x-tsdproxy-auth-token을 삽입합니다. 이 토큰은 전달된 Tailscale ID 클레임을 신뢰하기 위해 관리 HTTP 서버에서 사용하는 것과 동일한 비밀입니다. 이 토큰을 수신하는 백엔드는 Tailscale 인증을 완전히 우회하여 임의의 'x-tsdproxy-id' 값을 사용하여 localhost에서 관리 포트로 토큰을 재생할 수 있습니다.
토큰은 무조건 전달됩니다. ProviderUserMiddleware는 사용자 인증 여부에 관계없이 항상 WhoisNewContext를 호출합니다. ReverseProxy.Rewrite 함수에서 WhoisFromContext는 값이 0인 Whois{}(인증되지 않은 요청 또는 Funnel 요청)에 대해서도 ok=true를 반환합니다. identityHeaders=true인 경우 모든 요청에 대해 HeaderAuthToken이 설정됩니다.
공격을 수행하려면 백엔드가 '127.0.0.1:8080'에 도달해야 합니다. 이는 (1) tsdproxy와 백엔드가 동일한 호스트에서 실행되는 Docker가 아닌 배포, (2) Docker 호스트 네트워크 모드 컨테이너, (3) tsdproxy의 네트워크 네임스페이스를 공유하는 컨테이너에 적용됩니다.
internal/proxymanager/port.go:123-132내부/코어/admin.go:160-182``가다 // port.go: 사용자 인증 상태와 관계없이 전달되는 인증 토큰 ID헤더가 {인 경우 사용자인 경우 ok := model.WhoisFromContext(r.In.Context()); 알았어 { // ProviderUserMiddleware에 의해 저장된 빈 Whois의 경우에도 ok=true입니다.{} r.Out.Header.Set(consts.HeaderAuthToken, core.ProxyAuthToken()) // 백엔드로 전송된 토큰 } }
// admin.go: 관리 포트는 토큰이 유효할 때 localhost의 x-tsdproxy-id를 신뢰합니다. func ResolveWhois(r *http.Request) 모델.Whois { If IsLocalhost(r.RemoteAddr) { 반환 모델.Whois{ ID: r.Header.Get(consts.HeaderID), // 토큰을 훔친 후 공격자가 제어 } } 반환 모델.Whois{} }
## 재현 단계
1. 'http://localhost:3000'에 백엔드가 있는 호스트(Docker가 아닌)에 tsdproxy를 배포합니다.
2. Tailscale 프록시를 통해 요청합니다. 백엔드는 요청 헤더에서 'x-tsdproxy-auth-token'을 수신합니다.
3. 호스트에서 관리 API에 토큰을 재생합니다.
``배쉬
# 백엔드 헤더에서 토큰을 캡처합니다(예: 헤더 반사 엔드포인트를 통해).
TOKEN=$(curl -s http://localhost:3000/debug/headers | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['headers'].get('X-Tsdproxy-Auth-Token',''))")
# 관리자 액세스 권한을 얻기 위해 localhost에서 재생
컬 -H "x-tsdproxy-auth-token: $TOKEN" \
-H "x-tsdproxy-id: 공격자" \
http://127.0.0.1:8080/api/v1/proxies
# 관리자 액세스 권한이 있는 전체 프록시 목록을 반환합니다.
나가는 백엔드 요청에서 HeaderAuthToken을 제거하고 user.ID != ""에 대한 ID 헤더 삽입을 보호합니다.
``가다 // port.go: 실제로 인증된 사용자에 대해서만 헤더를 삽입합니다. ID헤더가 {인 경우 사용자인 경우 ok := model.WhoisFromContext(r.In.Context()); 확인 && user.ID != "" { r.Out.Header.Set(consts.HeaderID, user.ID) // HeaderAuthToken은 백엔드로 전달되어서는 안 됩니다. } }
## 영향
tsdproxy(동일한 호스트)에 의해 프록시된 백엔드에서 코드를 실행하는 공격자는 전체 관리 API 제어 권한을 얻습니다. 모든 프록시 서비스(DoS)를 다시 시작 또는 일시 중지하고, 모든 프록시 구성 및 백엔드 네트워크 토폴로지를 열거하고, 웹훅 전달(구성된 웹훅 URL을 통한 SSRF)을 트리거합니다.
## 크레딧
Vishal Shukla(@shukla304 / @therawdev)가 보고했습니다.
## 후원
이 감사는 [sechub.dev](https://sechub.dev)의 AI 지원 연구 에이전트가 수행한 것입니다. OSS 프로젝트에서 이를 실행하는 것은 유지관리자에게 무료입니다.
왜 이 VPI인가 (설명가능 · 실험적)
VPI 산정 기준
| 영향도 | 90.00 |
| 악용 신호(추가 악용신호 없음) | ×1.00 |
| VPI | 90.00 |
VPI 공식 vpi-v1 기준