Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-34213 — 권한이 낮은 Docmost 사용자가 피해자의 attachmentId를 일반 업로드 엔드포인트에 제공하여 동일한 작업 공간 내에서 다른 페이지의 저장된 첨부 파일을 덮어쓸 수 있습니다. | Kitploit
도구/GitHubGitHub/0xmrma/cve-2026-34213
Vulnerability AnalysisWeb Application ExploitationPenetration TestingPapers & ResearchLearning & Education
GitHub0xmrma/cve-2026-34213

CVE-2026-34213

권한이 낮은 Docmost 사용자가 피해자의 attachmentId를 일반 업로드 엔드포인트에 제공하여 동일한 작업 공간 내에서 다른 페이지의 저장된 첨부 파일을 덮어쓸 수 있습니다.

저장소 보기
442개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-34213

Docmost의 권한이 낮은 사용자가 피해자의 attachmentId를 일반 업로드 엔드포인트에 제공하고 동일한 워크스페이스 내 다른 페이지의 저장된 첨부 파일을 덮어쓸 수 있습니다.

소개

저는 Docmost(오픈소스 협업 문서화 플랫폼)에서 High 심각도의 인가 결함을 식별하고, 책임감 있게 공개했으며, 재현했습니다.

Docmost의 공식 사이트는 이 플랫폼을 300만 회 이상 다운로드된 엔터프라이즈급 온프레미스 위키로 소개하며, Vilnius City, Bechtle, 호주 정부, 적십자, ETS Quebec 등 조직의 팀들이 신뢰한다고 밝히고 있습니다.

버그는 Docmost가 다이어그램 저장/업데이트 흐름에도 사용하는 일반 파일 업로드 경로에 존재했습니다.

저는 매우 구체적인 질문을 가지고 그 코드를 검토하고 있었습니다.

업로드 엔드포인트가 한 페이지에 대한 편집 접근을 증명하지만, 덮어쓰기 대상이 사용자가 제어하는 별도의 첨부 ID로 선택되면 어떻게 될까요?

이 경우, 그 질문은 실제 객체 바인딩 실패로 바로 이어졌습니다.

Docmost는 호출자가 다음을 보낼 수 있도록 허용했습니다.

  • 편집이 허용된 페이지의 pageId
  • 동일한 워크스페이스 내 다른 페이지에 속하는 attachmentId

서버는 덮어쓰기 일관성 검사를 수행했지만, 가드가 잘못된 부울 로직을 사용했습니다.

즉, 요청이 인가를 통과하고 피해자 첨부 파일을 어쨌든 덮어쓸 수 있었습니다.

이 이슈는 CVE-2026-34213이 되었습니다.

Docmost: docmost/docmost
Advisory: GHSA-89fp-2hch-j9gp
CVE: CVE-2026-34213
패치 버전: v0.71.0

---
photo0

공격 체인

공격자가 제어하는 편집 접근 권한이 있는 pageId -> 공격자가 제어하는 피해자 attachmentId -> 결함이 있는 덮어쓰기 가드가 페이지 간 덮어쓰기를 유효한 것으로 처리 -> 피해자 attachmentId로부터 스토리지 경로 재구성 -> 공격자 바이트가 피해자 파일을 대체 -> 피해자 페이지가 계속 수정된 첨부 파일을 제공


Docmost의 이 부분이 하는 일

Docmost는 업로드된 페이지 첨부 파일을 데이터베이스 레코드와 스토리지의 백업 파일로 저장합니다.

일반 업로드의 경우 서버는 새 첨부 ID를 생성하고 새 파일을 씁니다.

그러나 다이어그램 저장/업데이트 흐름의 경우 클라이언트는 의도적으로 기존 attachmentId를 재사용하여 매번 새 첨부 레코드를 생성하는 대신 동일한 다이어그램 파일을 제자리에서 업데이트할 수 있도록 합니다.

그 동작 자체는 합법적입니다.

