DOCS/ideas/250815_로그인_인증_시스템_보안_개선_필요사항.md
happybell80 8c1a16a109 docs: 로그인 보안 취약점 서버팀 관점 분석 추가
- 치명적 보안 취약점 상세 분석
- 실제 해킹 시연 코드 추가
- 24시간 내 조치사항 명확화
- 자물쇠와 열쇠 구멍 비유 추가
2025-08-15 12:53:26 +09:00

9.5 KiB

로그인 인증 시스템 보안 개선 필요사항

작성일: 2025-08-15
작성자: Claude & happybell80
상태: 아이디어 → 긴급 개선 필요
관련: auth-server, robing-gateway, frontend

개요

현재 로빙 프로젝트의 인증 시스템에서 JWT 토큰은 발급되지만 실제 API 호출 시 검증되지 않는 심각한 보안 취약점이 발견되었습니다. 누구나 X-User-Id 헤더를 조작하여 다른 사용자로 위장할 수 있는 상태입니다.

현재 인증 흐름

1. Frontend → auth-server로 리다이렉트 (/auth/gmail/login)
   ↓
2. Auth-server → Google OAuth → 콜백 받음
   ↓
3. Auth-server → Redis에 임시 코드 저장 → Frontend로 리다이렉트 (#code=xxx)
   ↓
4. Frontend → 코드로 토큰 교환 (/auth/verify) → JWT 토큰 받음
   ↓
5. Frontend → localStorage에 토큰 저장
   ↓
6. Frontend → robing-gateway 호출 시 X-User-Id 헤더만 전송 ⚠️
   ↓
7. robing-gateway → 토큰 검증 없이 X-User-Id 신뢰 ⚠️

발견된 보안 취약점

1. 인증 토큰 미전달

  • 현상: Frontend가 JWT 토큰을 저장하지만 API 호출 시 전송하지 않음
  • 코드 위치: frontend의 API 클라이언트
  • 영향: 인증 메커니즘이 무의미함

2. 토큰 검증 부재

  • 현상: robing-gateway가 X-User-Id 헤더만 확인하고 JWT 검증 없음
  • 코드 예시:
    # 현재 (취약함)
    user_id = request.headers.get("X-User-Id", "default")
    
    # 필요한 것
    token = request.headers.get("Authorization", "").replace("Bearer ", "")
    user_id = verify_jwt_token(token)  # 검증 필요!
    
  • 영향: 누구나 헤더 조작으로 다른 사용자 행세 가능

3. 시스템 간 연결 끊김

  • auth-server: JWT 발급 기능 있음 (51123 서버, 포트 9000)
  • robing-gateway: JWT 검증 기능 없음 (51124 서버)
  • frontend: 토큰 보유하지만 전송 안 함
  • 영향: 인증 시스템이 형식적으로만 존재

4. 기본값 폴백 문제

  • 현상: X-User-Id 없으면 "default"로 처리
  • 영향: 모든 미인증 사용자가 동일한 로빙/데이터 공유

긴급 수정 필요사항

Frontend 수정

robing-api.ts 수정 필요

// 현재 문제: 토큰 전송 안 함, 하드코딩된 userId
export async function sendMessage(text: string, userId: string = 'test_user'): Promise<MessageResponse> {
  const response = await fetch(`${ROBING_API_URL}/api/chat`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-User-Id': userId  // ❌ 토큰 없음, 조작 가능
    },
    body: JSON.stringify({
      message: text,
      user_id: userId
    })
  });
}

// 수정 필요
export async function sendMessage(text: string, token: string, userId: string): Promise<MessageResponse> {
  const response = await fetch(`${ROBING_API_URL}/api/chat`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,  // ✅ JWT 토큰 추가
      'X-User-Id': userId  // 호환성 유지 (임시)
    },
    body: JSON.stringify({
      message: text,
      user_id: userId
    })
  });
}

chat-interface.tsx 수정 필요

// 현재: useAuth 사용 안 함
import { sendMessage } from '@/services/robing-api';

// 수정 필요
import { useAuth } from '@/contexts/auth-context';

const ChatInterface = () => {
  const { user, isAuthenticated } = useAuth();
  
  const handleSend = async (input: string) => {
    if (!isAuthenticated || !user) {
      // 로그인 유도
      return;
    }
    
    const token = localStorage.getItem('auth_token');
    const response = await sendMessage(input, token, user.id);
  };
};

robing-gateway 수정

# JWT 검증 미들웨어 추가
from jose import jwt, JWTError

async def verify_token(request: Request):
    token = request.headers.get("Authorization", "").replace("Bearer ", "")
    if not token:
        raise HTTPException(status_code=401, detail="Token missing")
    
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
        request.state.user_id = payload.get("sub", "default")
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")
    
    return request.state.user_id

@app.post("/api/chat")
async def chat(request: Request):
    user_id = await verify_token(request)  # 검증 필수!
    # ...

시스템 간 연동

  1. JWT 시크릿 공유: auth-server와 robing-gateway가 동일한 JWT_SECRET 사용
  2. 환경변수 설정:
    # .env 파일
    JWT_SECRET=동일한_시크릿_키_사용
    JWT_ALGORITHM=HS256
    JWT_EXPIRY_HOURS=24
    

에러 처리

