CVE-2026-33954

CVE-2026-33954

Area
Web / AppSec
Credit
Public Attribution
Credit Note
CVSS
6.5
CVSS Source
CNA / Vendor
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
CWE
CWE-285
Impact
Information Disclosure
Key Finding
API에서는 가려지는 private note가 웹 상세 화면에서는 같은 visibility filter 없이 렌더링됐습니다.
Published
Mar 27, 2026
Score Note
Severity
Medium
Status
Public
Target
LinkAce · Private note visibility
같은 private note가 API에서는 필터링됐지만 웹 link 상세 화면에서는 그대로 렌더링됐습니다. 두 사용자와 두 인터페이스를 교차해 visibility 정책의 누락 지점을 찾았습니다.
Taegu Ha · LinkAce · CWE-285 · CVSS 6.5
2026년 3월 27일 공개 · LinkAce 2.5.3에서 수정 · 공식 기록

한눈에 보기

🔎
LinkAce API는 note visibility scope를 적용했지만 web template은 $link->notes를 직접 순회했습니다. link 자체를 볼 수 있는 사용자가 다른 사람의 private note까지 볼 수 있었습니다.
구분
확인 내용
프로젝트·컴포넌트
LinkAce web link detail · note rendering
공격 입력
공유·내부 link 페이지 조회
필요 조건
link는 볼 수 있지만 그 link의 private note 권한은 없는 사용자
취약 지점
web template의 raw notes relation과 API의 visibleForUser() 사이 정책 불일치
검증 결과
권한 없는 private note가 web response에 렌더링됨
영향
private note confidentiality 침해
수정 원칙
web·API 모두 동일한 visibility scope와 authorization helper 사용

공격 흐름

검증 결론: 같은 데이터가 API에서는 필터링되지만 web view에서는 필터링되지 않아 presentation 경로별 authorization drift를 확인했습니다.

취약점 개요

LinkAce의 link와 note는 각각 공개 범위를 가집니다. 다른 사용자가 볼 수 있는 internal 또는 public link에 소유자만 볼 private note를 붙일 수 있습니다. 이 경우 parent link가 보인다는 사실이 child note의 공개까지 의미하지 않습니다.
API note 경로는 visibleForUser()를 적용해 타인의 private note를 제외했지만, 웹 상세 template은 $link->notes collection을 그대로 순회했습니다. 공격자는 link를 볼 권한만으로 그 아래 private note 본문까지 읽을 수 있었습니다.
공격자가 갖는 권한
다른 사용자의 internal/public link 열람
보호할 객체
그 link에 연결된 private note
취약 인터페이스
웹 link detail page
정상 대조군
note API의 visibleForUser() 필터

조사 대상

부모 객체와 자식 객체의 visibility가 독립적인 모델을 우선 확인했습니다. controller가 parent link에 대해 authorize한 뒤 relation을 eager/lazy load할 때 child scope가 자동으로 붙는지 추적했습니다.
LinkController::show()는 lists와 tags에는 visibleForUser()를 명시했지만 notes에는 같은 scope를 적용하지 않았습니다. template은 전달받은 $link->notes를 별도 검사 없이 출력했습니다.
부모 link를 볼 수 있지만 child note는 볼 수 없는 사용자가 인터페이스에 따라 다른 결과를 받는가?

원인 분석

victim 계정으로 internal link와 private note를 만들고 attacker 계정에는 link만 보이도록 했습니다. 여기서 link 자체까지 private이면 controller에서 막혀 note 렌더링 경로에 도달하지 않으므로 적절한 경계 입력이 아닙니다.
attacker가 API로 note를 조회했을 때는 private note가 빠졌지만, 같은 link ID의 웹 상세 화면 HTML에는 note 본문이 포함됐습니다. database row나 policy가 잘못된 것이 아니라 controller별 query scope 적용이 달랐습니다.
웹 template과 API query의 차이
// web: link-notes.blade.php @foreach($link->notes as $note) @include('models.notes.partials.single', ['note' => $note]) @endforeach // API path Note::query()->visibleForUser()->... // web relation에는 visibleForUser()가 없음
입력에서 영향까지의 경로
  1. 피해자가 공개 가능한 link에 private note를 추가합니다.
  1. 공격자는 parent link를 볼 수 있습니다.
  1. 웹 controller가 link를 authorize합니다.
  1. notes relation이 visibility scope 없이 로드됩니다.
  1. template이 private note를 HTML에 렌더링합니다.