문제는 이것이 고위험 경로를 생성한다는 점입니다.

  • 하나의 입력은 인가되는 페이지를 식별합니다.
  • 다른 입력은 덮어쓸 첨부 파일을 식별합니다.

엔드포인트가 이 두 가지 책임을 혼합할 때마다 구현은 이들을 정확하게 바인딩해야 합니다.

Docmost는 그렇게 하지 못했습니다.


이 표면이 살펴볼 가치가 있었던 이유

생성/업데이트 혼합 엔드포인트는 인가 버그가 흔한 곳입니다.

그 이유는 간단합니다.

  • 생성 흐름은 일반적으로 컨테이너 객체에 대해 인가됩니다.
  • 업데이트 흐름은 일반적으로 기존 레코드에 대해 인가됩니다.
  • 하나의 엔드포인트가 둘 다 수행하려고 하면 잘못된 것을 먼저 검증하고 두 번째 식별자를 '그냥 메타데이터'로 취급하기 쉽습니다.

이것이 바로 여기의 패턴입니다.

POST /api/files/upload는 호출자가 pageId가 지정한 페이지를 편집할 수 있는지 검증했습니다.

그러나 attachmentId도 제공되면 서버는 덮어쓰기 경로로 전환하고 별도로 기존 첨부 레코드를 선택했습니다.

이로 인해 중요한 보안 질문이 생겼습니다.

덮어쓰기 경로가 선택된 첨부 파일이 실제로 인가된 페이지에 속한다는 것을 증명합니까?

취약한 버전에서의 대답은 '아니오'였습니다.


근본 원인

근본 원인은 사용자 제어 키를 통한 인가 우회와 덮어쓰기 가드의 부울 로직 버그가 결합된 것이었습니다.

취약한 흐름은 다음과 같습니다.

  1. AttachmentController.uploadFile()이 멀티파트 폼 데이터에서 pageId를 읽었습니다.
  2. 해당 페이지를 로드하고 validateCanEdit(page, user)를 호출했습니다.
  3. 동일한 요청에서 선택적 attachmentId를 별도로 허용했습니다.
  4. AttachmentService.uploadFile()이 공격자가 제공한 ID로 기존 첨부 파일을 로드했습니다.
  5. 덮어쓰기 가드가 기존 첨부 파일이 인가된 페이지와 일치하는지 확인하려고 시도했습니다.
  6. 가드가 &&를 사용했지만, 불일치가 있으면 거부해야 했습니다.

취약한 가드는 다음과 같았습니다.

root@kitploit:~
if (
  existingAttachment.pageId !== pageId &&
  existingAttachment.fileExt !== preparedFile.fileExtension &&
  existingAttachment.workspaceId !== workspaceId
) {
  throw new BadRequestException("File attachment does not match");
}

해당 조건은 다음과 같은 경우에만 요청을 거부했습니다.

  • 페이지 ID가 일치하지 않고,
  • 파일 확장자가 일치하지 않고,
  • 워크스페이스 ID가 일치하지 않는 경우

모두 동시에.

이는 덮어쓰기 가드가 해야 할 일과 반대입니다.

실제 공격 상황에서 공격자는 의도적으로 동일한 워크스페이스 내에 머물렀습니다.

따라서:

  • existingAttachment.workspaceId !== workspaceId는 false였습니다.

해당 피연산자가 false가 되자, 첨부 파일이 다른 페이지에 속하더라도 전체 && 조건이 false로 평가되었습니다.

따라서 서버는 페이지 간 덮어쓰기를 유효한 것으로 처리했습니다.

그것이 버그의 전반부였습니다.

후반부는 실제 영향을 실현한 부분입니다.

검사 후 서비스는 공격자가 제공한 attachmentId와 파일 이름을 사용하여 대상 스토리지 경로를 재구성했습니다.

root@kitploit:~
const filePath =
  `${getAttachmentFolderPath(AttachmentType.File, workspaceId)}/` +
  `${attachmentId}/${preparedFile.fileName}`;

