기술 분석 · Apache DolphinScheduler
워크플로 릴리스·스케줄의 프로젝트 간 권한 검사 우회
CVE-2026-66082의 워크플로·스케줄 상태 변경에 적용돼야 할 프로젝트 경계와 Apache DolphinScheduler 3.4.3에 포함된 인가 수정을 분석합니다.
어느 프로젝트의 상태를 바꿀 수 있는가
한 프로젝트에서 필요한 권한을 가진 인증된 DolphinScheduler 사용자가 권한이 없는 다른 프로젝트의 워크플로와 스케줄 상태를 바꿀 수 있었습니다. Apache는 2026년 10월 8일 이 인가 오류를 CVE-2026-66082로 공개했습니다. 공개된 설명에서 지적한 것은 인가에 사용한 프로젝트와 실제로 변경하는 리소스 사이의 연결 확인이 빠졌다는 점입니다. Apache 권고문.
제가 살펴본 질문은 워크플로의 릴리스 상태를 바꾸거나 스케줄을 활성화할 때 이 연결이 유지되는지였습니다. 보호된 프로젝트를 가진 Alice와, 별도 프로젝트에서 정당한 권한을 가진 일반 사용자 Mallory를 생각해 보겠습니다. Mallory는 자신의 계정을 사용합니다. 자신의 프로젝트에 대한 권한이 Alice의 워크플로나 스케줄까지 확장돼서는 안 됩니다. 당시 조사에서는 대상 워크플로 코드나 스케줄 식별자를 알고 있다고 가정했으며, 이 식별자를 별도로 찾아내는 방법은 입증하지 않았습니다.
애플리케이션 관찰은 직접 빌드한 DolphinScheduler 3.4.2의 로컬 독립 실행형 Docker 환경에서 얻었고, 데이터베이스는 인메모리 H2를 사용했습니다. 앞선 서비스 수준 검사는 배포된 3.4.2 패키지가 아닌 개발 스냅샷을 사용했습니다. 이 글을 위해 보존된 애플리케이션 기록과 3.4.2·3.4.3 소스를 검토했으며, 취약점을 새로 재현하거나 수정 릴리스의 런타임을 시험하지는 않았습니다.
유효한 프로젝트만으로 리소스의 소속이 확인되지는 않음
워크플로 정의와 스케줄은 연결돼 있지만 서로 다른 객체입니다. 워크플로의 릴리스 상태는 사용 가능 여부를, 스케줄은 예약 실행을 제어합니다. 두 상태를 바꾸는 작업 모두 영향을 받는 워크플로가 속한 프로젝트에 대한 인가가 필요합니다.
3.4.2의 dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java에서 onlineScheduler는 전달받은 프로젝트를 검사한 뒤 스케줄을 별도로 조회했습니다.
public void onlineScheduler(User loginUser, Long projectCode, Integer schedulerId) {
projectService.checkProjectAndAuthThrowException(loginUser, projectCode, WORKFLOW_ONLINE_OFFLINE);
Schedule schedule = scheduleDao.queryById(schedulerId);
doOnlineScheduler(schedule);
}
3.4.2 구현은 상태를 바꾸기 전에 조회한 스케줄의 워크플로가 인가된 프로젝트에 속하는지 확인하지 않습니다. 비활성화 메서드도 같은 구조입니다. 따라서 Mallory가 자신의 프로젝트에 가진 권한을 확인해도 선택한 스케줄이 그 프로젝트에 속하는지는 알 수 없습니다.
다른 검사들은 여전히 적용됩니다. 스케줄을 활성화하려면 워크플로가 온라인 상태여야 하며, 이미 요청한 상태라면 변경 없이 반환합니다. 이들은 운영 상태에 대한 검사로, Alice의 프로젝트에 대한 Mallory의 권한을 확인하지는 않습니다. 보조 함수는 스케줄을 갱신한 뒤 실제 워크플로와 연결된 프로젝트를 사용해 스케줄러를 호출합니다.
3.4.2의 워크플로 릴리스 메서드도 같은 단절을 보입니다. 전달받은 프로젝트에 대한 인가 후 코드로 워크플로를 조회하고 릴리스 상태를 바꿉니다. 워크플로 유효성 검사나 하위 워크플로의 준비 상태 확인은 빠진 프로젝트 관계를 확인해 주지 않습니다.
보호된 상태를 다시 읽어 확인하기
당시 로컬 조사에서는 이 상태 변경들을 프로젝트 불일치를 거부하는 작업과 비교했습니다. HTTP 성공 상태만으로는 충분하지 않았습니다. 거부된 대조 조건에서도 애플리케이션은 HTTP 200을 반환했기 때문입니다.
| 과거 검증의 비교 조건 | 기록된 관찰 | 확인되는 내용 |
|---|---|---|
| 프로젝트 범위의 워크플로 조회 | 프로젝트가 일치하지 않는 워크플로를 거부했습니다. | 일반 조회에서는 호출자의 프로젝트에 해당 워크플로가 노출되지 않았습니다. |
| 스케줄 수정 | 프로젝트가 일치하지 않는 스케줄을 거부했습니다. | 다른 변경 경로에서는 프로젝트 관계를 검사했습니다. |
| 워크플로 릴리스 상태 변경 | 애플리케이션 성공 응답 후 관리자 조회에서 ONLINE과 OFFLINE을 확인했습니다. | 보호된 워크플로의 상태가 실제로 바뀌었습니다. |
| 스케줄 상태 변경 | 애플리케이션 성공 응답 후 관리자 조회에서 ONLINE과 OFFLINE을 확인했습니다. | 보호된 스케줄의 상태가 실제로 바뀌었습니다. |
이는 보존된 관찰의 요약이며 재현 절차가 아닙니다. 작업 이후의 상태는 별도 관리자 계정으로 애플리케이션을 통해 읽었습니다. 변경 요청의 응답만으로 추정하거나 데이터베이스를 직접 비교한 결과가 아닙니다.
상태 변경 작업에 의도적으로 더 넓은 권한이 부여된 것은 아닐까요? 대조 조건은 그 설명의 설득력을 낮추지만, 그것만으로 정책이 확정되지는 않습니다. Apache의 공개된 CVE 기록은 이 프로젝트 불일치를 잘못된 인가로 분류하며, 적용돼야 할 경계를 확인해 줍니다.
3.4.3에서 소속과 권한을 연결하는 방식
Apache는 3.4.3 이전 버전이 영향받는다고 밝히고 3.4.3으로 업그레이드할 것을 권고합니다. 배포된 소스에서는 스케줄 상태를 바꾸는 두 메서드가 모두 프로젝트 쓰기 권한을 요구하고, 변경 전에 checkScheduleBelongsToProject를 호출합니다. 스케줄이 존재하면 이 보조 함수는 연결된 워크플로를 따라 프로젝트를 확인합니다.
WorkflowDefinition workflowDefinition =
workflowDefinitionDao.queryByCode(schedule.getWorkflowDefinitionCode()).orElse(null);
if (workflowDefinition == null || workflowDefinition.getProjectCode() != projectCode) {
throw new ServiceException(Status.SCHEDULE_NOT_EXISTS, schedule.getId());
}
이 3.4.3의 소속 검사는 리소스를 인가된 프로젝트와 연결합니다. 3.4.3의 워크플로 릴리스 메서드도 프로젝트 쓰기 권한을 요구하고, 상태를 바꾸기 전에 소속 프로젝트가 다른 워크플로를 거부합니다.
이 변경은 공개된 인가 수정에 나타나며 3.4.3에 포함돼 있습니다. 이는 배포된 수정의 소스 검증으로, Apache의 수정 설명이나 런타임 검증과는 구별됩니다. 검토한 두 릴리스만으로 문제를 도입한 변경이나 최초 영향 버전을 확정하지는 않습니다. 더 넓은 영향 버전 범위는 Apache의 설명에 따릅니다.
확인된 상태 제어와 남은 한계
근거가 뒷받침하는 결과는 프로젝트 경계를 넘어 허가되지 않은 ONLINE/OFFLINE 변경을 수행할 수 있다는 것입니다. 소스에서는 스케줄 변경이 스케줄러 작업으로 이어지지만, 보존된 변경 후 상태만으로 작업이 실제 실행됐다고 볼 수는 없습니다. 이 관찰은 코드 실행, 관리자 접근이나 다양한 운영 환경에서의 발생 빈도까지 입증하지 않습니다.
Apache는 수치 점수 없이 Moderate로 평가했고, CISA-ADP는 Medium 6.5 (CVSS 3.1)을 부여했습니다. 두 평가는 공개된 CVE 데이터에 기록돼 있습니다.
유용한 회귀 검사는 허가된 동일 프로젝트의 변경을 유지하면서 소속이 다른 워크플로와 스케줄을 거부하는 것입니다. 각 상태 전환의 양방향과 쓰기 권한이 없는 호출자를 포함해야 하며, 거부된 작업은 보호된 상태를 바꾸지 않아야 합니다. 이는 제안하는 검사로, 새로 실행한 결과나 업스트림 테스트 범위에 대한 설명은 아닙니다.