mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-04-21 10:16:32 +02:00
Add files via upload
This commit is contained in:
@@ -334,7 +334,7 @@ func (b *Builder) buildSimplePrompt(reactInput, modelOutput string) string {
|
||||
|
||||
## 核心目标
|
||||
|
||||
构建一个能够讲述完整攻击故事的攻击链(通常8-15个节点,必要时可适当增加),让学习者能够:
|
||||
构建一个能够讲述完整攻击故事的攻击链让学习者能够:
|
||||
1. 理解渗透测试的完整流程和思维逻辑(从目标识别到漏洞发现的每一步)
|
||||
2. 学习如何从失败中获取线索并调整策略
|
||||
3. 掌握工具使用的实际效果和局限性
|
||||
@@ -358,11 +358,15 @@ func (b *Builder) buildSimplePrompt(reactInput, modelOutput string) string {
|
||||
- **vulnerability节点**:每个真实确认的漏洞创建一个vulnerability节点
|
||||
- **完整性检查**:对照ReAct输入中的工具调用序列,确保每个有意义的工具执行都被包含在攻击链中
|
||||
|
||||
### 第三步:构建逻辑关系
|
||||
按照时间顺序和因果关系连接节点:
|
||||
### 第三步:构建逻辑关系(树状结构)
|
||||
**重要:必须构建树状结构,而不是简单的线性链。**
|
||||
按照因果关系连接节点,形成树状图(因为是单agent执行,所以可以不按照时间顺序):
|
||||
- **分支结构**:一个节点可以有多个后续节点(例如:端口扫描发现多个端口后,可以同时进行多个不同的测试)
|
||||
- **汇聚结构**:多个节点可以指向同一个节点(例如:多个不同的测试都发现了同一个漏洞)
|
||||
- 识别哪些action是基于前面action的结果而执行的
|
||||
- 识别哪些vulnerability是由哪些action发现的
|
||||
- 识别失败节点如何为后续成功提供线索
|
||||
- **避免线性链**:不要将所有节点连成一条线,应该根据实际的并行测试和分支探索构建树状结构
|
||||
|
||||
### 第四步:优化和精简
|
||||
- **完整性检查**:确保所有有意义的工具执行都被包含,不要遗漏关键步骤
|
||||
@@ -449,10 +453,12 @@ func (b *Builder) buildSimplePrompt(reactInput, modelOutput string) string {
|
||||
### 边的类型
|
||||
- **leads_to**:表示"导致"或"引导到",用于action→action、target→action
|
||||
* 例如:端口扫描 → 目录扫描(因为发现了80端口,所以进行目录扫描)
|
||||
- **discovers**:表示"发现",用于action→vulnerability
|
||||
- **discovers**:表示"发现",**专门用于action→vulnerability**
|
||||
* 例如:SQL注入测试 → SQL注入漏洞
|
||||
- **enables**:表示"使能"或"促成",用于vulnerability→vulnerability、action→action(当后续行动依赖前面结果时)
|
||||
* **重要**:所有action→vulnerability的边都必须使用discovers类型,即使多个action都指向同一个vulnerability,也应该统一使用discovers
|
||||
- **enables**:表示"使能"或"促成",**仅用于vulnerability→vulnerability、action→action(当后续行动依赖前面结果时)**
|
||||
* 例如:信息泄露漏洞 → 权限提升漏洞(通过信息泄露获得的信息促成了权限提升)
|
||||
* **重要**:enables不能用于action→vulnerability,action→vulnerability必须使用discovers
|
||||
|
||||
### 边的权重
|
||||
- **权重1-2**:弱关联(如初步探测到进一步探测)
|
||||
@@ -460,10 +466,14 @@ func (b *Builder) buildSimplePrompt(reactInput, modelOutput string) string {
|
||||
- **权重5-7**:强关联(如发现漏洞、关键信息泄露)
|
||||
- **权重8-10**:极强关联(如漏洞利用成功、权限提升)
|
||||
|
||||
### DAG结构要求
|
||||
### DAG结构要求(树状图)
|
||||
- 所有边的source节点id必须小于target节点id(确保无环)
|
||||
- 节点id从"node_1"开始递增
|
||||
- 确保无孤立节点(每个节点至少有一条边连接)
|
||||
- **树状结构要求**:
|
||||
* 一个节点可以有多个后续节点(分支),例如:端口扫描节点可以同时连接到"Web服务识别"、"FTP服务识别"、"SSH服务识别"等多个节点
|
||||
* 多个节点可以汇聚到一个节点(汇聚),例如:多个不同的测试都指向同一个漏洞节点
|
||||
* 避免将所有节点连成一条线,应该根据实际的并行测试和分支探索构建树状结构
|
||||
|
||||
## 攻击链逻辑连贯性要求
|
||||
|
||||
@@ -487,6 +497,8 @@ func (b *Builder) buildSimplePrompt(reactInput, modelOutput string) string {
|
||||
|
||||
严格按照以下JSON格式输出,不要添加任何其他文字:
|
||||
|
||||
**重要:示例展示的是树状结构,注意node_2(端口扫描)同时连接到多个后续节点(node_3、node_4),形成分支结构。**
|
||||
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
@@ -513,30 +525,42 @@ func (b *Builder) buildSimplePrompt(reactInput, modelOutput string) string {
|
||||
{
|
||||
"id": "node_3",
|
||||
"type": "action",
|
||||
"label": "尝试SQL注入(被WAF拦截)",
|
||||
"risk_score": 0,
|
||||
"metadata": {
|
||||
"tool_name": "sqlmap",
|
||||
"tool_intent": "SQL注入检测",
|
||||
"ai_analysis": "对/login.php进行SQL注入测试时被WAF拦截,返回403错误。错误信息显示检测到Cloudflare防护。这表明目标部署了WAF,需要调整测试策略,可尝试绕过技术或寻找其他未受保护的攻击面。",
|
||||
"findings": ["WAF拦截", "返回403", "检测到Cloudflare", "目标部署WAF"],
|
||||
"status": "failed_insight"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node_4",
|
||||
"type": "action",
|
||||
"label": "目录扫描发现/admin后台",
|
||||
"risk_score": 0,
|
||||
"metadata": {
|
||||
"tool_name": "dirsearch",
|
||||
"tool_intent": "目录扫描",
|
||||
"ai_analysis": "使用dirsearch对目标进行目录扫描,发现/admin目录存在且可访问。该目录可能为管理后台,是重要的测试目标。结合之前发现的WAF防护,可以尝试对/admin目录进行绕过测试。",
|
||||
"ai_analysis": "使用dirsearch对目标进行目录扫描,发现/admin目录存在且可访问。该目录可能为管理后台,是重要的测试目标。",
|
||||
"findings": ["/admin目录存在", "返回200状态码", "疑似管理后台"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node_4",
|
||||
"type": "action",
|
||||
"label": "识别Web服务为Apache 2.4",
|
||||
"risk_score": 0,
|
||||
"metadata": {
|
||||
"tool_name": "whatweb",
|
||||
"tool_intent": "Web服务识别",
|
||||
"ai_analysis": "识别出目标运行Apache 2.4服务器,这为后续的漏洞测试提供了重要信息。",
|
||||
"findings": ["Apache 2.4", "PHP版本信息"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node_5",
|
||||
"type": "action",
|
||||
"label": "尝试SQL注入(被WAF拦截)",
|
||||
"risk_score": 0,
|
||||
"metadata": {
|
||||
"tool_name": "sqlmap",
|
||||
"tool_intent": "SQL注入检测",
|
||||
"ai_analysis": "对/login.php进行SQL注入测试时被WAF拦截,返回403错误。错误信息显示检测到Cloudflare防护。这表明目标部署了WAF,需要调整测试策略。",
|
||||
"findings": ["WAF拦截", "返回403", "检测到Cloudflare", "目标部署WAF"],
|
||||
"status": "failed_insight"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node_6",
|
||||
"type": "vulnerability",
|
||||
"label": "SQL注入漏洞",
|
||||
"risk_score": 85,
|
||||
@@ -559,17 +583,23 @@ func (b *Builder) buildSimplePrompt(reactInput, modelOutput string) string {
|
||||
"source": "node_2",
|
||||
"target": "node_3",
|
||||
"type": "leads_to",
|
||||
"weight": 4
|
||||
},
|
||||
{
|
||||
"source": "node_2",
|
||||
"target": "node_4",
|
||||
"type": "leads_to",
|
||||
"weight": 3
|
||||
},
|
||||
{
|
||||
"source": "node_3",
|
||||
"target": "node_4",
|
||||
"target": "node_5",
|
||||
"type": "leads_to",
|
||||
"weight": 4
|
||||
},
|
||||
{
|
||||
"source": "node_4",
|
||||
"target": "node_5",
|
||||
"source": "node_5",
|
||||
"target": "node_6",
|
||||
"type": "discovers",
|
||||
"weight": 7
|
||||
}
|
||||
@@ -579,12 +609,13 @@ func (b *Builder) buildSimplePrompt(reactInput, modelOutput string) string {
|
||||
## 重要提醒
|
||||
|
||||
1. **严禁杜撰**:只使用ReAct输入中实际执行的工具和实际返回的结果。如无实际数据,返回空的nodes和edges数组。
|
||||
2. **完整性优先**:必须包含所有有意义的工具执行和关键步骤,不要为了控制节点数量而删除重要节点。攻击链必须能够完整展现从目标识别到漏洞发现的完整过程。
|
||||
3. **逻辑连贯**:确保攻击链能够讲述一个完整、连贯的渗透测试故事,包括所有关键步骤和决策点。
|
||||
4. **教育价值**:优先保留有教育意义的节点,帮助学习者理解渗透测试思维和完整流程。
|
||||
5. **准确性**:所有节点信息必须基于实际数据,不要推测或假设。
|
||||
6. **完整性检查**:确保每个节点都有必要的metadata字段,每条边都有正确的source和target,没有孤立节点。
|
||||
7. **不要过度精简**:如果实际执行步骤较多,可以适当增加节点数量(最多20个),确保不遗漏关键步骤。
|
||||
2. **树状结构优先**:必须构建树状结构,而不是线性链。一个节点可以有多个后续节点(分支),多个节点可以指向同一个节点(汇聚)。避免将所有节点连成一条线。
|
||||
3. **完整性优先**:必须包含所有有意义的工具执行和关键步骤,不要为了控制节点数量而删除重要节点。攻击链必须能够完整展现从目标识别到漏洞发现的完整过程。
|
||||
4. **逻辑连贯**:确保攻击链能够讲述一个完整、连贯的渗透测试故事,包括所有关键步骤和决策点。
|
||||
5. **教育价值**:优先保留有教育意义的节点,帮助学习者理解渗透测试思维和完整流程。
|
||||
6. **准确性**:所有节点信息必须基于实际数据,不要推测或假设。
|
||||
7. **完整性检查**:确保每个节点都有必要的metadata字段,每条边都有正确的source和target,没有孤立节点。
|
||||
8. **不要过度精简**:如果实际执行步骤较多,可以适当增加节点数量(最多20个),确保不遗漏关键步骤。
|
||||
|
||||
现在开始分析并构建攻击链:`, reactInput, modelOutput)
|
||||
}
|
||||
|
||||
+548
-105
@@ -1739,24 +1739,27 @@ header {
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
transition: all 0.25s ease;
|
||||
border: 2px solid transparent;
|
||||
background: transparent;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
background: linear-gradient(135deg, rgba(220, 53, 69, 0.1) 0%, rgba(220, 53, 69, 0.05) 100%);
|
||||
color: #dc3545;
|
||||
border-color: rgba(220, 53, 69, 0.2);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
@@ -3816,9 +3819,10 @@ header {
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 16px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.attack-chain-body {
|
||||
@@ -3827,73 +3831,160 @@ header {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||
background: linear-gradient(135deg, #fafbfc 0%, #f5f7fa 100%);
|
||||
}
|
||||
|
||||
.attack-chain-controls {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
.attack-chain-main-layout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.attack-chain-visualization-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.attack-chain-sidebar {
|
||||
width: 280px;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.attack-chain-sidebar-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
will-change: scroll-position;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.attack-chain-toolbar {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.attack-chain-info {
|
||||
font-size: 0.9375rem;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
padding: 8px 16px;
|
||||
background: linear-gradient(135deg, rgba(0, 102, 255, 0.08) 0%, rgba(0, 102, 255, 0.04) 100%);
|
||||
border: 1px solid rgba(0, 102, 255, 0.15);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
font-weight: 600;
|
||||
padding: 6px 12px;
|
||||
background: linear-gradient(135deg, rgba(0, 102, 255, 0.1) 0%, rgba(0, 102, 255, 0.06) 100%);
|
||||
border: 1px solid rgba(0, 102, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 3px rgba(0, 102, 255, 0.08);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.attack-chain-info::before {
|
||||
content: '📊';
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.attack-chain-legend {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
padding: 12px 20px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 0.875rem;
|
||||
gap: 10px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
padding: 4px 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.legend-item:hover {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 6px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
||||
transition: transform 0.2s ease;
|
||||
border: 1.5px solid rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12), inset 0 1px 2px rgba(255, 255, 255, 0.3);
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.legend-item:hover .legend-color {
|
||||
transform: scale(1.1);
|
||||
transform: scale(1.15);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2), inset 0 1px 2px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.legend-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.legend-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 6px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.legend-line {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 0;
|
||||
margin-right: 0;
|
||||
vertical-align: middle;
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.legend-item:hover .legend-line {
|
||||
transform: scaleX(1.2);
|
||||
}
|
||||
|
||||
.attack-chain-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background: linear-gradient(135deg, #fafbfc 0%, #f5f7fa 100%);
|
||||
background: #ffffff; // 使用纯白色背景,提高节点对比度
|
||||
border: none;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 12px;
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.attack-chain-container::before {
|
||||
@@ -3904,41 +3995,46 @@ header {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background:
|
||||
radial-gradient(circle at 20% 30%, rgba(0, 102, 255, 0.03) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, rgba(0, 102, 255, 0.03) 0%, transparent 50%);
|
||||
radial-gradient(circle at 20% 30%, rgba(0, 102, 255, 0.02) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, rgba(0, 102, 255, 0.02) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* 攻击链筛选器样式 */
|
||||
.attack-chain-filters {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.attack-chain-filters input[type="text"] {
|
||||
padding: 10px 16px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
font-size: 0.875rem;
|
||||
min-width: 220px;
|
||||
background: var(--bg-primary);
|
||||
padding: 6px 12px;
|
||||
border: 1.5px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 6px;
|
||||
font-size: 0.8125rem;
|
||||
min-width: 180px;
|
||||
flex: 1;
|
||||
max-width: 300px;
|
||||
background: #ffffff;
|
||||
color: var(--text-primary);
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.25s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.attack-chain-filters input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
||||
box-shadow: 0 0 0 4px rgba(0, 102, 255, 0.12), 0 2px 6px rgba(0, 102, 255, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.attack-chain-filters input[type="text"]:hover {
|
||||
border-color: rgba(0, 102, 255, 0.3);
|
||||
}
|
||||
|
||||
.attack-chain-filters input[type="text"]::placeholder {
|
||||
@@ -3946,74 +4042,267 @@ header {
|
||||
}
|
||||
|
||||
.attack-chain-filters select {
|
||||
padding: 10px 16px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
font-size: 0.875rem;
|
||||
background: var(--bg-primary);
|
||||
padding: 6px 12px;
|
||||
padding-right: 32px;
|
||||
border: 1.5px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 6px;
|
||||
font-size: 0.8125rem;
|
||||
background: #ffffff;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.25s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 12px center;
|
||||
padding-right: 40px;
|
||||
background-position: right 10px center;
|
||||
font-weight: 500;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.attack-chain-filters select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
||||
box-shadow: 0 0 0 4px rgba(0, 102, 255, 0.12), 0 2px 6px rgba(0, 102, 255, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.attack-chain-filters select:hover {
|
||||
border-color: var(--accent-color);
|
||||
border-color: rgba(0, 102, 255, 0.3);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.attack-chain-filters button.btn-secondary {
|
||||
padding: 10px 18px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
padding: 6px 14px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
border: 1.5px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.25s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.attack-chain-filters button.btn-secondary:hover {
|
||||
background: var(--bg-tertiary);
|
||||
background: linear-gradient(135deg, rgba(0, 102, 255, 0.08) 0%, rgba(0, 102, 255, 0.04) 100%);
|
||||
border-color: var(--accent-color);
|
||||
color: var(--accent-color);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 102, 255, 0.15);
|
||||
}
|
||||
|
||||
.attack-chain-action-btn {
|
||||
padding: 10px 18px !important;
|
||||
font-size: 0.875rem !important;
|
||||
font-weight: 600 !important;
|
||||
border-radius: 10px !important;
|
||||
transition: all 0.25s ease !important;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn:hover {
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 16px 20px !important;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06) !important;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%) !important;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 1.25rem !important;
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -0.3px !important;
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #4a4a4a 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.attack-chain-details {
|
||||
padding: 20px 24px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.04);
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
transition: opacity 0.2s ease;
|
||||
overflow: hidden;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
.attack-chain-details h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
.attack-chain-details[style*="display: none"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
max-height: 1500px;
|
||||
padding-top: 20px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.attack-chain-details-title {
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0 !important;
|
||||
padding-bottom: 8px !important;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.attack-chain-details-title span {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.attack-chain-details-close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.attack-chain-details-close:hover {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
color: var(--text-primary);
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.attack-chain-details-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 4px;
|
||||
will-change: scroll-position;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.attack-chain-details-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.attack-chain-details-content::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.attack-chain-details-content::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.attack-chain-details-content::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.node-detail-item {
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 0;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.6;
|
||||
padding: 10px 12px;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.node-detail-item:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
border-color: rgba(0, 102, 255, 0.2);
|
||||
}
|
||||
|
||||
.node-detail-item strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.node-detail-item code {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8125rem;
|
||||
word-break: break-all;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.node-detail-item pre {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
overflow-x: auto;
|
||||
margin: 8px 0 0 0;
|
||||
line-height: 1.5;
|
||||
max-width: 100%;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.node-detail-item ul {
|
||||
margin: 8px 0 0 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.node-detail-item li {
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.node-detail-item li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.node-detail-item div[style*="background"] {
|
||||
margin-top: 8px;
|
||||
padding: 10px 12px !important;
|
||||
border-radius: 6px;
|
||||
line-height: 1.6;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.node-detail-item ul {
|
||||
margin: 8px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.node-detail-item li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.node-detail-item strong {
|
||||
@@ -4040,30 +4329,50 @@ header {
|
||||
|
||||
.modal-header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn {
|
||||
padding: 10px 18px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border: 2px solid transparent;
|
||||
padding: 8px 16px !important;
|
||||
font-size: 0.8125rem !important;
|
||||
font-weight: 600 !important;
|
||||
border-radius: 8px !important;
|
||||
transition: all 0.25s ease !important;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08) !important;
|
||||
border: 2px solid transparent !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
gap: 6px !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn.btn-primary {
|
||||
background: linear-gradient(135deg, #0066ff 0%, #0052cc 100%);
|
||||
color: white;
|
||||
border-color: #0066ff;
|
||||
background: linear-gradient(135deg, #0066ff 0%, #0052cc 100%) !important;
|
||||
color: white !important;
|
||||
border-color: #0066ff !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #0052cc 0%, #0040a3 100%) !important;
|
||||
box-shadow: 0 6px 20px rgba(0, 102, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn.btn-secondary {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%) !important;
|
||||
color: var(--text-primary) !important;
|
||||
border-color: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn.btn-secondary:hover {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #f1f3f5 100%) !important;
|
||||
border-color: var(--accent-color) !important;
|
||||
color: var(--accent-color) !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn.btn-primary:hover {
|
||||
@@ -5980,3 +6289,137 @@ header {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* ==================== 攻击链可视化响应式样式 ==================== */
|
||||
@media (max-width: 1200px) {
|
||||
.attack-chain-modal-content {
|
||||
width: 98vw;
|
||||
height: 92vh;
|
||||
max-height: 92vh;
|
||||
}
|
||||
|
||||
.attack-chain-sidebar {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.attack-chain-toolbar {
|
||||
padding: 10px 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.attack-chain-filters {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.attack-chain-filters input[type="text"] {
|
||||
min-width: 150px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.attack-chain-container {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.attack-chain-modal-content {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.attack-chain-main-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.attack-chain-sidebar {
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
border-left: none;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.attack-chain-visualization-area {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.attack-chain-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.attack-chain-info {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.attack-chain-filters {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.attack-chain-filters input[type="text"],
|
||||
.attack-chain-filters select,
|
||||
.attack-chain-filters button {
|
||||
width: 100%;
|
||||
min-width: auto;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.attack-chain-legend {
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
padding: 12px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.legend-section {
|
||||
min-width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.attack-chain-details {
|
||||
min-width: 200px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.attack-chain-details-content {
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.attack-chain-container {
|
||||
margin: 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.attack-chain-details {
|
||||
margin: 0 8px 8px 8px;
|
||||
border-radius: 10px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 12px 16px !important;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 1.125rem !important;
|
||||
}
|
||||
|
||||
.modal-header-actions {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding: 7px 14px !important;
|
||||
font-size: 0.8125rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
+132
-98
@@ -2026,13 +2026,13 @@ function renderAttackChain(chainData) {
|
||||
// 根据节点类型调整大小,target节点更大(增加高度以容纳类型标签)
|
||||
'width': function(ele) {
|
||||
const type = ele.data('type');
|
||||
if (type === 'target') return isComplexGraph ? 240 : 260;
|
||||
return isComplexGraph ? 220 : 240;
|
||||
if (type === 'target') return isComplexGraph ? 380 : 420;
|
||||
return isComplexGraph ? 360 : 400;
|
||||
},
|
||||
'height': function(ele) {
|
||||
const type = ele.data('type');
|
||||
if (type === 'target') return isComplexGraph ? 115 : 125;
|
||||
return isComplexGraph ? 110 : 120;
|
||||
if (type === 'target') return isComplexGraph ? 180 : 200;
|
||||
return isComplexGraph ? 170 : 190;
|
||||
},
|
||||
'shape': function(ele) {
|
||||
// 所有节点都使用圆角矩形
|
||||
@@ -2044,7 +2044,7 @@ function renderAttackChain(chainData) {
|
||||
|
||||
// target节点使用更深的蓝色背景,增强对比度
|
||||
if (type === 'target') {
|
||||
return '#bbdefb'; // 更深的浅蓝色
|
||||
return '#e8f4fd'; // 更亮的蓝色背景,提高对比度
|
||||
}
|
||||
|
||||
// action节点根据执行有效性显示不同颜色
|
||||
@@ -2058,30 +2058,30 @@ function renderAttackChain(chainData) {
|
||||
const isFailedInsight = status === 'failed_insight';
|
||||
|
||||
if (hasFindings && !isFailedInsight) {
|
||||
return '#e8f5e9'; // 浅绿色:有效执行
|
||||
return '#f5fbf5'; // 更亮的绿色背景,提高对比度
|
||||
} else {
|
||||
return '#f5f5f5'; // 浅灰色:无效执行
|
||||
return '#fafafa'; // 浅灰色背景,与白色文字形成对比
|
||||
}
|
||||
}
|
||||
|
||||
// vulnerability节点根据风险分数显示不同颜色
|
||||
// vulnerability节点根据风险分数显示不同颜色,使用更亮的背景
|
||||
if (type === 'vulnerability') {
|
||||
if (riskScore >= 80) return '#ffcdd2'; // 更饱和的浅红色
|
||||
if (riskScore >= 60) return '#ffe0b2'; // 更饱和的浅橙色
|
||||
if (riskScore >= 40) return '#fff9c4'; // 更饱和的浅黄色
|
||||
return '#dcedc8'; // 更饱和的浅绿色
|
||||
if (riskScore >= 80) return '#fff0f0'; // 更亮的红色背景
|
||||
if (riskScore >= 60) return '#fff5e6'; // 更亮的橙色背景
|
||||
if (riskScore >= 40) return '#fffef0'; // 更亮的黄色背景
|
||||
return '#f5fbf5'; // 更亮的绿色背景
|
||||
}
|
||||
|
||||
return '#f5f5f5'; // 默认浅灰色
|
||||
return '#ffffff'; // 默认白色背景
|
||||
},
|
||||
// 根据节点类型和风险分数设置文字颜色
|
||||
// 根据节点类型和风险分数设置文字颜色,使用更深的颜色提高对比度
|
||||
// 注意:由于标签包含类型标签和内容,颜色适用于所有文本
|
||||
'color': function(ele) {
|
||||
const type = ele.data('type');
|
||||
const riskScore = ele.data('riskScore') || 0;
|
||||
|
||||
if (type === 'target') {
|
||||
return '#1976d2'; // 深蓝色文字
|
||||
return '#0d47a1'; // 更深的蓝色文字,提高对比度
|
||||
}
|
||||
|
||||
// action节点根据执行有效性显示不同文字颜色
|
||||
@@ -2095,54 +2095,54 @@ function renderAttackChain(chainData) {
|
||||
const isFailedInsight = status === 'failed_insight';
|
||||
|
||||
if (hasFindings && !isFailedInsight) {
|
||||
return '#2e7d32'; // 深绿色:有效执行
|
||||
return '#1b5e20'; // 更深的绿色:有效执行,提高对比度
|
||||
} else {
|
||||
return '#757575'; // 深灰色:无效执行
|
||||
return '#212121'; // 深灰色:无效执行,提高对比度
|
||||
}
|
||||
}
|
||||
|
||||
// vulnerability节点根据风险分数显示不同文字颜色
|
||||
// vulnerability节点根据风险分数显示不同文字颜色,使用更深的颜色
|
||||
if (type === 'vulnerability') {
|
||||
if (riskScore >= 80) return '#c62828'; // 深红色
|
||||
if (riskScore >= 60) return '#e65100'; // 深橙色
|
||||
if (riskScore >= 40) return '#f57f17'; // 深黄色
|
||||
return '#558b2f'; // 深绿色
|
||||
if (riskScore >= 80) return '#b71c1c'; // 更深的红色,提高对比度
|
||||
if (riskScore >= 60) return '#bf360c'; // 更深的橙色,提高对比度
|
||||
if (riskScore >= 40) return '#e65100'; // 更深的黄色,提高对比度
|
||||
return '#33691e'; // 更深的绿色,提高对比度
|
||||
}
|
||||
|
||||
return '#424242'; // 默认深灰色
|
||||
return '#000000'; // 黑色,最高对比度
|
||||
},
|
||||
'font-size': function(ele) {
|
||||
// 由于标签包含类型标签和内容,使用合适的字体大小
|
||||
// 进一步增大字体,提高可读性
|
||||
const type = ele.data('type');
|
||||
if (type === 'target') return isComplexGraph ? '16px' : '18px';
|
||||
return isComplexGraph ? '15px' : '17px';
|
||||
if (type === 'target') return isComplexGraph ? '20px' : '22px';
|
||||
return isComplexGraph ? '19px' : '21px';
|
||||
},
|
||||
'font-weight': '600',
|
||||
'font-weight': 'bold', // 加粗字体,提高可读性
|
||||
'font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||
'text-valign': 'center',
|
||||
'text-halign': 'center',
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': function(ele) {
|
||||
const type = ele.data('type');
|
||||
if (type === 'target') return isComplexGraph ? '220px' : '240px';
|
||||
return isComplexGraph ? '200px' : '220px';
|
||||
if (type === 'target') return isComplexGraph ? '340px' : '380px';
|
||||
return isComplexGraph ? '320px' : '360px';
|
||||
},
|
||||
'text-overflow-wrap': 'anywhere',
|
||||
'text-margin-y': 3, // 调整垂直边距以适应多行文本
|
||||
'padding': '14px', // 增加内边距,使节点内容更有呼吸感和现代感
|
||||
'text-margin-y': 5, // 调整垂直边距以适应多行文本
|
||||
'padding': '18px', // 进一步增加内边距,使节点内容更有呼吸感
|
||||
'line-height': 1.6, // 增加行高,提高可读性
|
||||
// 根据节点类型设置边框样式,使用更粗的边框增强视觉效果
|
||||
'border-width': function(ele) {
|
||||
const type = ele.data('type');
|
||||
if (type === 'target') return 5;
|
||||
return 4;
|
||||
if (type === 'target') return 6;
|
||||
return 5;
|
||||
},
|
||||
'border-radius': '12px', // 所有节点都使用圆角
|
||||
'border-color': function(ele) {
|
||||
const type = ele.data('type');
|
||||
const riskScore = ele.data('riskScore') || 0;
|
||||
|
||||
if (type === 'target') {
|
||||
return '#1976d2'; // 蓝色边框
|
||||
return '#1565c0'; // 更深的蓝色边框,提高对比度
|
||||
}
|
||||
|
||||
// action节点根据执行有效性显示不同边框颜色
|
||||
@@ -2156,38 +2156,26 @@ function renderAttackChain(chainData) {
|
||||
const isFailedInsight = status === 'failed_insight';
|
||||
|
||||
if (hasFindings && !isFailedInsight) {
|
||||
return '#66bb6a'; // 绿色边框:有效执行
|
||||
return '#4caf50'; // 更深的绿色边框:有效执行,提高对比度
|
||||
} else {
|
||||
return '#9e9e9e'; // 灰色边框:无效执行
|
||||
return '#757575'; // 更深的灰色边框:无效执行,提高对比度
|
||||
}
|
||||
}
|
||||
|
||||
// vulnerability节点根据风险分数显示不同边框颜色
|
||||
// vulnerability节点根据风险分数显示不同边框颜色,使用更深的颜色
|
||||
if (type === 'vulnerability') {
|
||||
if (riskScore >= 80) return '#d32f2f'; // 红色边框
|
||||
if (riskScore >= 60) return '#f57c00'; // 橙色边框
|
||||
if (riskScore >= 40) return '#fbc02d'; // 黄色边框
|
||||
return '#689f38'; // 绿色边框
|
||||
if (riskScore >= 80) return '#b71c1c'; // 更深的红色边框,提高对比度
|
||||
if (riskScore >= 60) return '#bf360c'; // 更深的橙色边框,提高对比度
|
||||
if (riskScore >= 40) return '#e65100'; // 更深的黄色边框,提高对比度
|
||||
return '#33691e'; // 更深的绿色边框,提高对比度
|
||||
}
|
||||
|
||||
return '#9e9e9e'; // 默认灰色边框
|
||||
},
|
||||
'border-style': function(ele) {
|
||||
const type = ele.data('type');
|
||||
// action节点使用虚线边框,其他使用实线
|
||||
if (type === 'action') return 'dashed';
|
||||
return 'solid';
|
||||
return '#616161'; // 更深的默认灰色边框,提高对比度
|
||||
},
|
||||
'border-style': 'solid', // 统一使用实线边框,提高可读性
|
||||
'overlay-padding': '12px',
|
||||
// 移除文字轮廓,使用纯色文字
|
||||
'text-outline-width': 0,
|
||||
// 增强阴影效果,使节点更立体更有层次感
|
||||
// 增强阴影效果,使节点更立体更有层次感(使用更柔和的阴影)
|
||||
'shadow-blur': 20,
|
||||
'shadow-opacity': 0.25,
|
||||
'shadow-offset-x': 2,
|
||||
'shadow-offset-y': 6,
|
||||
'shadow-color': 'rgba(0, 0, 0, 0.15)',
|
||||
'background-opacity': 1
|
||||
}
|
||||
},
|
||||
@@ -2213,7 +2201,6 @@ function renderAttackChain(chainData) {
|
||||
return '#9e9e9e';
|
||||
},
|
||||
'target-arrow-shape': 'triangle',
|
||||
'target-arrow-size': 8,
|
||||
// 使用bezier曲线,更美观
|
||||
'curve-style': 'bezier',
|
||||
'control-point-step-size': 60, // 增加步长,让控制点分布更均匀
|
||||
@@ -2231,13 +2218,7 @@ function renderAttackChain(chainData) {
|
||||
const type = ele.data('type');
|
||||
if (type === 'targets') return [8, 4]; // 虚线模式
|
||||
return [];
|
||||
},
|
||||
// 添加边的阴影效果(浅色主题使用浅阴影)
|
||||
'shadow-blur': 3,
|
||||
'shadow-opacity': 0.1,
|
||||
'shadow-offset-x': 1,
|
||||
'shadow-offset-y': 1,
|
||||
'shadow-color': '#000000'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -2245,29 +2226,11 @@ function renderAttackChain(chainData) {
|
||||
style: {
|
||||
'border-width': 5,
|
||||
'border-color': '#0066ff',
|
||||
'shadow-blur': 16,
|
||||
'shadow-opacity': 0.6,
|
||||
'shadow-offset-x': 4,
|
||||
'shadow-offset-y': 5,
|
||||
'shadow-color': '#0066ff',
|
||||
'z-index': 999,
|
||||
'opacity': 1,
|
||||
'overlay-opacity': 0.1,
|
||||
'overlay-color': '#0066ff'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'node:hover',
|
||||
style: {
|
||||
'border-width': 5,
|
||||
'shadow-blur': 14,
|
||||
'shadow-opacity': 0.5,
|
||||
'shadow-offset-x': 3,
|
||||
'shadow-offset-y': 4,
|
||||
'z-index': 998,
|
||||
'overlay-opacity': 0.05,
|
||||
'overlay-color': '#333333'
|
||||
}
|
||||
}
|
||||
],
|
||||
userPanningEnabled: true,
|
||||
@@ -2294,8 +2257,8 @@ function renderAttackChain(chainData) {
|
||||
const containerHeight = container ? container.offsetHeight : 800;
|
||||
|
||||
// 计算平均节点宽度(考虑不同类型节点的平均尺寸)
|
||||
const avgNodeWidth = isComplexGraph ? 230 : 250; // 基于新的节点尺寸
|
||||
const avgNodeHeight = isComplexGraph ? 97.5 : 107.5;
|
||||
const avgNodeWidth = isComplexGraph ? 370 : 410; // 进一步增大节点尺寸
|
||||
const avgNodeHeight = isComplexGraph ? 175 : 195;
|
||||
|
||||
// 计算图的层级深度(估算)
|
||||
const estimatedDepth = Math.ceil(Math.log2(Math.max(nodeCount, 2))) + 1;
|
||||
@@ -2317,13 +2280,13 @@ function renderAttackChain(chainData) {
|
||||
);
|
||||
|
||||
// 动态计算层级间距:基于容器高度和层级数
|
||||
// 大幅增加最小间距,避免节点重合
|
||||
// 减小垂直间距,让节点更紧凑,同时节点更大更易读
|
||||
const targetGraphHeight = containerHeight * 0.85;
|
||||
const calculatedRankSep = Math.max(
|
||||
avgNodeHeight * 2.5, // 最小为节点高度的2.5倍,确保垂直方向有足够间距
|
||||
avgNodeHeight * 1.3, // 减小到节点高度的1.3倍,让节点更紧凑
|
||||
Math.min(
|
||||
targetGraphHeight / Math.max(estimatedDepth - 1, 1),
|
||||
avgNodeHeight * 4.0 // 最大不超过节点高度的4.0倍
|
||||
avgNodeHeight * 2.0 // 最大不超过节点高度的2.0倍
|
||||
)
|
||||
);
|
||||
|
||||
@@ -2380,7 +2343,7 @@ function renderAttackChain(chainData) {
|
||||
// 动态计算节点间距,基于容器尺寸
|
||||
const container = attackChainCytoscape.container();
|
||||
const containerWidth = container ? container.offsetWidth : 1200;
|
||||
const avgNodeWidth = isComplexGraph ? 230 : 250;
|
||||
const avgNodeWidth = isComplexGraph ? 370 : 410; // 与布局计算保持一致
|
||||
const estimatedDepth = Math.ceil(Math.log2(Math.max(nodeCount, 2))) + 1;
|
||||
const maxLevelWidth = Math.max(1, Math.ceil(nodeCount / estimatedDepth));
|
||||
const targetGraphWidth = containerWidth * 0.95; // 与布局计算保持一致,使用95%宽度
|
||||
@@ -2827,7 +2790,24 @@ function renderAttackChain(chainData) {
|
||||
showNodeDetails(node.data());
|
||||
});
|
||||
|
||||
// 悬停渐变效果已移除
|
||||
// 添加悬停效果(使用事件监听器替代CSS选择器)
|
||||
attackChainCytoscape.on('mouseover', 'node', function(evt) {
|
||||
const node = evt.target;
|
||||
node.style('border-width', 5);
|
||||
node.style('z-index', 998);
|
||||
node.style('overlay-opacity', 0.05);
|
||||
node.style('overlay-color', '#333333');
|
||||
});
|
||||
|
||||
attackChainCytoscape.on('mouseout', 'node', function(evt) {
|
||||
const node = evt.target;
|
||||
const type = node.data('type');
|
||||
// 恢复默认边框宽度
|
||||
const defaultBorderWidth = type === 'target' ? 5 : 4;
|
||||
node.style('border-width', defaultBorderWidth);
|
||||
node.style('z-index', 'auto');
|
||||
node.style('overlay-opacity', 0);
|
||||
});
|
||||
|
||||
// 保存原始数据用于过滤
|
||||
window.attackChainOriginalData = chainData;
|
||||
@@ -3046,7 +3026,14 @@ function showNodeDetails(nodeData) {
|
||||
return;
|
||||
}
|
||||
|
||||
detailsPanel.style.display = 'block';
|
||||
// 使用 requestAnimationFrame 优化显示动画
|
||||
requestAnimationFrame(() => {
|
||||
detailsPanel.style.display = 'flex';
|
||||
// 在下一帧设置透明度,确保显示动画流畅
|
||||
requestAnimationFrame(() => {
|
||||
detailsPanel.style.opacity = '1';
|
||||
});
|
||||
});
|
||||
|
||||
let html = `
|
||||
<div class="node-detail-item">
|
||||
@@ -3154,16 +3141,39 @@ function showNodeDetails(nodeData) {
|
||||
`;
|
||||
}
|
||||
|
||||
if (nodeData.metadata && Object.keys(nodeData.metadata).length > 0) {
|
||||
html += `
|
||||
<div class="node-detail-item">
|
||||
<strong>完整元数据:</strong>
|
||||
<pre class="metadata-pre">${JSON.stringify(nodeData.metadata, null, 2)}</pre>
|
||||
</div>
|
||||
`;
|
||||
// 先重置滚动位置,避免内容更新时的滚动计算
|
||||
if (detailsContent) {
|
||||
detailsContent.scrollTop = 0;
|
||||
}
|
||||
|
||||
detailsContent.innerHTML = html;
|
||||
// 使用 requestAnimationFrame 优化 DOM 更新和滚动
|
||||
requestAnimationFrame(() => {
|
||||
// 更新内容
|
||||
detailsContent.innerHTML = html;
|
||||
|
||||
// 在下一帧执行滚动,避免与 DOM 更新冲突
|
||||
requestAnimationFrame(() => {
|
||||
// 重置详情内容区域的滚动位置
|
||||
if (detailsContent) {
|
||||
detailsContent.scrollTop = 0;
|
||||
}
|
||||
|
||||
// 重置侧边栏的滚动位置,确保详情区域可见
|
||||
const sidebar = document.querySelector('.attack-chain-sidebar-content');
|
||||
if (sidebar) {
|
||||
// 找到详情面板的位置
|
||||
const detailsPanel = document.getElementById('attack-chain-details');
|
||||
if (detailsPanel && detailsPanel.offsetParent !== null) {
|
||||
// 使用 getBoundingClientRect 获取位置,性能更好
|
||||
const detailsRect = detailsPanel.getBoundingClientRect();
|
||||
const sidebarRect = sidebar.getBoundingClientRect();
|
||||
const scrollTop = sidebar.scrollTop;
|
||||
const relativeTop = detailsRect.top - sidebarRect.top + scrollTop;
|
||||
sidebar.scrollTop = relativeTop - 20; // 留一点边距
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 获取严重程度颜色
|
||||
@@ -3197,6 +3207,27 @@ function updateAttackChainStats(chainData) {
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭节点详情
|
||||
function closeNodeDetails() {
|
||||
const detailsPanel = document.getElementById('attack-chain-details');
|
||||
if (detailsPanel) {
|
||||
// 添加淡出动画
|
||||
detailsPanel.style.opacity = '0';
|
||||
detailsPanel.style.maxHeight = detailsPanel.scrollHeight + 'px';
|
||||
|
||||
setTimeout(() => {
|
||||
detailsPanel.style.display = 'none';
|
||||
detailsPanel.style.maxHeight = '';
|
||||
detailsPanel.style.opacity = '';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// 取消选中节点
|
||||
if (attackChainCytoscape) {
|
||||
attackChainCytoscape.elements().unselect();
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭攻击链模态框
|
||||
function closeAttackChainModal() {
|
||||
const modal = document.getElementById('attack-chain-modal');
|
||||
@@ -3204,6 +3235,9 @@ function closeAttackChainModal() {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
|
||||
// 关闭节点详情
|
||||
closeNodeDetails();
|
||||
|
||||
// 清理Cytoscape实例
|
||||
if (attackChainCytoscape) {
|
||||
attackChainCytoscape.destroy();
|
||||
|
||||
+81
-48
@@ -821,57 +821,90 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body attack-chain-body">
|
||||
<div class="attack-chain-controls">
|
||||
<div class="attack-chain-info">
|
||||
<span id="attack-chain-stats">节点: 0 | 边: 0</span>
|
||||
</div>
|
||||
<div class="attack-chain-filters">
|
||||
<input type="text" id="attack-chain-search" placeholder="搜索节点..."
|
||||
oninput="filterAttackChainNodes(this.value)">
|
||||
<select id="attack-chain-type-filter"
|
||||
onchange="filterAttackChainByType(this.value)">
|
||||
<option value="all">所有类型</option>
|
||||
<option value="target">目标</option>
|
||||
<option value="action">行动</option>
|
||||
<option value="vulnerability">漏洞</option>
|
||||
</select>
|
||||
<select id="attack-chain-risk-filter"
|
||||
onchange="filterAttackChainByRisk(this.value)">
|
||||
<option value="all">所有风险</option>
|
||||
<option value="high">高风险 (80-100)</option>
|
||||
<option value="medium-high">中高风险 (60-79)</option>
|
||||
<option value="medium">中风险 (40-59)</option>
|
||||
<option value="low">低风险 (0-39)</option>
|
||||
</select>
|
||||
<button class="btn-secondary" onclick="resetAttackChainFilters()">
|
||||
重置筛选
|
||||
</button>
|
||||
</div>
|
||||
<div class="attack-chain-legend">
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background: #ff4444;"></span>
|
||||
<span>高风险 (80-100)</span>
|
||||
<div class="attack-chain-main-layout">
|
||||
<div class="attack-chain-visualization-area">
|
||||
<div class="attack-chain-toolbar">
|
||||
<div class="attack-chain-info">
|
||||
<span id="attack-chain-stats">节点: 0 | 边: 0</span>
|
||||
</div>
|
||||
<div class="attack-chain-filters">
|
||||
<input type="text" id="attack-chain-search" placeholder="搜索节点..."
|
||||
oninput="filterAttackChainNodes(this.value)">
|
||||
<select id="attack-chain-type-filter"
|
||||
onchange="filterAttackChainByType(this.value)">
|
||||
<option value="all">所有类型</option>
|
||||
<option value="target">目标</option>
|
||||
<option value="action">行动</option>
|
||||
<option value="vulnerability">漏洞</option>
|
||||
</select>
|
||||
<select id="attack-chain-risk-filter"
|
||||
onchange="filterAttackChainByRisk(this.value)">
|
||||
<option value="all">所有风险</option>
|
||||
<option value="high">高风险 (80-100)</option>
|
||||
<option value="medium-high">中高风险 (60-79)</option>
|
||||
<option value="medium">中风险 (40-59)</option>
|
||||
<option value="low">低风险 (0-39)</option>
|
||||
</select>
|
||||
<button class="btn-secondary" onclick="resetAttackChainFilters()">
|
||||
重置筛选
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background: #ff8800;"></span>
|
||||
<span>中高风险 (60-79)</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background: #ffbb00;"></span>
|
||||
<span>中风险 (40-59)</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background: #88cc00;"></span>
|
||||
<span>低风险 (0-39)</span>
|
||||
<div id="attack-chain-container" class="attack-chain-container">
|
||||
<div class="loading-spinner">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attack-chain-sidebar">
|
||||
<div class="attack-chain-sidebar-content">
|
||||
<div class="attack-chain-legend">
|
||||
<div class="legend-section">
|
||||
<div class="legend-title">风险等级</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background: #ff4444;"></span>
|
||||
<span>高风险 (80-100)</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background: #ff8800;"></span>
|
||||
<span>中高风险 (60-79)</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background: #ffbb00;"></span>
|
||||
<span>中风险 (40-59)</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background: #88cc00;"></span>
|
||||
<span>低风险 (0-39)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legend-section">
|
||||
<div class="legend-title">连接线含义</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-line" style="border-top: 2px solid #42a5f5;"></span>
|
||||
<span>蓝色线:行动发现漏洞</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-line" style="border-top: 2px solid #e53935;"></span>
|
||||
<span>红色线:使能/促成关系</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-line" style="border-top: 2px solid #616161;"></span>
|
||||
<span>灰色线:逻辑顺序</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="attack-chain-details" class="legend-section attack-chain-details" style="display: none;">
|
||||
<div class="legend-title attack-chain-details-title">
|
||||
<span>节点详情</span>
|
||||
<button class="attack-chain-details-close" onclick="closeNodeDetails()" title="关闭详情">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="attack-chain-details-content" class="attack-chain-details-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="attack-chain-container" class="attack-chain-container">
|
||||
<div class="loading-spinner">加载中...</div>
|
||||
</div>
|
||||
<div id="attack-chain-details" class="attack-chain-details" style="display: none;">
|
||||
<h3>节点详情</h3>
|
||||
<div id="attack-chain-details-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user