
관리자 전용 터미널 부트스트랩 경로는 로그인 상태만 확인하므로, 일반 팀 구성원이 Coolify의 실시간 터미널 백엔드를 구동하고 팀 서버에서 명령을 실행할 수 있습니다.
관리자 전용 터미널 부트스트랩 라우트가 로그인 상태만 확인하여, 일반 팀 멤버가 Coolify의 실시간 터미널 백엔드를 조작하고 팀 서버에서 명령을 실행할 수 있었습니다.
이 문제는 Coolify (오픈소스 셀프 호스팅 PaaS)를 검토하던 중, 매우 직접적인 보안 질문을 염두에 두고 발견했습니다:
터미널 접근이 실제로 백엔드 신뢰 경계에서 강제되고 있는가, 아니면 UI에서만 제한되는가?
이 경우, 대답은 나빴습니다.
Coolify는 팀 관리자와 소유자만 터미널 접근이 가능하도록 의도했지만, 실시간 터미널 부트스트랩 라우트는 사용자가 로그인했는지만 확인했습니다. 그 결과, 낮은 권한의 팀 멤버가 웹소켓 터미널 신뢰 검사를 통과하여 팀 서버에서 명령 실행까지 도달할 수 있었습니다.
취약한 리비전으로 구축한 로컬 실험실에서 종단 간 검증을 마친 후, 개인적으로 신고했습니다. 이 문제는 CVE-2026-34048로 할당되었으며, 다음과 같은 CVSS 점수를 받았습니다:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Coolify: Coolify on GitHub
CVE: CVE-2026-34048
이는 오픈소스 셀프 호스팅 PaaS인 Coolify에 영향을 주었습니다. 공식 사이트에서 Coolify는 3,641명 이상의 클라우드 고객을 보유하고 있으며, 웹사이트, 데이터베이스, 웹 애플리케이션 및 280개 이상의 원클릭 서비스를 배포하는 플랫폼이라고 소개합니다. 공식 v4.0 변경 로그에는 수천 개의 회사와 개인이 1-2년 동안 Coolify를 프로덕션 환경에서 사용해 왔다고 명시되어 있습니다.
photo0
낮은 권한의 팀 멤버 세션 -> /terminal/auth 및 /terminal/auth/ips는 로그인 상태만 확인 -> 실시간 웹소켓이 해당 응답을 신뢰 -> 멤버가 팀 서버와 표시된 SSH 키 UUID를 열거 -> /terminal/ws가 세션을 수락 -> SSH 기반 PTY가 생성됨 -> 팀 서버에서 쉘 접근
Coolify는 셀프 호스팅 PaaS 및 배포 플랫폼입니다.
다음을 관리합니다:
마지막 기능이 여기서 중요합니다.
플랫폼이 관리 호스트에 터미널을 열 수 있게 되면, 권한 부여 모델은 더 이상 단순한 애플리케이션 로직이 아닙니다. 이는 인프라 신뢰 경계가 됩니다.
중요한 질문은 /terminal 페이지가 관리자 전용처럼 보이는지가 아니었습니다.
진짜 질문은:
웹소켓 세션이 생성될 때 백엔드 터미널 경로가 실제로 동일한 권한 부여 경계를 강제하는가?
이 경우, 그렇지 않았습니다.
터미널 기능은 인프라 소프트웨어에서 가장 가치 있는 표면 중 일부입니다.
왜일까요?
UI 권한 부여, 백엔드 권한 부여, 웹소켓 부트스트랩 로직, 호스트 명령 실행 사이의 불일치가 있으면 일반 애플리케이션 사용자가 쉘을 사용할 수 있는 운영자로 변할 수 있기 때문입니다.
이것이 바로 이 표면을 테스트할 가치가 있었던 이유입니다.
무작위 충돌이나 사소한 권한 버그를 찾고 있던 것이 아닙니다.
더 강력한 실패 유형을 찾고 있었습니다:
관리자 전용 기능이 UI가 암시하는 것보다 약한 백엔드 신뢰 검사에 의존하고 있는가?
그것이 올바른 질문이었습니다.
Coolify에 접근할 때 무작위 엔드포인트를 퍼징하고 흥미로운 것을 기대하지는 않았습니다.
더 강력한 경로는 먼저 가장 위험한 경계를 식별하는 것이었습니다.
Coolify의 경우, 그 경계는 터미널 워크플로우였습니다:
이로 인해 부트스트랩 라우트가 살펴볼 올바른 위치가 되었습니다.
그리고 바로 그곳에 문제가 있었습니다.
근본 원인은 터미널 UI와 터미널 웹소켓 부트스트랩 라우트 간의 권한 부여 불일치였습니다.
취약한 리비전에서:
GET /terminal은 can.access.terminal로 보호됨POST /terminal/auth는 auth()->check()만 확인POST /terminal/auth/ips는 auth()->check()만 확인즉, UI는 터미널 권한 부여로 게이트되었지만, 백엔드 신뢰 경계는 단순한 인증된 세션 존재 여부로 게이트되었습니다.
그러면 실시간 서비스가 이 두 라우트를 완전히 신뢰했습니다.
docker/coolify-realtime/terminal-server.js에서:
verifyClient()는 /terminal/auth로 POST 요청/terminal/auth/ips로 POST 요청이것이 전체 버그 체인입니다.
일반 팀 멤버가 일반 애플리케이션 표면에서 필요한 입력을 구성할 수 있었기 때문입니다:
/servers는 표시된 서버 UUID를 노출/server/{uuid}는 ip, user, port를 렌더링된 폼 필드로 노출/security/private-key는 표시된 팀 개인 키 UUID를 노출/var/www/html/storage/app/ssh/keys/ssh_key@<uuid>
따라서 익스플로잇 경로는 간단했습니다:
/terminal/auth 호출/terminal/auth/ips 호출/terminal/ws에 연결이는 이론적 불일치가 아닙니다. 실용적인 백엔드 권한 부여 실패입니다.
중요한 차이는 백엔드 신뢰와 명령 실행입니다.
많은 버그는 다음과 같이 보입니다:
그것만으로는 충분하지 않습니다.
진짜 질문은:
낮은 권한의 사용자가 여전히 중요한 백엔드 신뢰 검사를 만족시킬 수 있는가?
여기서, 대답은 예였습니다.
이는 다음이 아니었습니다:
이는 다음이었습니다:
이것이 실제 보안 문제였던 이유입니다.
다음 리비전으로 구축한 통제된 로컬 실험실에서 검증했습니다:
06f60c9a98bead0c932c6adf7fd43a45d9149048
실험실 설정:
http://127.0.0.1:18000[email protected]localhost -> coolify-testing-host:22 as rootsshws://127.0.0.1:6002/terminal/ws멤버 계정은 일반 관리자용 UI를 통해 터미널 접근이 가능하지 않아야 합니다.
이로써 예상되는 보안 경계가 설정되었습니다.
멤버 세션을 사용하여 다음을 전송:
POST /terminal/authPOST /terminal/auth/ips둘 다 성공했습니다.
/terminal/auth/ips는 터미널 권한이 부여된 호스트를 반환했으며, 여기에는 다음이 포함되었습니다:
coolify-testing-host
host.docker.internal
localhost
127.0.0.1
즉, 백엔드 부트스트랩 라우트가 멤버 세션을 신뢰했음을 입증했습니다.
일반 인증된 페이지에서 동일한 멤버가 다음을 열거할 수 있었습니다:
이는 비밀 키 자료 공개 없이 터미널 경로를 구동하기에 충분했습니다.
동일한 인증 세션과 XSRF 토큰을 사용하여 다음에 연결:
ws://127.0.0.1:6002/terminal/ws
페이로드는 터미널 백엔드가 예상하는 동일한 명령 형식을 사용했습니다:
{"command":["timeout 30 ssh -i /var/www/html/storage/app/ssh/keys/ssh_key@ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PasswordAuthentication=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o RequestTTY=no -o LogLevel=ERROR -p '22' 'root'@'coolify-testing-host' 'bash -se' << \\P0C\nprintf '__COOLIFY_POC_BEGIN__\\n'; id; whoami; hostname; printf '__COOLIFY_POC_END__\\n'\nP0C"]}
웹소켓이 반환:
pty-ready
__COOLIFY_POC_BEGIN__
uid=0(root) gid=0(root) groups=0(root)
root
efa027413801
__COOLIFY_POC_END__
이는 중요한 증거였습니다.
단순히:
이 아니라, 관리자 전용 터미널 경로를 통해 관리 호스트에서 실제 명령이 실행되었습니다.
이 체인의 한 부분만으로도 이미 흥미로웠을 것입니다.
예를 들어:
/terminal/auth 접근/terminal/auth/ips 접근하지만 그렇다면 여전히 무시될 여지가 있었습니다.
더 강력한 검증은 종단 간이었습니다:
이는 "이론상 권한 부여 버그"와 "현실적인 인프라 영향" 사이의 간극을 메웠습니다.
또한 심각도를 훨씬 쉽게 방어할 수 있게 했습니다.
이 문제는 **치명적(Critical)**으로 적절히 분류되었습니다.
분류:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
타당합니다.
주장은 인증되지 않은 공격자가 아무것도 없이 쉘 접근을 얻을 수 있다는 것이 아닙니다.
주장은:
이는 애플리케이션 RBAC 실패에서 관리 호스트 영향으로의 주요 범위 변경입니다.
따라서 권한이 낮더라도 결과는 분명히 치명적입니다.
어떤 사람들은 PR:L로 시작하는 취약점을 과소평가합니다.
영향을 받는 기능이 터미널 접근일 때는 그것이 실수입니다.
진짜 질문은:
"공격자가 이미 로그인되어 있었는가?"
진짜 질문은:
"백엔드 권한 부여가 잘못되었을 때, 그 낮은 권한의 사용자가 도달할 수 있는 것은 무엇인가?"
이 경우, 대답은:
이는 일반 멤버 권한 버그를 훨씬 넘어섭니다.
최소한의 올바른 수정은 간단합니다:
POST /terminal/auth와 POST /terminal/auth/ips 모두에 can.access.terminal 적용이는 즉각적인 신뢰 경계 실패를 해결합니다.
로컬 검증 패치에서, 터미널 권한 부여 미들웨어를 해당 두 라우트에 적용하면 멤버-터미널 경로가 제거되었습니다.
하지만 더 강력한 교훈은 백엔드가 공격자가 제어하는 SSH 명령 문자열을 대상 메타데이터의 주요 소스로 신뢰해서는 안 된다는 것입니다.
권장 강화:
이것이 터미널 기능에 원하는 수정 유형입니다:
이 문제는 GitHub의 보안 보고 흐름을 통해 개인적으로 보고되었습니다.
보고서에는 다음이 포함되었습니다:
이후 이 문제는 할당되었습니다:
CVE-2026-34048
주요 교훈은 간단합니다:
관리자 전용 UI는 백엔드 부트스트랩 채널이 약한 상태를 신뢰한다면 의미가 없습니다.
이것이 실제 문제 클래스입니다.
그중 어느 것도 중요하지 않습니다:
플랫폼이 인프라를 관리하게 되면, 권한 부여 불일치는 더 이상 일반적인 접근 제어 실수가 아닙니다. 이는 인프라에 영향을 미치는 취약점이 됩니다.
이것이 진짜 핵심입니다.
이 취약점은 영리한 페이로드에 관한 것이 아니었습니다.
올바른 신뢰 경계를 식별하는 것이었습니다.
Coolify는 터미널 접근을 관리자 전용으로 의도했습니다. 하지만 실시간 터미널 백엔드는 사용자가 로그인했는지만 확인하는 라우트를 신뢰했습니다.
거기서부터 낮은 권한의 팀 멤버가 웹소켓 터미널 경로를 조작하여 팀 서버에서 쉘 실행에 도달할 수 있었습니다.
그것이 이 문제가 CVE-2026-34048이 된 이유입니다.