그런 다음 업데이트 경로에서 Docmost는 다음과 같은 변경 가능한 메타데이터만 업데이트했습니다.

  • fileSize
  • updatedAt

소유권을 공격자 페이지로 다시 바인딩하지 않았습니다.

따라서 피해자 페이지는 계속 동일한 첨부 레코드와 동일한 첨부 ID를 가리켰습니다. 기본 파일 바이트만 변경되었습니다.

이것이 단순한 불일치가 아닌 이유입니다.

지속적인 무단 덮어쓰기 원시(primitive)였습니다.


이것이 단순한 로직 실수가 아닌 보안 문제인 이유

이것은 미용상의 버그도 아니고 파일 이름 충돌 문제도 아닙니다.

공격자에게는 경쟁 조건이 필요하지 않았습니다. 공격자가 무작위 경로를 추측할 필요도 없었습니다. 공격자가 피해자 페이지에 쓰기 접근 권한이 필요하지도 않았습니다.

필요한 것은 다음과 같습니다.

  • 피해자 첨부 파일 참조를 알기 위한 읽기 접근 권한
  • 동일한 워크스페이스 내 다른 페이지에 대한 쓰기 접근 권한

그로부터 공격자는 피해자 페이지가 계속 해당 첨부 파일을 참조하고 아무 일도 없었던 것처럼 제공하는 동안 다른 페이지의 첨부 파일에 대한 저장된 파일 바이트를 대체할 수 있었습니다.

이는 직접적인 무결성 실패입니다.

실질적으로 공격자는 다음을 수행할 수 있었습니다.

  • 다이어그램을 변조
  • 첨부 파일을 오해의 소지가 있는 콘텐츠로 대체
  • 참조된 파일을 손상
  • 첨부 파일이 여전히 피해자 페이지에 속하는 것처럼 보이기 때문에 혼란스러운 감사 추적을 생성

중요한 점은 이것입니다.

서버는 공격자가 선택한 덮어쓰기 대상을 허용했으며, 실제로 확인된 페이지에 바인딩하지 않았습니다.

이는 나쁜 부울 위생이 아니라 접근 제어 실패입니다.


익스플로잇이 실용적이었던 이유

익스플로잇은 특히 다이어그램 첨부 파일에 대해 실용적이었습니다.

Docmost의 클라이언트는 다이어그램 저장을 위해 의도적으로 attachmentId를 재사용하고 결정적 파일 이름을 사용합니다.

  • diagram.excalidraw.svg
  • diagram.drawio.svg

이는 공격자의 요구 사항을 낮추기 때문에 중요합니다.

일반 첨부 파일의 경우 공격자는 다음이 모두 필요합니다.

  • 피해자 첨부 ID
  • 피해자 파일 이름

다이어그램의 경우 파일 이름은 이미 예측 가능합니다.

따라서 공격자가 피해자 페이지 내용을 읽을 수 있다면, 종종 필요한 유일한 누락 조각을 복구할 수 있습니다.

  • 피해자 attachmentId

제 검증 설정에서 저는 정확히 그 경로를 사용했습니다.

  • 공격자는 피해자 공간에 대해 읽기 접근 권한만 가졌습니다.
  • 공격자는 공격자가 제어하는 다른 공간에 대해 쓰기 접근 권한을 가졌습니다.
  • 두 공간 모두 동일한 워크스페이스에 속했습니다.

그것으로 충분했습니다.

익스플로잇은 동일한 워크스페이스 내에서 페이지 경계와 공간 경계를 넘어, 결함이 있는 워크스페이스 검사를 여전히 만족시키면서 실행되었습니다.


개념 증명(PoC)

저는 docmost/docmost:0.70.3, Postgres, Redis로 구축된 일회용 랩을 사용하여 Docmost v0.70.3 에 대해 이슈를 라이브로 검증했습니다.

