mirror of
https://github.com/yyhuni/xingrin.git
synced 2026-02-02 12:43:17 +08:00
196 lines
7.0 KiB
Python
196 lines
7.0 KiB
Python
|
|
"""
|
|||
|
|
配置解析器
|
|||
|
|
|
|||
|
|
负责解析引擎配置(YAML)并提取启用的工具及其配置。
|
|||
|
|
|
|||
|
|
架构说明:
|
|||
|
|
- 命令模板:在 command_templates.py 中定义(基础命令 + 可选参数映射)
|
|||
|
|
- 工具配置:从引擎配置(engine_config YAML 字符串)读取
|
|||
|
|
- 无默认配置文件:所有配置必须在引擎配置中提供
|
|||
|
|
|
|||
|
|
核心函数:
|
|||
|
|
- parse_enabled_tools_from_dict(): 解析并过滤启用的工具,返回工具配置字典
|
|||
|
|
|
|||
|
|
返回格式:
|
|||
|
|
- {'subfinder': {'enabled': True, 'threads': 10, 'timeout': 600}}
|
|||
|
|
- timeout 是必需参数,支持整数或 'auto'(由具体 Flow 处理)
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
from typing import Dict, Any
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _normalize_config_keys(config: Dict[str, Any]) -> Dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
将配置字典的 key 中划线转换为下划线
|
|||
|
|
|
|||
|
|
规范约定:
|
|||
|
|
- 配置文件统一用中划线(贴近 CLI 参数风格)
|
|||
|
|
- 代码里统一用下划线(Python 标识符规范)
|
|||
|
|
- 此处自动转换:rate-limit → rate_limit
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
config: 原始配置字典
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
key 已转换的新字典
|
|||
|
|
"""
|
|||
|
|
return {
|
|||
|
|
k.replace('-', '_') if isinstance(k, str) else k: v
|
|||
|
|
for k, v in config.items()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _parse_subdomain_discovery_config(scan_config: Dict[str, Any]) -> Dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
解析子域名发现配置(4阶段流程)
|
|||
|
|
|
|||
|
|
配置格式:
|
|||
|
|
{
|
|||
|
|
'passive_tools': {'subfinder': {...}, ...},
|
|||
|
|
'bruteforce': {'enabled': True, 'subdomain_bruteforce': {...}},
|
|||
|
|
'permutation': {'enabled': True, 'subdomain_permutation_resolve': {...}},
|
|||
|
|
'resolve': {'enabled': True, 'subdomain_resolve': {...}}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
scan_config: subdomain_discovery 的配置字典
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
配置字典,供 Flow 使用
|
|||
|
|
"""
|
|||
|
|
if 'passive_tools' not in scan_config:
|
|||
|
|
logger.warning("子域名发现配置缺少 passive_tools")
|
|||
|
|
return {}
|
|||
|
|
|
|||
|
|
result = {}
|
|||
|
|
|
|||
|
|
# Stage 1: 被动收集工具
|
|||
|
|
passive_tools = scan_config.get('passive_tools', {})
|
|||
|
|
enabled_passive = {}
|
|||
|
|
for name, config in passive_tools.items():
|
|||
|
|
if isinstance(config, dict) and config.get('enabled', False):
|
|||
|
|
enabled_passive[name] = _normalize_config_keys(config)
|
|||
|
|
result['passive_tools'] = enabled_passive
|
|||
|
|
|
|||
|
|
# Stage 2: 字典爆破(可选)
|
|||
|
|
bruteforce = scan_config.get('bruteforce', {})
|
|||
|
|
if bruteforce.get('enabled', False):
|
|||
|
|
# 转换内部工具配置的 key
|
|||
|
|
normalized_bruteforce = _normalize_config_keys(bruteforce)
|
|||
|
|
if 'subdomain_bruteforce' in normalized_bruteforce:
|
|||
|
|
normalized_bruteforce['subdomain_bruteforce'] = _normalize_config_keys(
|
|||
|
|
normalized_bruteforce['subdomain_bruteforce']
|
|||
|
|
)
|
|||
|
|
result['bruteforce'] = normalized_bruteforce
|
|||
|
|
|
|||
|
|
# Stage 3: 变异生成(可选)
|
|||
|
|
permutation = scan_config.get('permutation', {})
|
|||
|
|
if permutation.get('enabled', False):
|
|||
|
|
normalized_permutation = _normalize_config_keys(permutation)
|
|||
|
|
if 'subdomain_permutation_resolve' in normalized_permutation:
|
|||
|
|
normalized_permutation['subdomain_permutation_resolve'] = _normalize_config_keys(
|
|||
|
|
normalized_permutation['subdomain_permutation_resolve']
|
|||
|
|
)
|
|||
|
|
result['permutation'] = normalized_permutation
|
|||
|
|
|
|||
|
|
# Stage 4: 存活验证(可选)
|
|||
|
|
resolve = scan_config.get('resolve', {})
|
|||
|
|
if resolve.get('enabled', False):
|
|||
|
|
normalized_resolve = _normalize_config_keys(resolve)
|
|||
|
|
if 'subdomain_resolve' in normalized_resolve:
|
|||
|
|
normalized_resolve['subdomain_resolve'] = _normalize_config_keys(
|
|||
|
|
normalized_resolve['subdomain_resolve']
|
|||
|
|
)
|
|||
|
|
result['resolve'] = normalized_resolve
|
|||
|
|
|
|||
|
|
logger.info(
|
|||
|
|
f"子域名发现: passive={len(enabled_passive)}, "
|
|||
|
|
f"bruteforce={'bruteforce' in result}, "
|
|||
|
|
f"permutation={'permutation' in result}, "
|
|||
|
|
f"resolve={'resolve' in result}"
|
|||
|
|
)
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
|
|||
|
|
def parse_enabled_tools_from_dict(
|
|||
|
|
scan_type: str,
|
|||
|
|
parsed_config: Dict[str, Any]
|
|||
|
|
) -> Dict[str, Dict[str, Any]]:
|
|||
|
|
"""
|
|||
|
|
从解析后的配置字典中获取启用的工具及其配置
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
scan_type: 扫描类型 (subdomain_discovery, port_scan, site_scan, directory_scan)
|
|||
|
|
parsed_config: 已解析的配置字典
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
启用的工具配置字典 {tool_name: tool_config}
|
|||
|
|
对于 subdomain_discovery,返回完整的配置结构(支持4阶段增强流程)
|
|||
|
|
|
|||
|
|
Raises:
|
|||
|
|
ValueError: 配置格式错误或必需参数缺失/无效时抛出
|
|||
|
|
"""
|
|||
|
|
if not parsed_config:
|
|||
|
|
logger.warning(f"配置字典为空 - scan_type: {scan_type}")
|
|||
|
|
return {}
|
|||
|
|
|
|||
|
|
if scan_type not in parsed_config:
|
|||
|
|
logger.warning(f"配置中未找到扫描类型: {scan_type}")
|
|||
|
|
return {}
|
|||
|
|
|
|||
|
|
scan_config = parsed_config[scan_type]
|
|||
|
|
|
|||
|
|
# 子域名发现支持增强配置格式(4阶段)
|
|||
|
|
if scan_type == 'subdomain_discovery':
|
|||
|
|
return _parse_subdomain_discovery_config(scan_config)
|
|||
|
|
|
|||
|
|
if 'tools' not in scan_config:
|
|||
|
|
logger.warning(f"扫描类型 {scan_type} 未配置任何工具")
|
|||
|
|
return {}
|
|||
|
|
|
|||
|
|
tools = scan_config['tools']
|
|||
|
|
|
|||
|
|
# 过滤出启用的工具
|
|||
|
|
enabled_tools = {}
|
|||
|
|
for name, config in tools.items():
|
|||
|
|
if not isinstance(config, dict):
|
|||
|
|
raise ValueError(f"工具 {name} 配置格式错误:期望 dict,实际 {type(config).__name__}")
|
|||
|
|
|
|||
|
|
# 检查是否启用(默认为 False)
|
|||
|
|
enabled_value = config.get('enabled', False)
|
|||
|
|
|
|||
|
|
# 验证 enabled 字段类型
|
|||
|
|
if not isinstance(enabled_value, bool):
|
|||
|
|
raise ValueError(
|
|||
|
|
f"工具 {name} 的 enabled 字段类型错误:期望 bool,实际 {type(enabled_value).__name__}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if enabled_value:
|
|||
|
|
# 检查 timeout 必需参数
|
|||
|
|
if 'timeout' not in config:
|
|||
|
|
raise ValueError(f"工具 {name} 缺少必需参数 'timeout'")
|
|||
|
|
|
|||
|
|
# 验证 timeout 值的有效性
|
|||
|
|
timeout_value = config['timeout']
|
|||
|
|
|
|||
|
|
if timeout_value == 'auto':
|
|||
|
|
# 允许 'auto',由具体 Flow 处理
|
|||
|
|
pass
|
|||
|
|
elif isinstance(timeout_value, int):
|
|||
|
|
if timeout_value <= 0:
|
|||
|
|
raise ValueError(f"工具 {name} 的 timeout 参数无效({timeout_value}),必须大于0")
|
|||
|
|
else:
|
|||
|
|
raise ValueError(
|
|||
|
|
f"工具 {name} 的 timeout 参数类型错误:期望 int 或 'auto',实际 {type(timeout_value).__name__}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 将配置 key 中划线转为下划线,统一给下游代码使用
|
|||
|
|
enabled_tools[name] = _normalize_config_keys(config)
|
|||
|
|
|
|||
|
|
logger.info(f"扫描类型: {scan_type}, 启用工具: {len(enabled_tools)}/{len(tools)}")
|
|||
|
|
|
|||
|
|
return enabled_tools
|