GET 컬렉션 엔드포인트의 보안 확인 누락으로 인해, 기본적인 CoopCycle 계정을 가진 누구나 공유 인스턴스 내 모든 상점의 전체 주소록을 가져올 수 있었으며, 이로 인해 수많은 고객의 이름, 도로명 주소 및 우편번호가 노출되었습니다. 이 결함은 이틀 만에 패치되었으며, 사용자들에게 최신 버전으로 업그레이드할 것을 권고합니다.
유출 발생 경위
음식 배달 협동조합에서 사용하는 오픈 소스 물류 플랫폼인 CoopCycle은 PHP 프레임워크인 API Platform을 사용하여 API를 정의합니다. 이 프레임워크에서는 각 작업(POST, GET 등)에 보안 표현식이 반드시 쌍을 이루어야 합니다. 만약 표현식이 누락되면 프레임워크는 권한 확인 없이 코드를 실행합니다.
개발자들은 상점의 주소 목록을 생성하거나 업데이트하는 POST 요청을 표준 표현식인 is_granted('edit', object)로 보호했습니다. 이 방식은 요청이 단일 상점 엔티티를 대상으로 하므로, 프레임워크가 평가할 구체적인 "객체(object)"가 존재하기 때문에 정상적으로 작동합니다.
동일한 리소스를 읽는 GET 요청은 컬렉션을 대상으로 합니다: /api/stores/{id}/addresses. 컬렉션에는 단일 객체가 없으므로, 동일한 is_granted('edit', object) 표현식을 적용할 수 없습니다. 개발자들이 보안 라인을 누락했기 때문에, 프레임워크는 테넌시(tenancy)와 관계없이 인증된 모든 사용자에게 주소 데이터를 제공했습니다.
공유 CoopCycle 인스턴스에서 악의적인 사용자는 단순히 상점 ID를 순회하며 해당 엔드포인트에 GET 요청을 보내고, 시스템에 저장된 모든 고객의 집 주소를 스크래핑할 수 있었습니다. 일반 계정 외에 추가적인 권한은 필요하지 않았습니다.
버그가 발견되지 않은 이유
이 문제는 단순한 실수 때문만이 아니었습니다. API Platform의 선언적 보안 모델에는 "사용자가 컬렉션 내의 각 객체와 동일한 테넌트에 속해야 한다"는 내용을 표현할 직관적인 방법이 부족했습니다. 누락된 코드 라인은 프레임워크가 권한 부여를 번거롭게 만드는 바로 그 지점에 위치해 있었습니다.
문제를 더욱 악화시킨 것은, 프로젝트의 테스트 스위트가 모든 주소를 포함하는 GET 응답이 예상된 동작이라고 실제로 단언(assert)하고 있었다는 점입니다. 즉, 테스트에 사용된 피스처(fixtures)가 교차 테넌트 액세스를 허용했기 때문에 자동화된 테스트가 통과되었고, 결과적으로 취약점을 은폐하게 되었습니다. 이 경우, 통과된 테스트 스위트는 잘못된 보안 의식을 심어주었습니다.
승자와 패자
- 고객: 이름과 집 주소를 포함한 개인 식별 정보(PII)가 플랫폼의 모든 사용자에게 노출되었습니다. 데이터가 공개적으로 게시된 것은 아니었지만, 이번 침해로 인해 여러 협동조합의 프라이버시가 위협받았습니다.
- CoopCycle을 사용하는 협동조합: 테넌트 데이터를 보호하는 플랫폼의 능력에 대한 신뢰가 흔들렸습니다. 아직 업그레이드하지 않은 모든 협동조합은 지속적인 노출 위험에 처해 있었습니다.
- CoopCycle 유지 관리자: 이틀 만에 패치를 완료하고 회귀 테스트를 추가하는 등 신속하게 대응함으로써 취약점 악용 기간을 제한하고 책임감 있는 오픈 소스 관리 능력을 보여주었습니다. 그러나 이번 사건은 특히 프레임워크 기반의 기본 설정과 관련하여 더욱 엄격한 보안 검토 프로세스가 필요함을 시사합니다.
개발자와 감사자가 확인해야 할 사항
- 작업 비대칭성: 특정 경로의 POST(또는 기타 변경 작업)는 보호되어 있지만 상응하는 GET이 열려 있다면, 이러한 불일치는 위험 신호입니다. POST 요청은 리소스를 보호하려는 개발자의 의도를 보여줍니다.
- 컬렉션 엔드포인트: 단일 항목이 아닌 목록을 반환하는 모든 것은 일반적인 보안 패턴에서 벗어나는 경우가 많습니다. 대량 읽기에 대해 권한 확인이 명시적으로 추가되었는지 확인하십시오.
- 테스트 스위트의 현실성: 피스처가 실제 테넌시 경계를 반영하도록 하십시오. 교차 테넌트 데이터 유출을 검증하는 통과된 테스트는 녹색 불이 아니라 경고 신호입니다.
수정 사항 및 향후 단계
취약점이 보고된 후, CoopCycle 핵심 팀은 GET 컬렉션 작업에 누락된 보안 표현식을 추가하고, 단일 항목 및 컬렉션 엔드포인트 모두에 대해 테넌트 격리를 강제하는 회귀 테스트를 도입했습니다. 패치는 소프트웨어의 후속 버전으로 배포되었습니다.
CoopCycle 사용자는 다음을 수행해야 합니다:
- 최신 버전의 소프트웨어를 실행 중인지 확인합니다.
- 유사한 컬렉션 수준의 공백을 유발할 수 있는 커스텀 확장 기능이나 플러그인을 검토합니다.
- 모든 API 경로에 대해 읽기/쓰기 비대칭성에 중점을 두어 보안 스캔을 다시 실행합니다.
시사점
보안을 선언적으로 만드는 프레임워크는 개발자가 단일 객체에만 적용되는 패턴에 의존할 경우 위험한 허점을 숨길 수 있습니다. "엔드포인트의 읽기 측이 쓰기 측과 동일한 보호 조치를 갖추고 있는가?"라는 간단한 점검만으로도, 자칫 테스트 통과(green test suites) 결과 뒤에 숨겨져 발견되지 않았을 교차 테넌트(cross-tenant) 데이터 유출 유형을 찾아낼 수 있습니다.