PoC 흐름은 다음과 같습니다.

  1. 소유자 계정을 만듭니다.
  2. 동일한 워크스페이스에 피해자 공간과 공격자가 제어하는 공간을 만듭니다.
  3. 두 번째 사용자를 공격자로 초대합니다.
  4. 공격자에게 다음을 부여합니다.
    • 피해자 공간에 대한 읽기 접근
    • 공격자가 제어하는 공간에 대한 쓰기 접근
  5. 피해자 공간에서 피해자 페이지에 다이어그램 첨부 파일을 업로드합니다.
  6. 공격자로서 피해자 페이지 정보를 검색하고 피해자 attachmentId를 기록합니다.
  7. POST /api/files/upload를 다음 항목과 함께 보냅니다.
    • pageId = 공격자 페이지 ID
    • attachmentId = 피해자 첨부 ID
    • file = 피해자 파일 이름을 사용하는 공격자 제어 대체 파일
  8. 덮어쓰기 전후의 피해자 첨부 파일을 다운로드하고 해시를 비교합니다.

최소 요청 형태는 다음과 같습니다.

root@kitploit:~
POST /api/files/upload
Content-Type: multipart/form-data

pageId=<attackerPageId>
attachmentId=<victimAttachmentId>
[email protected];filename=diagram.excalidraw.svg

관찰된 라이브 결과는 다음과 같습니다.

  • 공격자가 학습한 피해자 첨부 ID: 019d18ae-b176-751c-8525-b5f3cede131d
  • 덮어쓰기 요청에 사용된 공격자 페이지 ID: 019d18ae-b15b-70e9-ac67-64948e87cc5e
  • 피해자 소유자 페이지 ID가 그대로 유지됨: 019d18ae-b12f-75ec-8c1c-5aff3ba6be9c
  • 덮어쓰기 요청에 대한 서버 응답: 200 OK
  • 덮어쓰기 전 피해자 파일 SHA-256:
root@kitploit:~
686a0a0ede90ece1cbb975bb29304a6c3a90373a9c3ab2496345cf7ca59cc8fa
  • 덮어쓰기 후 피해자 파일 SHA-256:
root@kitploit:~
e0168298846cdaf75c4d880f4b721d7c0ef0ef310f75617bf2b833af34cdbeba
  • 공격자 페이로드 SHA-256:
root@kitploit:~
e0168298846cdaf75c4d880f4b721d7c0ef0ef310f75617bf2b833af34cdbeba
  • 마운트된 스토리지에서 피해자 경로에 이제 다음이 포함됨을 확인했습니다.
root@kitploit:~
Attacker replacement from another page

이는 이론적인 소스 검토가 아닌 완전한 종단간 덮어쓰기 증명입니다.


PoC가 이렇게 선택된 이유

트라이지 중 저는 두 가지 스타일의 증명을 사용했습니다.

  • 취약한 덮어쓰기 로직을 미러링한 좁은 독립형 harness
  • 일회용 Docmost 인스턴스에 대한 전체 라이브 HTTP 익스플로잇

독립형 harness는 부울 로직 실패를 격리하는 데 유용했습니다.

라이브 HTTP PoC는 더 강력한 증거였습니다. 전체 보안 스토리를 증명했기 때문입니다.

  • 페이지 인가는 공격자 페이지에서 성공
  • 피해자 attachmentId가 허용됨
  • 덮어쓰기 요청이 성공 반환
  • 피해자 페이지가 논리적 소유자로 남음
  • 디스크에 저장된 바이트가 공격자 제어 콘텐츠로 변경됨

이 구분은 접근 제어 버그에서 중요합니다.

"조건이 잘못되었다"는 것만으로는 충분하지 않습니다.

"조건이 잘못되었고, 애플리케이션이 종단간 지속적인 무단 덮어쓰기로 구동될 수 있다"는 것이 완전한 사례입니다.


수정 분석

수정은 v0.71.0에서 제공되었으며, 덮어쓰기 가드를 &&에서 ||로 변경했습니다.

root@kitploit:~
if (
  existingAttachment.pageId !== pageId ||
  existingAttachment.fileExt !== preparedFile.fileExtension ||
  existingAttachment.workspaceId !== workspaceId
) {
  throw new BadRequestException("File attachment does not match");
}