재현 및 검증

두 사용자, link 공개 범위 두 종류, note private 범위를 조합했습니다. 피해자 화면에서 note가 존재하는지 확인한 뒤 공격자 세션으로 API와 web route를 각각 요청했습니다.
응답 화면만 캡처하지 않고 API JSON과 웹 HTML 원문에서 marker를 검색했습니다. 같은 계정·같은 link·같은 note를 사용해 인터페이스 이외의 변수를 고정했습니다.
$ curl -b attacker.cookie /api/v2/links/42/notes $ curl -b attacker.cookie /links/42
검사
입력·조건
관찰
의미
소유자
victim → web/API
private note 표시
데이터가 정상 생성됨
API 대조군
attacker → note API
private note 없음
visibility policy 존재
웹 경로
attacker → link detail
private marker 표시
web relation에 필터 누락
Private link
attacker → private parent
접근 거부
parent policy와 child leak 구분
같은 attacker 세션에서의 인터페이스 차이
GET /api/v2/links/42/notes 200 OK [] GET /links/42 200 OK ... <div class="note">VICTIM_PRIVATE_NOTE</div> ...

판단 변화

부모 권한

처음에는 다른 사용자의 link를 볼 수 있다는 점 자체를 문제로 볼 수 있었습니다. 하지만 internal/public link는 공유가 의도된 상태였습니다. 문제를 parent link 노출이 아니라 독립적인 private child note 노출로 좁혔습니다.

API와 웹

API가 안전하다는 결과만 보고 전체 제품이 안전하다고 결론내릴 수 없었습니다. 동일 모델을 렌더링하는 web controller와 template을 따로 확인하면서 정책이 query별로 수동 적용된다는 사실을 찾았습니다.

저장 데이터

DB에서 note의 visibility 값이 잘못 저장됐을 가능성을 배제하기 위해 owner 화면과 API 필터를 확인했습니다. 두 결과가 정상이라 저장 문제가 아니라 web read path 문제로 판단했습니다.

영향과 수정

인증된 사용자는 자신이 볼 수 있는 다른 사용자의 link 상세 페이지에서 그 사용자의 private note를 읽을 수 있었습니다. 링크에 내부 메모나 자격 정보가 기록된 경우 기밀성이 침해됩니다.

확인한 범위

  • API에서는 숨겨지는 동일 note의 웹 HTML 노출
  • 두 계정으로 소유자·비소유자 경계 재현
  • parent와 child visibility의 독립성

제외한 범위

  • 비인증 사용자
  • private parent link 우회
  • note 수정·삭제

수정

2.5.3에서는 web detail path에서도 notes relation에 사용자 visibility scope를 적용했습니다. 같은 모델을 여러 controller에서 읽을 때 scope를 호출자 기억에 맡기기보다 relation 또는 policy 계층에서 일관되게 적용하는 방향이 중요합니다.

분석 기준과 증거 보존

항목
최종 감사 결과
소스 기준
LinkAce checkout 605ce0401036bb54408f043ebd3f8ee0366fa036
원본 검증
web template과 API serializer/query scope 비교, 권한 없는 사용자 관점의 렌더 결과 확인
GitHub 보존 상태
Public 저장소 main의 감사 commit c04cacf; evidence 5개와 root manifest.json에 size·SHA-256 고정
무결성
사례별 SHA256SUMS 검증 통과, root manifest의 모든 file hash와 실제 파일 일치, README 상대 링크 확인 완료
보존 경계·제약
실제 사용자 note, database dump와 인증 cookie는 제외
감사 완료
2026-08-23 · Docker/로컬 원본 대조 후 GitHub push 완료

관련 파일

2026-08-23 최종 감사

웹 controller와 note template의 조사 스냅샷, 공개 advisory와 CVE 레코드를 포함했습니다.

느낀 점

데이터의 공개 범위는 row 하나에 붙은 속성이 아니라 관계를 따라갈 때마다 다시 확인해야 하는 조건이었습니다. 부모를 볼 수 있다는 이유로 자식도 볼 수 있다고 가정하는 순간 privacy 경계가 무너졌습니다.
API와 웹을 같은 입력으로 교차한 방법도 이후 조사에 남았습니다. 한 인터페이스의 안전한 구현은 정답지가 되어 다른 인터페이스에서 빠진 scope를 빠르게 찾게 해주었습니다.