High
github
·
GHSA-x8gv-g2g3-65fj
SiYuan Agent Tools SSRF via DNS-Rebinding TOCTOU (Bypass of CheckHostSSRF)
Published Oct 2, 2026
CVSS 8.2
Security Advisory — SiYuan Agent Tools SSRF via DNS-Rebinding TOCTOU (Bypass of CheckHostSSRF)
| Field | Value |
|---|---|
| Disclosed by | joysinleung ([email protected]) |
| Report date | 2026-08-13 |
| Product | SiYuan (思源笔记) — siyuan-note/siyuan |
| Go module | github.com/siyuan-note/siyuan/kernel |
| Affected versions | ` 203.0.113.1 (public, passes CheckHostSSRF) |
even query (dial) -> 127.0.0.1 (loopback internal victim)
Directly invoke the real agent-tool functions (v3.8.0 code path):
util.HTTPRequest("GET", "http://rebind.local:15353/secret", ...) util.WebFetch("http://rebind.local:15353/secret", ...)
Result (evidence):
body = "F9_REBIND_PROOF=reached-internal-only-service-via-TOCTOU"
Negative control: CheckHostSSRF("127.0.0.1") -> blocked.
Positive control: CheckHostSSRF("pub.local") -> 203.0.113.1, allowed.
The loopback victim is a faithful stand-in for `169.254.169.254` / any internal address: the guard allowed a public IP while the connection reached a private one.
## Impact
Confidentiality breach via SSRF: an attacker who can steer the agent can read cloud instance metadata (IAM temporary credentials), internal service responses, and anything reachable from the SiYuan kernel's network position. Scope is changed (`S:C`) because the kernel often runs with cloud-instance privileges. Exploitable under default config (SafeMode on or off).
## Scope
Reachable whenever the agent `http_request` / `web_fetch` tool is usable (default AI tooling). Independent of Publish/auth configuration. Not gated by `SafeMode`.
## Remediation
1. **Connect-time enforcement (preferred)**: mount `SSRFSafeDialer` (or a dedicated always-on private-IP `Control` hook) on the transport used by `http_request` / `web_fetch`, independent of `SafeMode` — matching the already-correct `generatedImageDialer`.
2. **Pin resolution**: after `CheckHostSSRF` passes, reuse the *same* resolved IP for the connection (or short-TTL cache) so guard and dial cannot diverge.
3. **Minimum change**: replace `httpclient.NewBrowserRequest()` in `http_request.go` / `webfetch.go` with a custom client whose `DialContext` is `util.SSRFSafeDialer(timeout).DialContext` (not `SafeMode`-gated).
> Note: patch authored against v3.8.0 source; regression-tested in the researcher's environment for the PoC path but not compiled into a full SiYuan release build. Provided for the maintainer to validate in CI.
---
## Appendix: Suggested Patch (F9)
```diff
diff --git a/kernel/util/httprequest.go b/kernel/util/httprequest.go
index aaa..bbb 100644
--- a/kernel/util/httprequest.go
+++ b/kernel/util/httprequest.go
@@ -40,6 +40,18 @@ func CheckHostSSRF(host string) error {
return nil
}
+// SSRFSafeClient returns an *http.Client whose transport enforces the
+// private/loopback/link-local IP block at CONNECT time (independent of SafeMode),
+// closing the DNS-rebinding TOCTOU left by parse-time-only CheckHostSSRF.
+func SSRFSafeClient(timeout time.Duration) *http.Client {
+ return &http.Client{
+ Timeout: timeout,
+ Transport: &http.Transport{
+ DialContext: util.SSRFSafeDialer(timeout).DialContext,
+ },
+ }
+}
+
diff --git a/kernel/mcp/tools/http_request.go b/kernel/mcp/tools/http_request.go
index ccc..ddd 100644
--- a/kernel/mcp/tools/http_request.go
+++ b/kernel/mcp/tools/http_request.go
@@ -90,7 +90,7 @@ func httpRequest(args map[string]any) (CallToolResult, error) {
if serr := util.CheckHostSSRF(u.Hostname()); serr != nil {
return CallToolResult{}, serr
}
- resp, err := httpclient.NewBrowserRequest().Get(rawURL)
+ resp, err := util.SSRFSafeClient(30 * time.Second).Get(rawURL)
...
}
diff --git a/kernel/util/webfetch.go b/kernel/util/webfetch.go
index eee..fff 100644
--- a/kernel/util/webfetch.go
+++ b/kernel/util/webfetch.go
@@ -50,7 +50,7 @@ func WebFetch(rawURL string, ...) (string, error) {
if serr := util.CheckHostSSRF(u.Hostname()); serr != nil {
return "", serr
}
- resp, err := httpclient.NewBrowserRequest().Get(rawURL)
+ resp, err := util.SSRFSafeClient(30 * time.Second).Get(rawURL)
...
}
Affected AI Products
ai agent
openai