724 lines
23 KiB
Python
724 lines
23 KiB
Python
|
|
"""
|
|||
|
|
Kunlun-M 静态代码分析工具集成
|
|||
|
|
|
|||
|
|
Kunlun-M (昆仑镜) 是一款开源的静态代码安全审计工具,
|
|||
|
|
支持 PHP、JavaScript 等语言的语义分析和漏洞检测。
|
|||
|
|
|
|||
|
|
MIT License
|
|||
|
|
Copyright (c) 2017 Feei. <feei@feei.cn> All rights reserved
|
|||
|
|
|
|||
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|||
|
|
of this software and associated documentation files (the "Software"), to deal
|
|||
|
|
in the Software without restriction, including without limitation the rights
|
|||
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|||
|
|
copies of the Software, and to permit persons to whom the Software is
|
|||
|
|
furnished to do so, subject to the following conditions:
|
|||
|
|
|
|||
|
|
The above copyright notice and this permission notice shall be included in all
|
|||
|
|
copies or substantial portions of the Software.
|
|||
|
|
|
|||
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|||
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|||
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
|
|
SOFTWARE.
|
|||
|
|
|
|||
|
|
原始项目: https://github.com/LoRexxar/Kunlun-M
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import asyncio
|
|||
|
|
import json
|
|||
|
|
import logging
|
|||
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
import tempfile
|
|||
|
|
import subprocess
|
|||
|
|
from typing import Optional, List, Dict, Any
|
|||
|
|
from pydantic import BaseModel, Field
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
from .base import AgentTool, ToolResult
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
# Kunlun-M 安装路径(相对于项目根目录)
|
|||
|
|
KUNLUN_M_PATH = os.path.join(
|
|||
|
|
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))),
|
|||
|
|
"Kunlun-M-master"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class KunlunScanInput(BaseModel):
|
|||
|
|
"""Kunlun-M 扫描输入"""
|
|||
|
|
target_path: str = Field(
|
|||
|
|
description="要扫描的目录或文件路径(相对于项目根目录)"
|
|||
|
|
)
|
|||
|
|
language: Optional[str] = Field(
|
|||
|
|
default=None,
|
|||
|
|
description="指定扫描语言: php, javascript, solidity, chromeext。不指定则自动检测"
|
|||
|
|
)
|
|||
|
|
rules: Optional[str] = Field(
|
|||
|
|
default=None,
|
|||
|
|
description="指定规则ID,多个规则用逗号分隔,如: 1000,1001,1002"
|
|||
|
|
)
|
|||
|
|
tamper: Optional[str] = Field(
|
|||
|
|
default=None,
|
|||
|
|
description="指定 tamper 名称,用于自定义修复函数检测"
|
|||
|
|
)
|
|||
|
|
include_unconfirmed: bool = Field(
|
|||
|
|
default=False,
|
|||
|
|
description="是否包含未确认的漏洞(疑似漏洞)"
|
|||
|
|
)
|
|||
|
|
max_results: int = Field(
|
|||
|
|
default=50,
|
|||
|
|
description="最大返回结果数"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class KunlunRuleListInput(BaseModel):
|
|||
|
|
"""Kunlun-M 规则列表输入"""
|
|||
|
|
language: Optional[str] = Field(
|
|||
|
|
default=None,
|
|||
|
|
description="按语言过滤规则: php, javascript, solidity, chromeext"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class KunlunMTool(AgentTool):
|
|||
|
|
"""
|
|||
|
|
Kunlun-M (昆仑镜) 静态代码安全审计工具
|
|||
|
|
|
|||
|
|
特点:
|
|||
|
|
- 语义分析:深度AST分析,减少误报
|
|||
|
|
- 多语言支持:PHP、JavaScript 语义分析,Solidity、Chrome Extension 基础扫描
|
|||
|
|
- 函数回溯:支持污点追踪和数据流分析
|
|||
|
|
- 丰富的规则库:覆盖 OWASP Top 10 等常见漏洞
|
|||
|
|
|
|||
|
|
支持的漏洞类型:
|
|||
|
|
- SQL 注入
|
|||
|
|
- XSS 跨站脚本
|
|||
|
|
- 命令注入
|
|||
|
|
- 代码执行
|
|||
|
|
- 文件包含
|
|||
|
|
- 文件上传
|
|||
|
|
- 反序列化
|
|||
|
|
- SSRF
|
|||
|
|
- XXE
|
|||
|
|
- 等等...
|
|||
|
|
|
|||
|
|
使用场景:
|
|||
|
|
- PHP 代码深度安全审计
|
|||
|
|
- JavaScript 代码安全扫描
|
|||
|
|
- 智能合约安全检查
|
|||
|
|
- Chrome 扩展安全审计
|
|||
|
|
|
|||
|
|
原始项目: https://github.com/LoRexxar/Kunlun-M
|
|||
|
|
License: MIT
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
SUPPORTED_LANGUAGES = ["php", "javascript", "solidity", "chromeext"]
|
|||
|
|
|
|||
|
|
def __init__(self, project_root: str):
|
|||
|
|
super().__init__()
|
|||
|
|
self.project_root = project_root
|
|||
|
|
self.kunlun_path = KUNLUN_M_PATH
|
|||
|
|
self._initialized = False
|
|||
|
|
self._db_initialized = False
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def name(self) -> str:
|
|||
|
|
return "kunlun_scan"
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def description(self) -> str:
|
|||
|
|
return """使用 Kunlun-M (昆仑镜) 进行静态代码安全审计。
|
|||
|
|
Kunlun-M 是一款专注于代码安全审计的工具,特别擅长 PHP 和 JavaScript 的语义分析。
|
|||
|
|
|
|||
|
|
支持的语言:
|
|||
|
|
- php: PHP 语义分析(最完善)
|
|||
|
|
- javascript: JavaScript 语义分析
|
|||
|
|
- solidity: 智能合约基础扫描
|
|||
|
|
- chromeext: Chrome 扩展安全检查
|
|||
|
|
|
|||
|
|
主要功能:
|
|||
|
|
- 深度 AST 语义分析
|
|||
|
|
- 污点追踪和函数回溯
|
|||
|
|
- 自定义规则和 tamper 支持
|
|||
|
|
- 支持识别常见安全漏洞
|
|||
|
|
|
|||
|
|
使用场景:
|
|||
|
|
- 对 PHP/JS 代码进行深度安全审计
|
|||
|
|
- 检测 SQL 注入、XSS、命令注入等漏洞
|
|||
|
|
- 分析代码中的危险函数调用
|
|||
|
|
- 追踪用户输入的传播路径"""
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def args_schema(self):
|
|||
|
|
return KunlunScanInput
|
|||
|
|
|
|||
|
|
async def _ensure_initialized(self) -> bool:
|
|||
|
|
"""确保 Kunlun-M 已初始化"""
|
|||
|
|
if self._initialized:
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# 检查 Kunlun-M 是否存在
|
|||
|
|
if not os.path.exists(self.kunlun_path):
|
|||
|
|
logger.error(f"Kunlun-M not found at {self.kunlun_path}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
kunlun_py = os.path.join(self.kunlun_path, "kunlun.py")
|
|||
|
|
if not os.path.exists(kunlun_py):
|
|||
|
|
logger.error(f"kunlun.py not found at {kunlun_py}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 检查数据库是否已初始化
|
|||
|
|
db_path = os.path.join(self.kunlun_path, "db.sqlite3")
|
|||
|
|
if not os.path.exists(db_path):
|
|||
|
|
logger.info("Kunlun-M database not found, initializing...")
|
|||
|
|
try:
|
|||
|
|
await self._initialize_database()
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"Failed to initialize Kunlun-M database: {e}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
self._initialized = True
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
async def _initialize_database(self):
|
|||
|
|
"""初始化 Kunlun-M 数据库"""
|
|||
|
|
# 复制 settings.py
|
|||
|
|
settings_bak = os.path.join(self.kunlun_path, "Kunlun_M", "settings.py.bak")
|
|||
|
|
settings_py = os.path.join(self.kunlun_path, "Kunlun_M", "settings.py")
|
|||
|
|
|
|||
|
|
if os.path.exists(settings_bak) and not os.path.exists(settings_py):
|
|||
|
|
import shutil
|
|||
|
|
shutil.copy(settings_bak, settings_py)
|
|||
|
|
|
|||
|
|
# 运行初始化命令
|
|||
|
|
init_cmd = [
|
|||
|
|
sys.executable,
|
|||
|
|
os.path.join(self.kunlun_path, "kunlun.py"),
|
|||
|
|
"init", "initialize"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
process = await asyncio.create_subprocess_exec(
|
|||
|
|
*init_cmd,
|
|||
|
|
cwd=self.kunlun_path,
|
|||
|
|
stdout=asyncio.subprocess.PIPE,
|
|||
|
|
stderr=asyncio.subprocess.PIPE,
|
|||
|
|
env={**os.environ, "DJANGO_SETTINGS_MODULE": "Kunlun_M.settings"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=120)
|
|||
|
|
|
|||
|
|
if process.returncode != 0:
|
|||
|
|
raise Exception(f"Database init failed: {stderr.decode()}")
|
|||
|
|
|
|||
|
|
# 加载规则
|
|||
|
|
load_cmd = [
|
|||
|
|
sys.executable,
|
|||
|
|
os.path.join(self.kunlun_path, "kunlun.py"),
|
|||
|
|
"config", "load"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
process = await asyncio.create_subprocess_exec(
|
|||
|
|
*load_cmd,
|
|||
|
|
cwd=self.kunlun_path,
|
|||
|
|
stdout=asyncio.subprocess.PIPE,
|
|||
|
|
stderr=asyncio.subprocess.PIPE,
|
|||
|
|
env={**os.environ, "DJANGO_SETTINGS_MODULE": "Kunlun_M.settings"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=120)
|
|||
|
|
|
|||
|
|
self._db_initialized = True
|
|||
|
|
logger.info("Kunlun-M database initialized successfully")
|
|||
|
|
|
|||
|
|
async def _execute(
|
|||
|
|
self,
|
|||
|
|
target_path: str = ".",
|
|||
|
|
language: Optional[str] = None,
|
|||
|
|
rules: Optional[str] = None,
|
|||
|
|
tamper: Optional[str] = None,
|
|||
|
|
include_unconfirmed: bool = False,
|
|||
|
|
max_results: int = 50,
|
|||
|
|
**kwargs
|
|||
|
|
) -> ToolResult:
|
|||
|
|
"""执行 Kunlun-M 扫描"""
|
|||
|
|
|
|||
|
|
# 确保初始化
|
|||
|
|
if not await self._ensure_initialized():
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error="Kunlun-M 未正确安装或初始化失败。请确保 Kunlun-M-master 目录存在且依赖已安装。"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 构建完整目标路径
|
|||
|
|
if target_path.startswith("/"):
|
|||
|
|
full_target = target_path
|
|||
|
|
else:
|
|||
|
|
full_target = os.path.join(self.project_root, target_path)
|
|||
|
|
|
|||
|
|
if not os.path.exists(full_target):
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error=f"目标路径不存在: {target_path}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 构建扫描命令
|
|||
|
|
cmd = [
|
|||
|
|
sys.executable,
|
|||
|
|
os.path.join(self.kunlun_path, "kunlun.py"),
|
|||
|
|
"scan",
|
|||
|
|
"-t", full_target,
|
|||
|
|
"-o", "json" # JSON 输出格式
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# 添加语言参数
|
|||
|
|
if language:
|
|||
|
|
if language.lower() not in self.SUPPORTED_LANGUAGES:
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error=f"不支持的语言: {language}。支持: {', '.join(self.SUPPORTED_LANGUAGES)}"
|
|||
|
|
)
|
|||
|
|
cmd.extend(["-l", language.lower()])
|
|||
|
|
|
|||
|
|
# 添加规则参数
|
|||
|
|
if rules:
|
|||
|
|
cmd.extend(["-r", rules])
|
|||
|
|
|
|||
|
|
# 添加 tamper 参数
|
|||
|
|
if tamper:
|
|||
|
|
cmd.extend(["-tp", tamper])
|
|||
|
|
|
|||
|
|
# 包含未确认漏洞
|
|||
|
|
if include_unconfirmed:
|
|||
|
|
cmd.append("-uc")
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
# 创建临时输出文件
|
|||
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|||
|
|
output_file = f.name
|
|||
|
|
|
|||
|
|
# 修改命令使用输出文件
|
|||
|
|
cmd.extend(["-o", output_file])
|
|||
|
|
|
|||
|
|
logger.debug(f"Running Kunlun-M: {' '.join(cmd)}")
|
|||
|
|
|
|||
|
|
# 执行扫描
|
|||
|
|
process = await asyncio.create_subprocess_exec(
|
|||
|
|
*cmd,
|
|||
|
|
cwd=self.kunlun_path,
|
|||
|
|
stdout=asyncio.subprocess.PIPE,
|
|||
|
|
stderr=asyncio.subprocess.PIPE,
|
|||
|
|
env={**os.environ, "DJANGO_SETTINGS_MODULE": "Kunlun_M.settings"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
stdout, stderr = await asyncio.wait_for(
|
|||
|
|
process.communicate(),
|
|||
|
|
timeout=600 # 10 分钟超时
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
stdout_text = stdout.decode('utf-8', errors='ignore')
|
|||
|
|
stderr_text = stderr.decode('utf-8', errors='ignore')
|
|||
|
|
|
|||
|
|
# 解析结果
|
|||
|
|
findings = await self._parse_results(stdout_text, stderr_text, output_file)
|
|||
|
|
|
|||
|
|
# 清理临时文件
|
|||
|
|
try:
|
|||
|
|
os.unlink(output_file)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
if not findings:
|
|||
|
|
return ToolResult(
|
|||
|
|
success=True,
|
|||
|
|
data="🛡️ Kunlun-M 扫描完成,未发现安全问题",
|
|||
|
|
metadata={
|
|||
|
|
"findings_count": 0,
|
|||
|
|
"target": target_path,
|
|||
|
|
"language": language
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 格式化输出
|
|||
|
|
output = self._format_findings(findings[:max_results], target_path)
|
|||
|
|
|
|||
|
|
return ToolResult(
|
|||
|
|
success=True,
|
|||
|
|
data=output,
|
|||
|
|
metadata={
|
|||
|
|
"findings_count": len(findings),
|
|||
|
|
"target": target_path,
|
|||
|
|
"language": language,
|
|||
|
|
"findings": findings[:10] # 只在 metadata 中保存前10个
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
except asyncio.TimeoutError:
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error="Kunlun-M 扫描超时(10分钟)"
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"Kunlun-M scan error: {e}", exc_info=True)
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error=f"扫描执行失败: {str(e)}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def _parse_results(
|
|||
|
|
self,
|
|||
|
|
stdout: str,
|
|||
|
|
stderr: str,
|
|||
|
|
output_file: str
|
|||
|
|
) -> List[Dict[str, Any]]:
|
|||
|
|
"""解析 Kunlun-M 扫描结果"""
|
|||
|
|
findings = []
|
|||
|
|
|
|||
|
|
# 尝试从输出文件读取 JSON
|
|||
|
|
try:
|
|||
|
|
if os.path.exists(output_file):
|
|||
|
|
with open(output_file, 'r', encoding='utf-8') as f:
|
|||
|
|
data = json.load(f)
|
|||
|
|
if isinstance(data, list):
|
|||
|
|
findings.extend(data)
|
|||
|
|
elif isinstance(data, dict) and 'vulnerabilities' in data:
|
|||
|
|
findings.extend(data['vulnerabilities'])
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.debug(f"Failed to parse output file: {e}")
|
|||
|
|
|
|||
|
|
# 如果没有 JSON 输出,尝试从 stdout 解析
|
|||
|
|
if not findings and stdout:
|
|||
|
|
# 尝试提取 JSON 部分
|
|||
|
|
try:
|
|||
|
|
json_start = stdout.find('[')
|
|||
|
|
json_end = stdout.rfind(']') + 1
|
|||
|
|
if json_start >= 0 and json_end > json_start:
|
|||
|
|
json_str = stdout[json_start:json_end]
|
|||
|
|
findings = json.loads(json_str)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 尝试解析表格格式输出
|
|||
|
|
if not findings:
|
|||
|
|
findings = self._parse_table_output(stdout)
|
|||
|
|
|
|||
|
|
return findings
|
|||
|
|
|
|||
|
|
def _parse_table_output(self, output: str) -> List[Dict[str, Any]]:
|
|||
|
|
"""解析 Kunlun-M 表格格式输出"""
|
|||
|
|
findings = []
|
|||
|
|
lines = output.split('\n')
|
|||
|
|
|
|||
|
|
for line in lines:
|
|||
|
|
# 匹配漏洞行格式: | index | CVI-xxxx | rule_name | language | file:line | ...
|
|||
|
|
if '|' in line and 'CVI' in line:
|
|||
|
|
parts = [p.strip() for p in line.split('|') if p.strip()]
|
|||
|
|
if len(parts) >= 6:
|
|||
|
|
try:
|
|||
|
|
finding = {
|
|||
|
|
"id": parts[1], # CVI-xxxx
|
|||
|
|
"rule_name": parts[2],
|
|||
|
|
"language": parts[3],
|
|||
|
|
"location": parts[4],
|
|||
|
|
"author": parts[5] if len(parts) > 5 else "",
|
|||
|
|
"code": parts[6] if len(parts) > 6 else "",
|
|||
|
|
"analysis": parts[7] if len(parts) > 7 else "",
|
|||
|
|
}
|
|||
|
|
findings.append(finding)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
return findings
|
|||
|
|
|
|||
|
|
def _format_findings(self, findings: List[Dict[str, Any]], target: str) -> str:
|
|||
|
|
"""格式化漏洞发现"""
|
|||
|
|
output_parts = [
|
|||
|
|
f"🔍 Kunlun-M 扫描结果",
|
|||
|
|
f"目标: {target}",
|
|||
|
|
f"发现 {len(findings)} 个潜在安全问题:\n"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
severity_icons = {
|
|||
|
|
"CRITICAL": "🔴",
|
|||
|
|
"HIGH": "🟠",
|
|||
|
|
"MEDIUM": "🟡",
|
|||
|
|
"LOW": "🟢",
|
|||
|
|
"INFO": "⚪"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for i, finding in enumerate(findings, 1):
|
|||
|
|
# 获取严重程度
|
|||
|
|
severity = finding.get("severity", "MEDIUM")
|
|||
|
|
if isinstance(severity, int):
|
|||
|
|
if severity >= 9:
|
|||
|
|
severity = "CRITICAL"
|
|||
|
|
elif severity >= 6:
|
|||
|
|
severity = "HIGH"
|
|||
|
|
elif severity >= 3:
|
|||
|
|
severity = "MEDIUM"
|
|||
|
|
else:
|
|||
|
|
severity = "LOW"
|
|||
|
|
|
|||
|
|
icon = severity_icons.get(severity.upper(), "⚪")
|
|||
|
|
|
|||
|
|
output_parts.append(f"\n{icon} [{i}] {finding.get('rule_name', 'Unknown')}")
|
|||
|
|
output_parts.append(f" ID: {finding.get('id', 'N/A')}")
|
|||
|
|
output_parts.append(f" 语言: {finding.get('language', 'N/A')}")
|
|||
|
|
|
|||
|
|
location = finding.get('location') or finding.get('file_path', '')
|
|||
|
|
line_number = finding.get('line_number', '')
|
|||
|
|
if location:
|
|||
|
|
if line_number:
|
|||
|
|
output_parts.append(f" 位置: {location}:{line_number}")
|
|||
|
|
else:
|
|||
|
|
output_parts.append(f" 位置: {location}")
|
|||
|
|
|
|||
|
|
code = finding.get('code') or finding.get('code_content', '')
|
|||
|
|
if code:
|
|||
|
|
code_preview = code[:100].strip().replace('\n', ' ')
|
|||
|
|
output_parts.append(f" 代码: {code_preview}")
|
|||
|
|
|
|||
|
|
analysis = finding.get('analysis', '')
|
|||
|
|
if analysis:
|
|||
|
|
output_parts.append(f" 分析: {analysis}")
|
|||
|
|
|
|||
|
|
return "\n".join(output_parts)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class KunlunRuleListTool(AgentTool):
|
|||
|
|
"""
|
|||
|
|
查看 Kunlun-M 可用的扫描规则
|
|||
|
|
|
|||
|
|
可以按语言过滤规则,了解支持检测的漏洞类型。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, project_root: str):
|
|||
|
|
super().__init__()
|
|||
|
|
self.project_root = project_root
|
|||
|
|
self.kunlun_path = KUNLUN_M_PATH
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def name(self) -> str:
|
|||
|
|
return "kunlun_list_rules"
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def description(self) -> str:
|
|||
|
|
return """查看 Kunlun-M 可用的扫描规则。
|
|||
|
|
|
|||
|
|
可以按语言过滤:
|
|||
|
|
- php: PHP 规则
|
|||
|
|
- javascript: JavaScript 规则
|
|||
|
|
- solidity: 智能合约规则
|
|||
|
|
- chromeext: Chrome 扩展规则
|
|||
|
|
|
|||
|
|
返回规则ID、名称、描述等信息,帮助选择合适的规则进行扫描。"""
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def args_schema(self):
|
|||
|
|
return KunlunRuleListInput
|
|||
|
|
|
|||
|
|
async def _execute(
|
|||
|
|
self,
|
|||
|
|
language: Optional[str] = None,
|
|||
|
|
**kwargs
|
|||
|
|
) -> ToolResult:
|
|||
|
|
"""列出可用规则"""
|
|||
|
|
|
|||
|
|
if not os.path.exists(self.kunlun_path):
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error="Kunlun-M 未安装"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 构建命令
|
|||
|
|
cmd = [
|
|||
|
|
sys.executable,
|
|||
|
|
os.path.join(self.kunlun_path, "kunlun.py"),
|
|||
|
|
"show", "rule"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
if language:
|
|||
|
|
cmd.extend(["-k", language.lower()])
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
process = await asyncio.create_subprocess_exec(
|
|||
|
|
*cmd,
|
|||
|
|
cwd=self.kunlun_path,
|
|||
|
|
stdout=asyncio.subprocess.PIPE,
|
|||
|
|
stderr=asyncio.subprocess.PIPE,
|
|||
|
|
env={**os.environ, "DJANGO_SETTINGS_MODULE": "Kunlun_M.settings"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
stdout, stderr = await asyncio.wait_for(
|
|||
|
|
process.communicate(),
|
|||
|
|
timeout=60
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
output = stdout.decode('utf-8', errors='ignore')
|
|||
|
|
|
|||
|
|
if not output.strip():
|
|||
|
|
return ToolResult(
|
|||
|
|
success=True,
|
|||
|
|
data="未找到匹配的规则" if language else "规则列表为空,请先运行初始化",
|
|||
|
|
metadata={"language": language}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return ToolResult(
|
|||
|
|
success=True,
|
|||
|
|
data=f"📋 Kunlun-M 规则列表{f' ({language})' if language else ''}:\n\n{output}",
|
|||
|
|
metadata={"language": language}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
except asyncio.TimeoutError:
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error="获取规则列表超时"
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error=f"获取规则列表失败: {str(e)}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class KunlunPluginInput(BaseModel):
|
|||
|
|
"""Kunlun-M 插件输入"""
|
|||
|
|
plugin_name: str = Field(
|
|||
|
|
description="插件名称: php_unserialize_chain_tools (PHP反序列化链分析), entrance_finder (入口点发现)"
|
|||
|
|
)
|
|||
|
|
target_path: str = Field(
|
|||
|
|
description="要分析的目标路径(相对于项目根目录)"
|
|||
|
|
)
|
|||
|
|
depth: int = Field(
|
|||
|
|
default=3,
|
|||
|
|
description="分析深度(仅对 entrance_finder 有效)"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class KunlunPluginTool(AgentTool):
|
|||
|
|
"""
|
|||
|
|
Kunlun-M 插件工具
|
|||
|
|
|
|||
|
|
提供额外的分析功能:
|
|||
|
|
- php_unserialize_chain_tools: 自动化寻找 PHP 反序列化链
|
|||
|
|
- entrance_finder: 发现 PHP 代码中的入口点/路由
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
AVAILABLE_PLUGINS = {
|
|||
|
|
"php_unserialize_chain_tools": "PHP 反序列化链分析工具,用于发现潜在的反序列化攻击链",
|
|||
|
|
"entrance_finder": "入口点发现工具,帮助找到 PHP 代码中的入口页面和路由",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def __init__(self, project_root: str):
|
|||
|
|
super().__init__()
|
|||
|
|
self.project_root = project_root
|
|||
|
|
self.kunlun_path = KUNLUN_M_PATH
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def name(self) -> str:
|
|||
|
|
return "kunlun_plugin"
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def description(self) -> str:
|
|||
|
|
return """运行 Kunlun-M 插件进行专项分析。
|
|||
|
|
|
|||
|
|
可用插件:
|
|||
|
|
- php_unserialize_chain_tools: 自动分析 PHP 反序列化链,寻找 POP 链
|
|||
|
|
- entrance_finder: 发现 PHP 入口点和路由
|
|||
|
|
|
|||
|
|
使用场景:
|
|||
|
|
- 分析 PHP 框架的反序列化漏洞利用链
|
|||
|
|
- 快速定位大型 PHP 项目的入口文件"""
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def args_schema(self):
|
|||
|
|
return KunlunPluginInput
|
|||
|
|
|
|||
|
|
async def _execute(
|
|||
|
|
self,
|
|||
|
|
plugin_name: str,
|
|||
|
|
target_path: str = ".",
|
|||
|
|
depth: int = 3,
|
|||
|
|
**kwargs
|
|||
|
|
) -> ToolResult:
|
|||
|
|
"""执行插件"""
|
|||
|
|
|
|||
|
|
if plugin_name not in self.AVAILABLE_PLUGINS:
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error=f"未知插件: {plugin_name}。可用插件: {', '.join(self.AVAILABLE_PLUGINS.keys())}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if not os.path.exists(self.kunlun_path):
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error="Kunlun-M 未安装"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 构建完整目标路径
|
|||
|
|
if target_path.startswith("/"):
|
|||
|
|
full_target = target_path
|
|||
|
|
else:
|
|||
|
|
full_target = os.path.join(self.project_root, target_path)
|
|||
|
|
|
|||
|
|
if not os.path.exists(full_target):
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error=f"目标路径不存在: {target_path}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 构建命令
|
|||
|
|
cmd = [
|
|||
|
|
sys.executable,
|
|||
|
|
os.path.join(self.kunlun_path, "kunlun.py"),
|
|||
|
|
"plugin", plugin_name,
|
|||
|
|
"-t", full_target
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
if plugin_name == "entrance_finder":
|
|||
|
|
cmd.extend(["-l", str(depth)])
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
process = await asyncio.create_subprocess_exec(
|
|||
|
|
*cmd,
|
|||
|
|
cwd=self.kunlun_path,
|
|||
|
|
stdout=asyncio.subprocess.PIPE,
|
|||
|
|
stderr=asyncio.subprocess.PIPE,
|
|||
|
|
env={**os.environ, "DJANGO_SETTINGS_MODULE": "Kunlun_M.settings"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
stdout, stderr = await asyncio.wait_for(
|
|||
|
|
process.communicate(),
|
|||
|
|
timeout=300 # 5 分钟超时
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
output = stdout.decode('utf-8', errors='ignore')
|
|||
|
|
|
|||
|
|
if not output.strip():
|
|||
|
|
return ToolResult(
|
|||
|
|
success=True,
|
|||
|
|
data=f"插件 {plugin_name} 执行完成,未发现结果",
|
|||
|
|
metadata={"plugin": plugin_name, "target": target_path}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return ToolResult(
|
|||
|
|
success=True,
|
|||
|
|
data=f"🔌 Kunlun-M 插件 [{plugin_name}] 分析结果:\n\n{output}",
|
|||
|
|
metadata={"plugin": plugin_name, "target": target_path}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
except asyncio.TimeoutError:
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error=f"插件 {plugin_name} 执行超时"
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
return ToolResult(
|
|||
|
|
success=False,
|
|||
|
|
error=f"插件执行失败: {str(e)}"
|
|||
|
|
)
|