""" 沙箱执行工具 在 Docker 沙箱中执行代码和命令进行漏洞验证 """ import asyncio import json import logging import tempfile import os import shutil from typing import Optional, List, Dict, Any from pydantic import BaseModel, Field from dataclasses import dataclass from .base import AgentTool, ToolResult logger = logging.getLogger(__name__) @dataclass class SandboxConfig: """沙箱配置""" image: str = "python:3.11-slim" memory_limit: str = "512m" cpu_limit: float = 1.0 timeout: int = 60 network_mode: str = "none" # none, bridge, host read_only: bool = True user: str = "1000:1000" class SandboxManager: """ 沙箱管理器 管理 Docker 容器的创建、执行和清理 """ def __init__(self, config: Optional[SandboxConfig] = None): self.config = config or SandboxConfig() self._docker_client = None self._initialized = False async def initialize(self): """初始化 Docker 客户端""" if self._initialized: return try: import docker self._docker_client = docker.from_env() # 测试连接 self._docker_client.ping() self._initialized = True logger.info("Docker sandbox manager initialized") except Exception as e: logger.warning(f"Docker not available: {e}") self._docker_client = None @property def is_available(self) -> bool: """检查 Docker 是否可用""" return self._docker_client is not None async def execute_command( self, command: str, working_dir: Optional[str] = None, env: Optional[Dict[str, str]] = None, timeout: Optional[int] = None, ) -> Dict[str, Any]: """ 在沙箱中执行命令 Args: command: 要执行的命令 working_dir: 工作目录 env: 环境变量 timeout: 超时时间(秒) Returns: 执行结果 """ if not self.is_available: return { "success": False, "error": "Docker 不可用", "stdout": "", "stderr": "", "exit_code": -1, } timeout = timeout or self.config.timeout try: # 创建临时目录 with tempfile.TemporaryDirectory() as temp_dir: # 准备容器配置 container_config = { "image": self.config.image, "command": ["sh", "-c", command], "detach": True, "mem_limit": self.config.memory_limit, "cpu_period": 100000, "cpu_quota": int(100000 * self.config.cpu_limit), "network_mode": self.config.network_mode, "user": self.config.user, "read_only": self.config.read_only, "volumes": { temp_dir: {"bind": "/workspace", "mode": "rw"}, }, "working_dir": working_dir or "/workspace", "environment": env or {}, # 安全配置 "cap_drop": ["ALL"], "security_opt": ["no-new-privileges:true"], } # 创建并启动容器 container = await asyncio.to_thread( self._docker_client.containers.run, **container_config ) try: # 等待执行完成 result = await asyncio.wait_for( asyncio.to_thread(container.wait), timeout=timeout ) # 获取日志 stdout = await asyncio.to_thread( container.logs, stdout=True, stderr=False ) stderr = await asyncio.to_thread( container.logs, stdout=False, stderr=True ) return { "success": result["StatusCode"] == 0, "stdout": stdout.decode('utf-8', errors='ignore')[:10000], "stderr": stderr.decode('utf-8', errors='ignore')[:2000], "exit_code": result["StatusCode"], "error": None, } except asyncio.TimeoutError: await asyncio.to_thread(container.kill) return { "success": False, "error": f"执行超时 ({timeout}秒)", "stdout": "", "stderr": "", "exit_code": -1, } finally: # 清理容器 await asyncio.to_thread(container.remove, force=True) except Exception as e: logger.error(f"Sandbox execution error: {e}") return { "success": False, "error": str(e), "stdout": "", "stderr": "", "exit_code": -1, } async def execute_python( self, code: str, timeout: Optional[int] = None, ) -> Dict[str, Any]: """ 在沙箱中执行 Python 代码 Args: code: Python 代码 timeout: 超时时间 Returns: 执行结果 """ # 转义代码中的单引号 escaped_code = code.replace("'", "'\\''") command = f"python3 -c '{escaped_code}'" return await self.execute_command(command, timeout=timeout) async def execute_http_request( self, method: str, url: str, headers: Optional[Dict[str, str]] = None, data: Optional[str] = None, timeout: int = 30, ) -> Dict[str, Any]: """ 在沙箱中执行 HTTP 请求 Args: method: HTTP 方法 url: URL headers: 请求头 data: 请求体 timeout: 超时 Returns: HTTP 响应 """ # 构建 curl 命令 curl_parts = ["curl", "-s", "-S", "-w", "'\\n%{http_code}'", "-X", method] if headers: for key, value in headers.items(): curl_parts.extend(["-H", f"'{key}: {value}'"]) if data: curl_parts.extend(["-d", f"'{data}'"]) curl_parts.append(f"'{url}'") command = " ".join(curl_parts) # 使用带网络的镜像 original_network = self.config.network_mode self.config.network_mode = "bridge" # 允许网络访问 try: result = await self.execute_command(command, timeout=timeout) if result["success"] and result["stdout"]: lines = result["stdout"].strip().split('\n') if lines: status_code = lines[-1].strip() body = '\n'.join(lines[:-1]) return { "success": True, "status_code": int(status_code) if status_code.isdigit() else 0, "body": body[:5000], "error": None, } return { "success": False, "status_code": 0, "body": "", "error": result.get("error") or result.get("stderr"), } finally: self.config.network_mode = original_network async def verify_vulnerability( self, vulnerability_type: str, target_url: str, payload: str, expected_pattern: Optional[str] = None, ) -> Dict[str, Any]: """ 验证漏洞 Args: vulnerability_type: 漏洞类型 target_url: 目标 URL payload: 攻击载荷 expected_pattern: 期望在响应中匹配的模式 Returns: 验证结果 """ verification_result = { "vulnerability_type": vulnerability_type, "target_url": target_url, "payload": payload, "is_vulnerable": False, "evidence": None, "error": None, } try: # 发送请求 response = await self.execute_http_request( method="GET" if "?" in target_url else "POST", url=target_url, data=payload if "?" not in target_url else None, ) if not response["success"]: verification_result["error"] = response.get("error") return verification_result body = response.get("body", "") status_code = response.get("status_code", 0) # 检查响应 if expected_pattern: import re if re.search(expected_pattern, body, re.IGNORECASE): verification_result["is_vulnerable"] = True verification_result["evidence"] = f"响应中包含预期模式: {expected_pattern}" else: # 根据漏洞类型进行通用检查 if vulnerability_type == "sql_injection": error_patterns = [ r"SQL syntax", r"mysql_fetch", r"ORA-\d+", r"PostgreSQL.*ERROR", r"SQLite.*error", r"ODBC.*Driver", ] for pattern in error_patterns: if re.search(pattern, body, re.IGNORECASE): verification_result["is_vulnerable"] = True verification_result["evidence"] = f"SQL错误信息: {pattern}" break elif vulnerability_type == "xss": if payload in body: verification_result["is_vulnerable"] = True verification_result["evidence"] = "XSS payload 被反射到响应中" elif vulnerability_type == "command_injection": # 检查命令执行结果 if "uid=" in body or "root:" in body: verification_result["is_vulnerable"] = True verification_result["evidence"] = "命令执行成功" verification_result["response_status"] = status_code verification_result["response_length"] = len(body) except Exception as e: verification_result["error"] = str(e) return verification_result class SandboxCommandInput(BaseModel): """沙箱命令输入""" command: str = Field(description="要执行的命令") timeout: int = Field(default=30, description="超时时间(秒)") class SandboxTool(AgentTool): """ 沙箱执行工具 在安全隔离的环境中执行代码和命令 """ # 允许的命令前缀 ALLOWED_COMMANDS = [ "python", "python3", "node", "curl", "wget", "cat", "head", "tail", "grep", "find", "ls", "echo", "printf", "test", "id", "whoami", ] def __init__(self, sandbox_manager: Optional[SandboxManager] = None): super().__init__() self.sandbox_manager = sandbox_manager or SandboxManager() @property def name(self) -> str: return "sandbox_exec" @property def description(self) -> str: return """在安全沙箱中执行命令或代码。 用于验证漏洞、测试 PoC 或执行安全检查。 ⚠️ 安全限制: - 命令在 Docker 容器中执行 - 网络默认隔离 - 资源有限制 - 只允许特定命令 允许的命令: python, python3, node, curl, cat, grep, find, ls, echo, id 使用场景: - 验证命令注入漏洞 - 执行 PoC 代码 - 测试 payload 效果""" @property def args_schema(self): return SandboxCommandInput async def _execute( self, command: str, timeout: int = 30, **kwargs ) -> ToolResult: """执行沙箱命令""" # 初始化沙箱 await self.sandbox_manager.initialize() if not self.sandbox_manager.is_available: return ToolResult( success=False, error="沙箱环境不可用(Docker 未安装或未运行)", ) # 安全检查:验证命令是否允许 cmd_parts = command.strip().split() if not cmd_parts: return ToolResult(success=False, error="命令不能为空") base_cmd = cmd_parts[0] if not any(base_cmd.startswith(allowed) for allowed in self.ALLOWED_COMMANDS): return ToolResult( success=False, error=f"命令 '{base_cmd}' 不在允许列表中。允许的命令: {', '.join(self.ALLOWED_COMMANDS)}", ) # 执行命令 result = await self.sandbox_manager.execute_command( command=command, timeout=timeout, ) # 格式化输出 output_parts = ["🐳 沙箱执行结果\n"] output_parts.append(f"命令: {command}") output_parts.append(f"退出码: {result['exit_code']}") if result["stdout"]: output_parts.append(f"\n标准输出:\n```\n{result['stdout']}\n```") if result["stderr"]: output_parts.append(f"\n标准错误:\n```\n{result['stderr']}\n```") if result.get("error"): output_parts.append(f"\n错误: {result['error']}") return ToolResult( success=result["success"], data="\n".join(output_parts), error=result.get("error"), metadata={ "command": command, "exit_code": result["exit_code"], } ) class HttpRequestInput(BaseModel): """HTTP 请求输入""" method: str = Field(default="GET", description="HTTP 方法 (GET, POST, PUT, DELETE)") url: str = Field(description="请求 URL") headers: Optional[Dict[str, str]] = Field(default=None, description="请求头") data: Optional[str] = Field(default=None, description="请求体") timeout: int = Field(default=30, description="超时时间(秒)") class SandboxHttpTool(AgentTool): """ 沙箱 HTTP 请求工具 在沙箱中发送 HTTP 请求 """ def __init__(self, sandbox_manager: Optional[SandboxManager] = None): super().__init__() self.sandbox_manager = sandbox_manager or SandboxManager() @property def name(self) -> str: return "sandbox_http" @property def description(self) -> str: return """在沙箱中发送 HTTP 请求。 用于测试 Web 漏洞如 SQL 注入、XSS、SSRF 等。 输入: - method: HTTP 方法 - url: 请求 URL - headers: 可选,请求头 - data: 可选,请求体 - timeout: 超时时间 使用场景: - 验证 SQL 注入漏洞 - 测试 XSS payload - 验证 SSRF 漏洞 - 测试认证绕过""" @property def args_schema(self): return HttpRequestInput async def _execute( self, url: str, method: str = "GET", headers: Optional[Dict[str, str]] = None, data: Optional[str] = None, timeout: int = 30, **kwargs ) -> ToolResult: """执行 HTTP 请求""" await self.sandbox_manager.initialize() if not self.sandbox_manager.is_available: return ToolResult( success=False, error="沙箱环境不可用", ) result = await self.sandbox_manager.execute_http_request( method=method, url=url, headers=headers, data=data, timeout=timeout, ) output_parts = ["🌐 HTTP 请求结果\n"] output_parts.append(f"请求: {method} {url}") if headers: output_parts.append(f"请求头: {json.dumps(headers, ensure_ascii=False)}") if data: output_parts.append(f"请求体: {data[:500]}") output_parts.append(f"\n状态码: {result.get('status_code', 'N/A')}") if result.get("body"): body = result["body"] if len(body) > 2000: body = body[:2000] + f"\n... (截断,共 {len(result['body'])} 字符)" output_parts.append(f"\n响应内容:\n```\n{body}\n```") if result.get("error"): output_parts.append(f"\n错误: {result['error']}") return ToolResult( success=result["success"], data="\n".join(output_parts), error=result.get("error"), metadata={ "method": method, "url": url, "status_code": result.get("status_code"), "response_length": len(result.get("body", "")), } ) class VulnerabilityVerifyInput(BaseModel): """漏洞验证输入""" vulnerability_type: str = Field(description="漏洞类型 (sql_injection, xss, command_injection, etc.)") target_url: str = Field(description="目标 URL") payload: str = Field(description="攻击载荷") expected_pattern: Optional[str] = Field(default=None, description="期望在响应中匹配的正则模式") class VulnerabilityVerifyTool(AgentTool): """ 漏洞验证工具 在沙箱中验证漏洞是否真实存在 """ def __init__(self, sandbox_manager: Optional[SandboxManager] = None): super().__init__() self.sandbox_manager = sandbox_manager or SandboxManager() @property def name(self) -> str: return "verify_vulnerability" @property def description(self) -> str: return """验证漏洞是否真实存在。 发送包含攻击载荷的请求,分析响应判断漏洞是否可利用。 输入: - vulnerability_type: 漏洞类型 - target_url: 目标 URL - payload: 攻击载荷 - expected_pattern: 可选,期望在响应中匹配的模式 支持的漏洞类型: - sql_injection: SQL 注入 - xss: 跨站脚本 - command_injection: 命令注入 - path_traversal: 路径遍历 - ssrf: 服务端请求伪造""" @property def args_schema(self): return VulnerabilityVerifyInput async def _execute( self, vulnerability_type: str, target_url: str, payload: str, expected_pattern: Optional[str] = None, **kwargs ) -> ToolResult: """执行漏洞验证""" await self.sandbox_manager.initialize() if not self.sandbox_manager.is_available: return ToolResult( success=False, error="沙箱环境不可用", ) result = await self.sandbox_manager.verify_vulnerability( vulnerability_type=vulnerability_type, target_url=target_url, payload=payload, expected_pattern=expected_pattern, ) output_parts = ["🔍 漏洞验证结果\n"] output_parts.append(f"漏洞类型: {vulnerability_type}") output_parts.append(f"目标: {target_url}") output_parts.append(f"Payload: {payload[:200]}") if result["is_vulnerable"]: output_parts.append(f"\n🔴 结果: 漏洞已确认!") output_parts.append(f"证据: {result.get('evidence', 'N/A')}") else: output_parts.append(f"\n🟢 结果: 未能确认漏洞") if result.get("error"): output_parts.append(f"错误: {result['error']}") if result.get("response_status"): output_parts.append(f"\nHTTP 状态码: {result['response_status']}") return ToolResult( success=True, data="\n".join(output_parts), metadata={ "vulnerability_type": vulnerability_type, "is_vulnerable": result["is_vulnerable"], "evidence": result.get("evidence"), } )