# 인증 실패 시 명확한 에러
@app.exception_handler(401)
async def unauthorized_handler(request, exc):
    return JSONResponse(
        status_code=401,
        content={"error": "Authentication required", "detail": str(exc)}
    )

추가 개선사항

1. Refresh Token 구현

# 장기 세션 유지를 위한 refresh token
def create_tokens(user_id: str):
    access_token = create_jwt(user_id, expires_in=timedelta(hours=1))
    refresh_token = create_jwt(user_id, expires_in=timedelta(days=30))
    return access_token, refresh_token

2. 토큰 블랙리스트

# Redis를 활용한 로그아웃 토큰 관리
async def logout(token: str):
    await redis.setex(f"blacklist:{token}", JWT_EXPIRY_HOURS * 3600, "1")
    
async def is_token_blacklisted(token: str):
    return await redis.exists(f"blacklist:{token}")

3. Role-Based Access Control (RBAC)

# JWT에 역할 정보 포함
payload = {
    "sub": user_id,
    "email": user_email,
    "role": "admin",  # or "user", "guest"
    "permissions": ["read", "write", "delete"]
}

서버별 현황 및 설정 관리 문제

51123 서버

  • auth-server 실행 중 (포트 9000)
  • JWT 발급 기능 정상
  • Redis 임시 코드 시스템 작동
  • pydantic BaseSettings 사용

51124 서버

  • robing-gateway 실행 중
  • JWT 검증 로직 없음 ⚠️
  • 모든 로빙 서비스 운영
  • config.py 없음 - os.getenv() 분산 사용 (database.py에서 직접 호출)

로컬 개발

  • frontend 개발
  • 토큰 저장은 하지만 전송 안 함 ⚠️
  • API URL 문제: https://ro-being.com/rb10508 대신 Gateway URL 사용 필요

설정 관리 표준화 필요

서비스 Config 위치 상태
rb10508_micro app/config.py 표준
rb8001 app/core/config.py 표준
skill-slack app/core/config.py 표준
robing-gateway 없음 비표준

robing-gateway도 BaseSettings 패턴으로 전환 필요

보안 위험 수준

🔴 Critical (긴급)

현재 상태에서는:

  1. 누구나 다른 사용자로 위장 가능
  2. 개인 데이터 접근 가능
  3. 로빙 설정 변경 가능
  4. 실질적인 인증 보호 없음

서버팀 관점 분석

🚨 현재 상황: 치명적 보안 취약점

인증 시스템이 껍데기만 있고 실제로는 작동하지 않는 상태입니다.

핵심 문제 요약

  1. 토큰은 있는데 쓰지 않음

    • Frontend: JWT 받아서 localStorage에 저장만 함
    • API 호출 시: Authorization 헤더 없이 X-User-Id만 전송
  2. 검증 없이 믿음

    • robing-gateway: X-User-Id 헤더만 보고 "아, 이 사람이구나" 믿음
    • 마치 신분증 검사 없이 "저 김철수입니다" 하면 믿는 것
  3. 시스템 단절

    • 51123: auth-server (JWT 발급)
    • 51124: robing-gateway (JWT 검증 코드 없음)
    • 서로 JWT_SECRET도 공유 안 함

실제 해킹 시연

누구나 5초면 해킹 가능:

curl -X POST https://ro-being.com/api/chat \
  -H "X-User-Id: happybell80" \
  -d '{"message": "비밀번호 알려줘"}'

즉시 필요한 조치 (24시간 내)

  1. robing-gateway에 JWT 검증 미들웨어 추가
  2. Frontend API 클라이언트에 Authorization 헤더 추가
  3. JWT_SECRET 환경변수 통일

기술적 판단

  • 난이도: 낮음 (코드 10-20줄)
  • 영향도: 극대
  • 작업 시간: 2-3시간

의문점

  1. 왜 처음부터 이렇게 만들었을까?
  2. 테스트 중이라 일부러 풀어놓은 건가?
  3. 아니면 단순 실수?

결론

"자물쇠는 있는데 열쇠 구멍이 막혀있는 상태"

지금 당장 고치지 않으면 모든 사용자 데이터가 위험합니다. 51124 서버만으로는 수정 불가능하고, 23 서버와 로컬 개발자의 즉각적인 협업이 필요합니다.

구현 우선순위

  1. 즉시 (1일 내)

    • robing-gateway에 JWT 검증 추가
    • Frontend API 호출 시 토큰 헤더 추가
  2. 긴급 (1주 내)

    • JWT 시크릿 환경변수 통일
    • 에러 처리 및 로깅 추가
  3. 중요 (2주 내)

    • Refresh token 구현
    • 토큰 블랙리스트 관리
    • RBAC 권한 시스템

테스트 체크리스트

  • Frontend에서 Authorization 헤더 전송 확인
  • robing-gateway JWT 검증 동작 확인
  • 잘못된 토큰 시 401 에러 반환
  • 토큰 없을 시 적절한 에러 메시지
  • 토큰 만료 시 재로그인 유도
  • 로그아웃 시 토큰 무효화

참고사항

  • JWT 라이브러리: python-jose (이미 사용 중)
  • Redis: 임시 코드 및 블랙리스트 관리
  • 환경변수: JWT_SECRET 반드시 동일하게 설정

"인증은 시스템의 문지기입니다. 문이 열려있다면 의미가 없습니다."

이 문제는 즉시 수정이 필요한 보안 취약점입니다.