
Penpot의 원격 이미지 가져오기 기능은 인증된 파일 편집자가 일반적인 미디어 편의 기능을 백엔드 기반 SSRF로 전환할 수 있게 합니다. 이는 공격자가 제어하는 URL이 대상 필터링 없이 리디렉션을 따르는 서버 가져오기 경로로 넘어가기 때문입니다.
Penpot의 원격 이미지 가져오기 기능에서 인증된 파일 편집자가 일반적인 미디어 편의 기능을 백엔드 기반 SSRF로 전환할 수 있었습니다. 공격자가 제어하는 URL이 리디렉션을 따르는 서버 가져오기 경로로 들어갔지만 목적지 필터링이 없었기 때문입니다.
이 문제는 Penpot(오픈소스 디자인 및 코드 협업 플랫폼)을 검토하던 중, 특정 질문을 염두에 두고 발견했습니다.
협업 디자인 도구에서 한 사용자가 백엔드에 원격 이미지 URL을 전달하면 어떤 일이 발생할까?
이 경우, 그 질문이 실제 버그로 이어졌습니다.
Penpot의 원격 이미지 가져오기 흐름은 사용자 제어 URL을 허용하고 백엔드가 서버 네트워크 컨텍스트에서 이를 가져오도록 하면서 루프백 또는 사설 네트워크 대상에 대한 목적지 제한을 적용하지 않았습니다. 공유 HTTP 클라이언트는 자동으로 리디렉션을 따랐습니다.
이는 일반적인 미디어 편의 기능을 인증된 백엔드 기반 SSRF 프리미티브로 전환시켰고, 최종적으로 CVE-2026-45806이 되었습니다.
Penpot: Penpot on GitHub
CVE: CVE-2026-45806
CVSS: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
이 문제는 Penpot에 영향을 미쳤습니다. 공식 사이트와 미디어 키트에 따르면 Penpot은 +1M 이상의 사용자 기반을 보유하고 있으며 수만 개의 조직이 사용하고 있다고 밝히고 있습니다. 여기에는 Blender, Mozilla, Fedora, NTT Data, MIT, Société Générale, Cisco, Fujitsu, Indra, ByteDance가 포함됩니다.
인증된 파일 편집자 -> 공격자가 제어하는 원격 이미지 URL -> create-file-media-object-from-url -> 리디렉션 활성화된 백엔드 download-image fetch -> 최종 요청이 내부 전용 이미지 엔드포인트에 도달 -> 백엔드 기반 SSRF / 내부 접근 가능
Penpot은 오픈소스 디자인 및 코드 협업 플랫폼입니다.
다음과 같은 작업을 처리합니다:
따라서 미디어 가져오기 경로는 실제 신뢰 경계에 위치합니다.
여기서 중요한 질문은 Penpot이 원격 이미지 가져오기를 지원하는지 여부가 아니었습니다.
진짜 질문은 다음과 같습니다.
사용자가 원격 이미지를 가져올 때 Penpot이 백엔드가 연결할 수 있는 대상을 제한하는가?
이 경우, 그렇지 않았습니다.
많은 사람들이 원격 가져오기 기능을 과소평가합니다.
그것은 실수입니다.
애플리케이션이 다음과 같은 순간:
이는 실제 아웃바운드 신뢰 경계를 생성합니다.
이것이 여기서의 문제였습니다.
이 버그는 이미지 렌더링에 있지 않았습니다. 파일 저장에도 있지 않았습니다. 파일 편집을 위한 일반적인 권한 검사에도 있지 않았습니다.
이는 고전적인 서버 측 신뢰 실패였습니다:
그것만으로도 실제 취약점이 발생하기에 충분합니다.
Penpot에 접근할 때 무작위 RPC 메서드를 블라인드 퍼징하거나 먼저 충돌을 찾지는 않았습니다.
더 강력한 접근 방식은 가장 유망한 보안 경계를 식별하는 것이었습니다.
Penpot의 경우, 그것은 원격 미디어 가져오기였습니다.
왜일까요?
이 기능은 다음을 결합하기 때문입니다:
이것이 검토할 올바른 경계였습니다.
그리고 바로 거기에 버그가 있었습니다.
버그는 작은 신뢰 체인으로 요약됩니다.
프론트엔드에서:
(defn upload-media-url
[name file-id url]
(rp/cmd!
:create-file-media-object-from-url
{:name name
:file-id file-id
:url url
:is-local true}))
사용자가 제어하는 url이 직접 RPC 호출로 전달됩니다.
그런 다음 백엔드에서:
(sv/defmethod ::create-file-media-object-from-url
...
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(files/check-edition-permissions! pool profile-id file-id)
...
(let [_ (files/get-minimal-file cfg file-id)
mobj (create-file-media-object-from-url cfg (assoc params :profile-id profile-id))])
그리고:
(defn- create-file-media-object-from-url
[cfg {:keys [url name] :as params}]
(let [content (media/download-image cfg url)
백엔드는 호출자가 대상 파일을 편집할 수 있는지 확인한 후, 공격자가 제어하는 URL을 media/download-image로 전달합니다.
가져오기 구현은 다음과 같습니다:
(defn download-image
"Download an image from the provided URI and return the media input object"
[{:keys [::http/client]} uri]
...
(http/req! client
{:method :get :uri uri}
{:response-type :input-stream})
그리고 공유 HTTP 클라이언트는 다음과 같이 구성됩니다:
(http/build-client {:connect-timeout 30000
:follow-redirects :always}))
이것이 전체 취약점입니다:
공격자에게 필요한 것은:
공격 체인은 간단합니다:
이것이 전체 버그입니다.
중요한 차이는 요청이 발생하는 위치입니다.
질문은 다음이 아닙니다:
"Penpot이 URL에서 이미지를 가져올 수 있는가?"
진짜 질문은:
"인증된 사용자가 Penpot 백엔드로 하여금 애플리케이션을 통해 도달할 수 없어야 하는 내부 대상에 연결하도록 만들 수 있는가?"
이 경우, 답은 '예'였습니다.
이는 다음과 같은 실제 차이가 있기 때문에 중요합니다:
이미지 검증이 그 차이를 없애지 않습니다.
일부 직접적인 정보 유출 사례를 좁힐 수는 있지만, SSRF 조건이나 네트워크 경계 위반을 제거하지는 않습니다.
검토된 Penpot 코드 경로와 직접 연결된 통제된 로컬 증명을 통해 이 문제를 검증했습니다.
목표는 타사 인프라를 공격하는 것이 아니었습니다. 목표는 정확한 보안 속성을 증명하는 것이었습니다:
관련 동작을 미러링하는 자체 포함 Java 검증기를 구축했습니다:
content-type 및 content-length 기반 이미지 허용 검사두 가지 사례를 검증했습니다.
검증기가 다음을 요청했습니다:
http://127.0.0.1:7790/internal.png
관찰된 결과:
http://127.0.0.1:7790/internal.pnghttp://127.0.0.1:7790/internal.png200image/png이는 가져오기 스타일의 가져오기 로직이 내부 전용 이미지 엔드포인트를 직접 허용했음을 증명합니다.
그런 다음 검증기가 다음을 요청했습니다:
http://localhost:7791/redirect-to-internal
해당 엔드포인트가 다음으로 HTTP 리디렉션을 반환했습니다:
http://127.0.0.1:7790/internal.png
관찰된 결과:
http://localhost:7791/redirect-to-internalhttp://127.0.0.1:7790/internal.png200image/png내부 전용 리스너가 리디렉션된 요청을 기록했습니다.
이는 더 중요한 주장을 증명했습니다:
여기서 페이로드는 의도적으로 단순했습니다:
Penpot은 단순히 임의 바이트를 가져와서 멈추지 않기 때문에 이것이 중요했습니다. 요청 후 미디어 지향 검증을 수행합니다.
따라서 올바른 증명은 다음이 아니었습니다:
"백엔드가 어딘가에 연결을 시도할 수 있다"
더 강력한 증명은:
"백엔드가 내부에 연결하도록 만들 수 있고, 기능이 기대하는 동일한 이미지와 같은 제약 조건에서 요청을 성공적으로 완료할 수 있다"
이것이 검증에서 정확히 입증된 바입니다.
이런 SSRF 버그에 대한 일반적인 반응은:
"대상은 여전히 이미지를 반환해야 한다"
이 관찰은 사실이지만 불완전합니다.
취약점을 제거하지 않습니다.
어떤 내부 대상이 가장 직접적으로 유용한지만 알려줍니다.
이 문제는 여전히 다음을 가능하게 합니다:
이는 여전히 실제 보안 경계 위반입니다.
특히 자체 호스팅 환경에서는 내부 서비스가 종종 그 경계 뒤에 존재합니다.
이 문제는 최종적으로 높음 심각도의 CVSS로 할당되었습니다:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
이 분류는 타당합니다.
주장은 인증되지 않은 공격자가 처음부터 모든 Penpot 배포를 즉시 손상시킬 수 있다는 것이 아닙니다.
주장은 모든 일반 인증 파일 편집자가 Penpot을 내부 대상에 대한 백엔드 요청 프리미티브로 전환할 수 있으며, 여기에는 루프백 및 사설 네트워크 대상에 대한 리디렉션 지원 접근이 포함된다는 것입니다.
공개 과정에서 심각도에 대한 논의가 있었으며, 주로 다음에 관한 것이었습니다:
이는 논의할 만한 타당한 제약 조건입니다.
그러나 핵심 문제를 제거하지는 않습니다:
이는 실제적이고 방어 가능한 SSRF 취약점입니다.
여기서 중요한 수정은 더 엄격한 MIME 처리가 아닙니다.
진짜 수정은 아웃바운드 목적지 정책입니다.
이 유형의 버그에 대한 올바른 치료는 다음을 필요로 합니다:
http 및 https만 허용localhost이것이 올바른 수정 방향입니다. 이는 이미지 파싱 버그가 아니라 네트워크 신뢰 경계 버그였기 때문입니다.
이 문제는 GitHub의 보안 보고 흐름을 통해 비공개로 보고되었습니다.
보고서에는 다음이 포함되었습니다:
유지 관리자가 문제를 확인하고 해결 작업을 시작했습니다.
이후 다음이 할당되었습니다:
CVE-2026-45806
핵심 교훈은 간단합니다:
원격 미디어 가져오기는 편의 기능이 아니라 아웃바운드 신뢰 경계입니다
많은 개발자가 다음과 같은 사고 방식을 가집니다:
이는 구현 세부 사항입니다.
진정한 보안 질문은:
백엔드가 사용자를 대신하여 어디에 연결할 수 있는가?
이 질문에 명시적으로 답하지 않으면 원격 가져오기와 같은 기능이 기본적으로 SSRF 표면이 됩니다.
이 버그는 SSRF 검토에 대해 중요한 점을 다시 한번 강조합니다:
이것이 진정한 교훈입니다.
이 취약점은 화려한 페이로드에 관한 것이 아니었습니다.
올바른 신뢰 경계 질문을 하는 것에 관한 것이었습니다.
Penpot은 인증된 파일 편집자가 원격 이미지 URL을 제공할 수 있게 했고, 백엔드는 그 URL을 필요한 것보다 더 신뢰했습니다. 리디렉션 처리가 나머지를 처리했습니다.
이것이 CVE-2026-45806이 된 이유입니다.