mirror of
https://github.com/yyhuni/xingrin.git
synced 2026-02-01 12:13:12 +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
|