오픈 소스 회계 도구인 Akaunting에서 읽기 전용 권한을 가진 회계사가 송장을 취소하고 결제 내역을 삭제할 수 있는 취약점이 발견되어, 소규모 기업이 소리 없는 데이터 손실 위험에 노출되었습니다. 이 결함은 버전 3.2.0에서 패치되었으나, 권한 확인을 메서드 이름의 하드코딩된 목록에 연결한 설계상의 실수는 역할 기반 액세스 제어(RBAC)에 의존하는 모든 시스템에 여전히 위협이 됩니다.
버그가 발생한 원인
Akaunting의 API는 메서드 이름의 허용 목록(allowlist)을 참조하여 사용자의 권한을 검증합니다. 이 목록에는 일반적인 CRUD 작업(생성, 읽기, 수정, 삭제)이 포함되어 있었으나, 상태를 변경하는 몇 가지 엔드포인트가 누락되었습니다.
markSentmarkCancelledmarkReceived
해당 핸들러들이 목록에 없었기 때문에, 프레임워크는 이들이 실행될 때 권한 확인 루틴을 호출하지 않았습니다. 읽기 전용 역할을 가진 사용자가 markCancelled 엔드포인트에 간단한 GET 요청을 보내면, 시스템은 이를 정당한 상태 변경으로 처리했습니다.
송장을 취소하는 것은 단순히 문서를 무효로 표시하는 것 이상의 작업을 수행합니다. 해당 송장과 연결된 모든 결제 기록도 함께 삭제합니다. 결과적으로, 수정 권한이 없는 사용자가 거래의 재무 기록을 완전히 지워버릴 수 있게 됩니다.
테스트 결과
이 취약점은 Akaunting의 공식 Docker 이미지에서 확인되었습니다.
- 송장을 업데이트하기 위한 표준 PUT 요청은 403 Forbidden을 반환하여, 일반적인 업데이트 경로가 보호되고 있음을 확인했습니다.
- 취소 엔드포인트에 대한 GET 요청은 권한 오류 없이 성공하여, 보안 허점이 드러났습니다.
이것이 중요한 이유
명확한 감사 추적(audit trail) 없이 재무제표가 변경될 수 있어, 부정행위를 발견하기 어렵게 만들고 정당한 실수를 바로잡는 것도 어렵게 만듭니다.
해결 방법
버전 3.2.0에서는 이전에 누락되었던 상태 변경 작업을 포함하도록 권한 맵을 확장했습니다. 해당 버전부터는 송장 발송, 취소, 수신 여부와 관계없이 문서의 상태를 변경하는 모든 요청은 표준 업데이트와 동일한 역할 검증 과정을 거쳐야 합니다. 이를 통해 읽기 전용 역할은 실제로 데이터를 수정할 수 없다는 신뢰를 회복했습니다.
개발자를 위한 교훈
- 메서드 이름을 안전성과 동일시하지 마십시오. 새로운 엔드포인트를 추가한다고 해서 보호 기능이 자동으로 상속되는 것은 아닙니다. 각 공개 메서드에 부수 효과(side effect)가 있는지 감사하십시오.
- 허용 목록(Allowlist)은 목록의 완전성에 의존합니다. "허용된" 동사들의 정적 목록은 간과하기 쉬운 허점을 남깁니다.
- 의도와 HTTP 메서드를 분리하십시오. GET은 읽기 전용이어야 하지만, 여기서는 상태 변경을 수행했습니다. 데이터 변경(mutation)은 POST, PUT, DELETE, PATCH로 제한하십시오.
- 권한 적용 범위 확인을 자동화하십시오. 정적 분석 도구를 사용하면 권한 확인 호출이 누락된 컨트롤러 메서드를 찾아내어, 배포 전에 보안 허점을 포착할 수 있습니다.
- 최소 권한 계정으로 테스트하십시오. Docker 기반 테스트에서는 읽기 전용 사용자를 사용했습니다. CI 파이프라인에서 이러한 시나리오를 재현하면 유사한 문제를 조기에 발견할 수 있습니다.
향후 조치 사항
Akaunting 커뮤니티는 이미 패치된 버전을 출시했습니다. 관리자는 사용 중인 인스턴스의 버전을 확인하고 즉시 업데이트를 적용해야 합니다.
역할 기반 시스템을 구축하는 개발자에게 주는 교훈은 명확합니다. 가능한 모든 동작을 일일이 기억해야 하는 권한 모델은 설계상 취약할 수밖에 없습니다. 어떤 작업이 상태를 변경하는지 명시적으로 선언하고, 프레임워크 수준에서 검증을 강제하며, 코드베이스를 정기적으로 감사하십시오. 그래야만 "읽기 전용"이라는 라벨이 재무 기록을 온전히 보존할 것이라고 신뢰할 수 있습니다.
