Vikunja는 기본 설정으로 모든 로컬호스트 포트를 신뢰하고, 여기에 자격증명(Credentials) 허용 설정이 결합되어 있습니다. 이 조합이 보안상 위험을 만듭니다. 공격자가 사용자의 로컬 환경에서 악성 페이지로 인증 토큰을 탈취하고 계정 전체를 제어할 수 있는 경로가 열리기 때문입니다.
Vikunja CORS 취약점(GHSA-m687-p538-r5hp) 개요
오픈소스 할 일 관리 서비스인 Vikunja에서는 교차 출처 리소스 공유(CORS) 정책 설정에 허점이 있어 보안 취약점이 발견되었습니다. 이 취약점은 GitHub Advisory Database에 GHSA-m687-p538-r5hp라는 권고 번호로 등재되어 관리되고 있습니다.
CORS는 웹 브라우저가 다른 도메인의 리소스에 접근할 때 보안을 위해 적용하는 메커니즘입니다. 하지만 Vikunja의 특정 버전은 기본 설정값이 지나치게 허용적으로 잡혀 있어, 신뢰할 필요가 없는 로컬호스트 출처까지 무분별하게 신뢰하게 됩니다.
이 취약점은 로컬호스트를 허용하는 데서 그치지 않습니다. 인증 정보가 포함된 요청까지 허용하면서 실제 사용자 데이터에 접근하는 통로를 열어두었다는 점이 핵심입니다.
기술적 분석: 로컬호스트 신뢰 및 자격증명 허용 메커니즘
기본 CORS 허용 범위의 문제
Vikunja는 cors.origins 설정의 기본값으로 http://127.0.0.1:*과 http://localhost:*를 지정합니다. 와일드카드(*) 포트가 붙어 있으니, 로컬호스트에서 실행되는 모든 포트의 요청을 신뢰하게 되는 셈입니다.
자격증명 포함 요청 허용
허용 범위만 문제가 아닙니다. 이 허용 범위와 함께 Access-Control-Allow-Credentials: true 헤더를 전송한다는 점이 더 심각합니다. Allow-Credentials가 true로 설정되면 브라우저는 CORS 요청 시 쿠키나 인증 헤더와 같은 자격증명을 함께 전송하게 됩니다.
그래서 로컬호스트의 임의 포트에서 실행되는 스크립트가 사용자의 인증 세션(쿠키 등)을 이용해 Vikunja API에 요청을 보낼 수 있고, 서버는 이를 정상적인 요청으로 수락해 응답하게 됩니다.
설정의 지속성 문제
시스템 관리자가 cors.enable을 true로 설정하면 service.publicurl 설정이 필수 항목이 됩니다. 그런데 service.publicurl에 실제 서비스 도메인을 지정하더라도 이 값은 기본 로컬호스트 허용 리스트를 대체하지 않습니다. 기존 리스트에 추가(append)되는 방식으로 작동하기 때문에, 프로덕션 환경에서 정교하게 설정을 마친 상태에서도 로컬호스트 신뢰 문제는 그대로 남습니다.
영향 범위 및 잠재적 공격 시나리오
영향 받는 버전 및 위험도
이 취약점은 Vikunja 버전 2.2.0에서 도입되었고, 2.6.0 버전까지 영향을 미치는 것으로 명시되어 있습니다. OSV.dev에 제시된 CVSS v4.0 벡터 값은 다음과 같습니다.
| 항목 | CVSS v4.0 벡터 값 |
|---|---|
| 벡터 스트링 | CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N |
| 주요 영향 | 기밀성(VC:H), 무결성(VI:H)에 높은 영향 |
공격 시나리오 분석
공격자가 사용자의 기기 내 로컬 포트에서 악성 페이지를 실행하거나, 사용자가 로컬에서 실행 중인 신뢰할 수 없는 웹 페이지에 접속하게 유도하면 공격이 다음과 같은 단계로 진행될 수 있습니다.
- 접근 유도: 사용자가 공격자가 제어하는 로컬 페이지에 접속합니다.
- 토큰 요청: 해당 페이지의 스크립트가
fetch()함수로 Vikunja의 토큰 갱신 엔드포인트에 요청을 보냅니다. 이때 브라우저는vikunja_refresh_token쿠키를 함께 전송합니다. - 토큰 획득: 서버는 로컬호스트 요청을 신뢰하고 자격증명을 확인한 뒤, 응답 본문에 베어러(Bearer) JWT 액세스 토큰을 반환합니다.
- 계정 제어: 공격자는 획득한 액세스 토큰을 이용해 로그인된 사용자의 권한으로 API를 호출하며, 결국 해당 계정 전체를 제어하게 됩니다.
대응 방안: cors.origins 명시적 설정 및 점검 항목
시스템 관리자와 DevOps 엔지니어는 단순히 service.publicurl을 설정하는 데서 멈추지 말고, CORS 설정을 명시적으로 관리해 로컬호스트 신뢰 문제를 해결해야 합니다.
1. cors.origins 명시적 재정의
기본값으로 제공되는 로컬호스트 허용 리스트가 추가(append)되는 구조이므로, 신뢰할 수 있는 특정 도메인만 명시적으로 지정해 기본 설정을 덮어써야 합니다.
2. 환경별 설정 분리
개발 환경과 프로덕션 환경의 설정은 엄격히 분리하는 게 맞습니다. 프로덕션 환경에서는 로컬호스트(127.0.0.1 또는 localhost)에 대한 모든 접근 권한을 제거하고, 오직 인증된 서비스 도메인만 허용하도록 구성합니다.
3. 보안 점검 리스트
운영 중인 Vikunja 서버가 있다면 다음 항목을 점검해 보세요.
- 버전 확인: 현재 사용 중인 버전이 2.2.0에서 2.6.0 사이인지 확인하고, 최신 보안 패치가 적용된 버전으로 업데이트를 계획해 둡니다.
- 헤더 분석: 외부 도메인이나 로컬 도메인에서 요청을 보냈을 때
Access-Control-Allow-Credentials: true헤더가 불필요하게 반환되는지 살펴봅니다. - 설정 파일 검토:
cors.enable이 true인 경우,service.publicurl외에cors.origins가 어떻게 정의되어 있는지 확인하고 불필요한 와일드카드(*) 포트 설정이 포함되어 있지 않은지 점검합니다.