해당 패치는 보고된 버그에 대해 최소화되고 직접적이며 정확합니다.

올바른 규칙을 복원합니다.

덮어쓰기는 기존 첨부 파일이 인가된 페이지/워크스페이스/유형 가정과 정확히 일치하는 경우에만 허용됩니다.

가드가 불일치가 있으면 거부하도록 변경되면:

  • 페이지 간 덮어쓰기 실패
  • 워크스페이스 간 덮어쓰기 실패
  • 유형/확장자 불일치 실패

이는 올바른 종류의 수정이었습니다.

  • 재설계 없음
  • 모호한 호환성 로직 없음
  • "최선의 노력" 복구 시도 없음

인가된 페이지와 덮어쓰기 대상 간의 엄격한 바인딩일 뿐입니다.

여전히 더 넓은 공학적 교훈이 있습니다.

내부 업데이트 흐름도 제공하는 일반 업로드 엔드포인트는 고위험 API 표면으로 취급되어야 합니다.

즉각적인 버그가 수정되더라도 장기적으로 더 강력한 설계는 다음과 같습니다.

  • 다이어그램 저장 흐름을 위한 전용 업데이트 엔드포인트
  • 파일 이름과 첨부 ID 모두에 대한 변경 불가능한 바인딩 검사
  • 페이지 간 덮어쓰기 시도를 명시적으로 모델링하는 회귀 테스트 범위

그러나 취약점 자체에 대해서는 게시된 패치가 핵심 이슈를 깔끔하게 종결했습니다.


중요한 회귀 테스트 케이스

프로젝트가 수정 사항에 대한 자체 비공개 테스트를 추가했는지 여부와 관계없이 장기적인 범위에 중요한 사례는 다음과 같습니다.

  • 동일한 페이지 ID와 동일한 첨부 ID로 덮어쓰기 시도는 성공해야 함
  • 다른 페이지 ID와 동일한 워크스페이스로 덮어쓰기 시도는 실패해야 함
  • 다른 워크스페이스로 덮어쓰기 시도는 실패해야 함
  • 일치하지 않는 파일 확장자로 덮어쓰기 시도는 실패해야 함
  • 알려진 다이어그램 파일 이름이지만 외부 첨부 ID를 사용한 덮어쓰기 시도는 실패해야 함
  • 덮어쓰기는 공격자가 제어하는 바이트가 기록된 후 피해자 소유권을 조용히 유지해서는 안 됨

이러한 테스트의 요점은 단순한 정확성이 아닙니다.

미래의 "유용한" 업로드 리팩터링이 동일한 버그 클래스를 다시 열지 않도록 인가 바인딩을 잠그는 것입니다.


심각도 및 분류

게시된 권고는 이 이슈를 다음과 같이 분류했습니다.

  • CWE-639: 사용자 제어 키를 통한 인가 우회
  • CVSS v3.1:
root@kitploit:~
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L

이는 7.1 / High에 해당하며 올바른 결론입니다.

여기서 중요한 지표는 무결성입니다.

이것은 낮은 등급의 메타데이터 버그가 아니었습니다. 공격자는 다른 페이지의 첨부 파일 경로에 기록된 대체 바이트를 완전히 제어했으며, 피해자 페이지는 이후에도 수정된 객체를 계속 제공했습니다.

이는 정확히 저장된 교차 레코드 변조의 종류로, Integrity High를 받을 자격이 있습니다.

Availability Low는 또한 말이 됩니다. 다이어그램이나 첨부 문서를 손상시키면 피해자 콘텐츠를 사용할 수 없게 만들 수 있지만, 주요 영향은 완전한 서비스 중단보다는 여전히 무단 수정입니다.


공개

저는 GitHub Security Advisories를 통해 개인적으로 이슈를 보고했습니다.

  • 근본 원인 분석
  • 라이브 HTTP PoC
  • 요청/응답 증거
  • 전후 파일 해시
  • 고정된 일회용 랩 설정

