docs: JWT.sub 매칭 미구현 취약점 상세 문서화

- 본인 확인 검증 문제 정확한 플로우 추가
- JWT 토큰 구성 (Gmail/Slack) 상세 명시
- 취약 엔드포인트 목록 업데이트
- JWT.sub ≠ URL.user_id 검증 없음 명확화

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
happybell80 2025-09-15 13:29:26 +09:00
parent 61b374636e
commit a8f3af04fb

View File

@ -105,9 +105,67 @@ const scheduledTasks: ScheduledTask[] = [...]; // 하드코딩
- localStorage 기반으로 다른 디바이스와 동기화 안됨
- WebSocket이나 polling 구현 없음
### 3.7 권한 검증 누락
- user_preferences 수정 시 본인 확인 로직 필요
- 다른 사용자 설정 수정 가능한 보안 문제
### 3.7 🔴 권한 검증 누락 (본인 확인 미구현)
#### 확인된 사실 (2025-09-15)
**전체 플로우:**
1. **Frontend** (`/home/admin/frontend-customer/src/components/activity-panel.tsx`)
- Line 85: `GET ${API_BASE}/api/preferences/${userId}`
- Line 155: `PUT ${API_BASE}/api/preferences/${userId}`
- userId는 localStorage에서 읽음 (Line 76)
- Authorization 헤더에 Bearer 토큰 포함 (Line 87, 158)
2. **Gateway** (`/home/admin/robeing-gateway/app/main.py`) - 51123:8100
- Line 365-391: `/api/preferences/{path}` 엔드포인트
- Line 369: `user_uuid = Depends(get_verified_user)` - JWT에서 UUID 추출
- Line 375-376 (GET): `http://192.168.219.52:9024/api/preferences/{path}` 프록시, 헤더 없음
- Line 377-382 (PUT):
- robeing-monitor로 프록시
- `X-User-Id: user_uuid` 헤더 추가 (Line 382)
- **문제점**: URL의 {path}와 JWT의 user_uuid 비교 없음
3. **robeing-monitor** (`/home/admin/ivada_project/robeing-monitor/app/api/monitor.py`) - 51124:9024
- Line 129: `async def get_preferences(user_id: str):`
- Line 194: `async def update_preferences(user_id: str, preferences: UserPreferencesUpdate):`
- 파라미터: URL의 {user_id}만 사용, Request 객체 없음
- **문제점**: JWT/헤더 검증 불가능
#### 취약점 핵심: JWT.sub ≠ URL.user_id 검증 없음
**문제**: URL의 user_id를 아무나 바꿔서 타인 데이터를 조회/수정 가능
- JWT의 sub(본인 UUID)과 URL의 user_id(대상 UUID)가 같은지 확인 안 함
```bash
# 정상 요청 (자기 데이터)
PUT /api/preferences/1e16e9d5-59f3-54da-a661-8abeabff4230
Authorization: Bearer [JWT with sub=1e16e9d5-59f3-54da-a661-8abeabff4230]
# 취약 요청 (타인 데이터 수정 가능)
PUT /api/preferences/b6ea2ee0-a15a-5cf4-93a9-a9ca20d4c4a0
Authorization: Bearer [JWT with sub=1e16e9d5-59f3-54da-a661-8abeabff4230]
# Gateway는 X-User-Id: 1e16e9d5를 전달하지만
# robeing-monitor는 URL의 b6ea2ee0를 사용
```
#### 관련 서비스 JWT 검증 현황 (2025-09-15 확인)
**JWT 토큰 발급:**
- auth-server: JWT sub에 user.id(UUID) 사용 (`/home/admin/auth-server/app/providers/gmail.py` L198~, `slack.py` L329)
- 알고리즘: HS256, 만료: 30일 (`/home/admin/auth-server/app/core/auth.py`)
- Gmail OAuth JWT: `sub`(user UUID), `username`, `email`, `name`, `exp`(만료시간), `iat`(발급시간)
- Slack OAuth JWT: `sub`(user UUID), `email`, `name`, `username`, `picture`(프로필 이미지), `slack_user_id`, `slack_team_id`, `exp`, `iat`
**JWT 검증 구현된 서비스:**
- Gateway: 모든 주요 엔드포인트에서 `Depends(get_verified_user)` 사용
- rb8001: `/api/message`에서 JWT 검증, sub(UUID)를 user_id로 사용
**JWT.sub 매칭 없는 취약 엔드포인트:**
- Gateway: `/api/workspace/{user_id}`, `/api/workspace/assign` - Depends(get_verified_user) 없음
- robeing-monitor: Preferences API (`/api/preferences/{user_id}`) - 경로 user_id만 사용
- robeing-monitor: Gmail Items API - X-User-Id와 경로 user_id 일치만 확인, JWT.sub 매칭 없음
- rb10508_micro: `/memories/{user_id}`, `/user/{user_id}/history` - JWT 검증 없음
---