이메일로 첨부파일 보낼 때, 왜 “테넌트 검증”까지 신경 써야 할까? (Cross-Tenant Download Issue)

작성자

카테고리:

이메일로 첨부파일 보낼 때, 왜 “테넌트 검증”까지 신경 써야 할까? (Cross-Tenant Download Issue)

Multi-tenant SaaS Security

멀티테넌트(Multi-tenant) SaaS 서비스를 개발하다 보면, 생각보다 자주 놓치는 보안 구멍이 하나 있습니다.
바로 “이메일로 첨부파일을 발송하는 기능”에서 발생하는 Cross-Tenant 다운로드 이슈입니다.

이번 글에서는 이 문제가 왜 생기는지, 실제로 어떤 사고로 이어질 수 있는지, 그리고 설계 단계에서 무엇을 확인해야 하는지를 정리해봤습니다.


1. 문제의 시작: 첨부파일, 어떻게 이메일에 태울 것인가

이메일에 파일을 담아 보내는 방식은 크게 두 가지입니다.

발송 방식 특징 요약
다운로드 링크(URL) 이메일 본문에 다운로드 링크 삽입 (가장 흔하지만 권한 제어 까다로움)
물리적 파일 첨부 파일 자체를 이메일에 물리적으로 첨부 (용량 제한 및 시스템 제어 밖 이탈)

방식에 따라 발생하는 리스크가 완전히 다르기 때문에, 이 둘을 구분하지 않고 설계하면 나중에 큰 사고로 이어질 수 있습니다.


2. 방식 ① 다운로드 링크를 보내는 경우

가장 흔한 방식이지만, 권한 제어 관점에서는 오히려 더 까다롭습니다.

비로그인(세션 없음) 문제

이메일을 열어보는 환경(모바일, 외부 PC 등)에는 로그인 세션이 없는 경우가 많습니다.
이 상태에서 링크를 클릭하면 로그인 페이지로 튕기거나 401/403 에러가 발생해 사용자 경험이 나빠집니다.

권한 우회 및 정보 유출 — 진짜 위험한 지점

더 심각한 문제는 이메일이 다른 회사(테넌트) 사람에게 포워딩되는 경우입니다.

시나리오

  1. A사 소속 홍길동에게 기밀 계약서 다운로드 링크가 담긴 메일이 발송됩니다.
    https://our-system.com/api/file/download?fileId=999
  2. 홍길동이 이 메일을 B사 소속 지인 김철수에게 실수로(혹은 의도적으로) 포워딩합니다.
  3. 김철수가 링크를 클릭합니다. 마침 본인 회사(B사) 계정으로 로그인이 되어 있는 상태입니다.
  4. 서버는 이렇게 판단합니다: “로그인된 정상 유저네. fileId=999도 DB에 존재하는 파일이네. 다운로드 시켜주자.”
  5. 결과적으로 B사 직원이 A사의 기밀 문서를 다운로드하게 됩니다.

이건 전형적인 IDOR(Insecure Direct Object Reference) 취약점입니다.
서버가 확인한 건 딱 두 가지뿐이었기 때문입니다.

  • 로그인한 유저인가? → O
  • 요청한 fileId가 DB에 존재하는가? → O

정작 확인하지 않은 건 “이 사람의 소속 테넌트”“이 파일의 소유 테넌트”가 일치하는지였습니다.

올바른 방어 로직 (백엔드 예시)

// 1. 파일 정보 조회
FileDTO fileInfo = fileRepository.findById(fileId);

// 2. 현재 로그인한 사용자의 테넌트 ID 조회
String currentTenantId = securityContext.getUser().getTenantId();

// 3. 핵심 검증: Cross-Tenant 차단
if (!fileInfo.getTenantId().equals(currentTenantId)) {
    throw new AccessDeniedException("다른 회사의 파일은 다운로드할 수 없습니다.");
}

// 4. 검증 통과 시에만 다운로드 실행
return downloadFile(fileInfo);

단순히 “로그인 여부”나 “권한 등급(Admin/User)”만 체크하는 게 아니라,
데이터 소유권(Tenant) 단위까지 격리(Isolation) 되어 있는지가 핵심입니다.

보완 방법

  • 세션 없이도 안전하게 다운로드 가능한 기간 한정 Pre-signed URL 발급
  • URL 자체에 1회성/기간제한 토큰을 포함시켜, 유출되더라도 피해 최소화

3. 방식 ② 파일을 물리적으로 직접 첨부하는 경우

이 경우는 애초에 시스템의 다운로드 권한 체크 로직 자체를 우회하게 됩니다.

리스크 요인 상세 내용
보안 경계 이탈 파일이 메일에 첨부되어 나가는 순간, 시스템의 테넌트 권한 제어 밖으로 완전히 벗어납니다. 수신자가 외부로 포워딩하면 그 어떤 Cross-Tenant 로직으로도 막을 수 없습니다.
배치/비동기 서버 권한 메일 발송 배치는 특정 사용자의 세션이 없기 때문에, 시스템(Admin) 권한으로 파일을 읽어옵니다. 이때 발송 대상 테넌트와 파일 소유 테넌트가 일치하는지를 쿼리 단에서 엄격히 검증하지 않으면, 실수로 B사 파일이 A사 고객에게 발송될 위험이 있습니다.
용량/트래픽 문제 메일 서버는 보통 10~25MB 이상 첨부파일을 차단하기 때문에, 대용량 파일은 발송 실패(Bounce)로 이어질 수 있습니다.

4. 설계자에게 확인해야 할 체크리스트

이 이슈를 다룰 때는 아래 항목들을 명확히 확인하는 게 중요합니다.

분류 점검 항목 (체크리스트) 확인
① 발송 방식 물리적 파일을 직접 첨부하나요, 다운로드 URL을 제공하나요?
② URL 링크 방식 링크 클릭 시 로그인(세션)이 필수인가요?
세션이 없는 환경(모바일 등)에서 로그인 페이지로 리다이렉트될 때 UX는 어떻게 처리하나요?
다른 테넌트 사용자가 포워딩된 링크를 클릭했을 때 차단하는 Tenant ID 검증 로직이 API에 반영되어 있나요?
세션 없이 다운로드해야 한다면, 1회성 토큰이나 Pre-signed URL 같은 방어책이 있나요?
③ 물리적 첨부 방식 발송 배치가 시스템 권한으로 파일을 무조건 읽어오나요?
수신자의 소속 Tenant ID와 첨부 파일의 소유 Tenant ID를 교차 검증하는 로직이 설계에 포함되어 있나요?
파일 용량 제한(10~20MB 초과 시 Bounce) 정책이 마련되어 있나요?
④ 보안/컴플라이언스 파일이 외부로 포워딩되어 기밀이 유출될 경우, 비즈니스 도메인상 이 리스크를 감수할 수 있나요?

5. 결론: 핵심은 “소유권 단위의 격리”

멀티테넌트 시스템에서 권한 체크는 “로그인했는가”만으로는 부족합니다.
“이 데이터가 정말 이 사람(테넌트) 소유인가”까지 검증해야 진짜 안전한 설계입니다.

이메일 발송처럼 시스템 경계를 넘나드는 기능일수록 이 원칙이 더 중요해집니다.
설계 단계에서 위 체크리스트를 짚고 넘어가면, 나중에 대형 보안 사고로 번질 수 있는 구멍을 미리 막을 수 있습니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다