이슈는 관리자에 의해 수락되었으며, CVE-2026-34213이 할당되었고 2026년 4월 14일에 게시되었습니다.

공개 권고는 다음을 나열합니다.

  • 영향을 받는 버전: >= v0.3.0
  • 패치된 버전: v0.71.0

해당 이력은 또한 제 로컬 소스 검토와 일치했습니다. 취약한 덮어쓰기 로직은 제가 취약한 라인을 확인한 가장 초기 태그 릴리스에 존재했습니다.


이 버그가 실제로 가르치는 것

흥미로운 교훈은 단순히 "&& 대신 ||를 사용하라"는 것이 아닙니다.

그것은 증상입니다.

더 깊은 교훈은 다음과 같습니다.

하나의 사용자 제어 필드가 인가를 증명하고 다른 사용자 제어 필드가 업데이트되는 객체를 선택하는 경우, 이 두 필드는 명시적이고 정확하게 바인딩되어야 합니다.

이 규칙은 모든 곳에 나타납니다.

  • 문서 첨부 파일
  • 프로필 미디어
  • 클라우드 객체 참조
  • 이슈/댓글 편집
  • 백그라운드 작업 재처리

시스템이 다음과 같이 말하는 순간:

  • "페이지 X를 편집할 수 있습니다."
  • "어떤 기존 레코드를 업데이트할지 알려주십시오."

정확한 일치 불변 조건으로 적용되어야 하는 보안 경계를 생성한 것입니다.

이보다 약한 것은 조만간 사용자 제어 키 버그로 변합니다.

이 이슈는 또한 과소평가하기 쉬운 두 번째 요점을 강화합니다.

가드 코드의 작은 부울 실수는 일차적인 보안 결과를 초래할 수 있습니다.

"눈에 합리적으로 보이는" 세 절 조건이 덮어쓰기 경로의 보호 모델을 역전시키기에 충분했습니다.

그렇기 때문에 이러한 표면은 캐주얼한 확신보다 신중한 검토가 필요합니다.


주요 요점

  • Docmost는 새 업로드와 내부 첨부 업데이트 모두에 하나의 엔드포인트를 사용했습니다.
  • 인가는 호출자가 제공한 pageId에 대해 확인되었지만, 덮어쓰기 대상 선택은 호출자가 제공한 별도의 attachmentId를 사용했습니다.
  • 덮어쓰기 가드는 모든 불일치 조건이 동시에 참일 때만 거부했습니다.
  • 동일한 워크스페이스 공격 사례에서 해당 검사는 열린 상태로 실패했습니다.
  • 서비스는 피해자 첨부 ID에서 스토리지 경로를 재구성하고 공격자 제어 바이트를 그 안에 썼습니다.
  • 첨부 레코드는 덮어쓰기 후에도 피해자 페이지에 바인딩된 상태로 유지되었습니다.
  • 결정적 다이어그램 파일 이름은 익스플로잇을 특히 실용적으로 만들었습니다.
  • v0.71.0의 수정은 올바르게 가드를 변경하여 불일치가 있으면 거부하도록 했습니다.

마지막 말

이 취약점은 이국적인 스토리지 동작에 관한 것이 아니었습니다.

업데이트 경로가 공격자가 선택한 객체 식별자를 너무 많이 신뢰한 것에 관한 것이었습니다.

Docmost는 한 페이지에 대한 편집 접근을 증명하고, 다른 페이지의 기존 첨부 ID를 수락한 다음, 결함이 있는 덮어쓰기 검사로 인해 해당 불일치가 성공적인 교차 페이지 파일 대체로 바뀌도록 했습니다.

그것이 CVE-2026-34213이 된 이유입니다.

v0.71.0의 패치는 즉각적인 이슈를 깔끔하게 수정했지만, 더 넓은 교훈은 여전히 가치 있습니다.

인가와 객체 선택이 별도의 사용자 제어 필드로 분할될 때, 정확한 바인딩이 보안 속성입니다.

도구 다운로드