Files
2026-07-13 12:30:44 +08:00

431 lines
15 KiB
Python

#!/usr/bin/env python3
"""
METATRON - metatron.py
Main CLI entry point. Wires db.py + tools.py + search.py + llm.py together.
Run with: python metatron.py
"""
from export import export_menu
import os
import sys
from db import (
get_connection,
create_session,
save_vulnerability,
save_fix,
save_exploit,
save_summary,
get_all_history,
get_session,
get_vulnerabilities,
get_fixes,
get_exploits,
edit_vulnerability,
edit_fix,
edit_exploit,
edit_summary_risk,
delete_vulnerability,
delete_exploit,
delete_fix,
delete_full_session,
print_history,
print_session
)
from tools import interactive_tool_run, format_recon_for_llm, run_default_recon
from llm import analyse_target
# ─────────────────────────────────────────────
# BANNER
# ─────────────────────────────────────────────
def banner():
os.system("clear")
print("""
\033[91m
███╗ ███╗███████╗████████╗ █████╗ ████████╗██████╗ ██████╗ ███╗ ██╗
████╗ ████║██╔════╝╚══██╔══╝██╔══██╗╚══██╔══╝██╔══██╗██╔═══██╗████╗ ██║
██╔████╔██║█████╗ ██║ ███████║ ██║ ██████╔╝██║ ██║██╔██╗ ██║
██║╚██╔╝██║██╔══╝ ██║ ██╔══██║ ██║ ██╔══██╗██║ ██║██║╚██╗██║
██║ ╚═╝ ██║███████╗ ██║ ██║ ██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║
╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
\033[0m
\033[90mAI Penetration Testing Assistant | Model: metatron-qwen | Parrot OS\033[0m
\033[90m─────────────────────────────────────────────────────────────────────\033[0m
""")
# ─────────────────────────────────────────────
# HELPERS
# ─────────────────────────────────────────────
def divider(label=""):
if label:
print(f"\n\033[33m{'─'*20} {label} {'─'*20}\033[0m")
else:
print(f"\033[90m{'─'*60}\033[0m")
def prompt(text):
return input(f"\033[36m{text}\033[0m").strip()
def success(text):
print(f"\033[92m[+] {text}\033[0m")
def warn(text):
print(f"\033[93m[!] {text}\033[0m")
def error(text):
print(f"\033[91m[✗] {text}\033[0m")
def info(text):
print(f"\033[94m[*] {text}\033[0m")
def confirm(question: str) -> bool:
ans = prompt(f"{question} [y/N]: ").lower()
return ans == "y"
# ─────────────────────────────────────────────
# NEW SCAN
# ─────────────────────────────────────────────
def new_scan():
divider("NEW SCAN")
target = prompt("[?] Enter target IP or domain: ")
if not target:
warn("No target entered.")
return
# check if target was scanned before
history = get_all_history()
past = [row for row in history if row[1] == target]
if past:
warn(f"Target '{target}' has been scanned before ({len(past)} time(s)).")
if not confirm("Continue with a new scan?"):
return
# create session in history table first
sl_no = create_session(target)
success(f"Session created — SL# {sl_no}")
# run recon tools
divider("RECON")
info("Choose recon tools to run:")
raw_scan = interactive_tool_run(target)
if not raw_scan.strip():
warn("No scan data collected. Aborting.")
delete_full_session(sl_no)
return
# send to AI
divider("AI ANALYSIS")
result = analyse_target(target, raw_scan)
# ── save everything to DB ──────────────────
divider("SAVING TO DATABASE")
# save vulnerabilities and their fixes
for vuln in result["vulnerabilities"]:
vuln_id = save_vulnerability(
sl_no,
vuln["vuln_name"],
vuln["severity"],
vuln["port"],
vuln["service"],
vuln["description"]
)
if vuln.get("fix"):
save_fix(sl_no, vuln_id, vuln["fix"], source="ai")
success(f"Saved vuln: {vuln['vuln_name']} [{vuln['severity']}]")
# save exploits
for exp in result["exploits"]:
save_exploit(
sl_no,
exp["exploit_name"],
exp["tool_used"],
exp["payload"],
exp["result"],
exp["notes"]
)
success(f"Saved exploit: {exp['exploit_name']}")
# save summary
save_summary(
sl_no,
result["raw_scan"],
result["full_response"],
result["risk_level"]
)
success(f"All data saved. SL# {sl_no} | Risk: {result['risk_level']}")
divider()
# show results and offer edit/delete
data = get_session(sl_no)
print_session(data)
if confirm("Edit or delete anything in this session?"):
edit_delete_menu(sl_no)
# ─────────────────────────────────────────────
# VIEW HISTORY
# ─────────────────────────────────────────────
def view_history():
divider("SCAN HISTORY")
rows = get_all_history()
if not rows:
warn("No scans in database yet.")
return
print_history(rows)
sl_no_str = prompt("Enter SL# to view details (or press Enter to go back): ")
if not sl_no_str:
return
try:
sl_no = int(sl_no_str)
except ValueError:
error("Invalid SL#.")
return
data = get_session(sl_no)
if not data["history"]:
error(f"SL# {sl_no} not found.")
return
print_session(data)
if confirm("Export this session?"):
export_menu(data)
if confirm("Edit or delete anything in this session?"):
edit_delete_menu(sl_no)
# ─────────────────────────────────────────────
# EDIT / DELETE MENU
# ─────────────────────────────────────────────
def edit_delete_menu(sl_no: int):
while True:
divider(f"EDIT / DELETE — SL# {sl_no}")
print(" [1] Edit a vulnerability")
print(" [2] Edit a fix")
print(" [3] Edit an exploit")
print(" [4] Edit risk level")
print(" [5] Delete a vulnerability")
print(" [6] Delete a fix")
print(" [7] Delete an exploit")
print(" [8] Delete FULL session (all tables)")
print(" [9] Back")
divider()
choice = prompt("Choice: ")
# ── EDIT VULNERABILITY ─────────────────
if choice == "1":
vulns = get_vulnerabilities(sl_no)
if not vulns:
warn("No vulnerabilities recorded for this session.")
continue
print("\n[ VULNERABILITIES ]")
for v in vulns:
print(f" id={v[0]} | {v[2]} | {v[3]} | port {v[4]} | {v[5]}")
vid = prompt("Enter vulnerability id to edit: ")
if not vid.isdigit():
error("Invalid id.")
continue
print(" Fields: vuln_name / severity / port / service / description")
field = prompt("Field to edit: ").strip()
value = prompt(f"New value for '{field}': ")
edit_vulnerability(int(vid), field, value)
# ── EDIT FIX ──────────────────────────
elif choice == "2":
fixes = get_fixes(sl_no)
if not fixes:
warn("No fixes recorded for this session.")
continue
print("\n[ FIXES ]")
for f in fixes:
print(f" id={f[0]} | vuln_id={f[2]} | {f[3][:80]}")
fid = prompt("Enter fix id to edit: ")
if not fid.isdigit():
error("Invalid id.")
continue
new_text = prompt("New fix text: ")
edit_fix(int(fid), new_text)
# ── EDIT EXPLOIT ──────────────────────
elif choice == "3":
exploits = get_exploits(sl_no)
if not exploits:
warn("No exploits recorded for this session.")
continue
print("\n[ EXPLOITS ]")
for e in exploits:
print(f" id={e[0]} | {e[2]} | tool: {e[3]} | result: {e[5]}")
eid = prompt("Enter exploit id to edit: ")
if not eid.isdigit():
error("Invalid id.")
continue
print(" Fields: exploit_name / tool_used / payload / result / notes")
field = prompt("Field to edit: ").strip()
value = prompt(f"New value for '{field}': ")
edit_exploit(int(eid), field, value)
# ── EDIT RISK LEVEL ───────────────────
elif choice == "4":
print(" Options: CRITICAL / HIGH / MEDIUM / LOW")
risk = prompt("New risk level: ").upper()
if risk not in ("CRITICAL", "HIGH", "MEDIUM", "LOW"):
error("Invalid risk level.")
continue
edit_summary_risk(sl_no, risk)
# ── DELETE VULNERABILITY ──────────────
elif choice == "5":
vulns = get_vulnerabilities(sl_no)
if not vulns:
warn("No vulnerabilities to delete.")
continue
print("\n[ VULNERABILITIES ]")
for v in vulns:
print(f" id={v[0]} | {v[2]} | {v[3]}")
vid = prompt("Enter vulnerability id to delete: ")
if not vid.isdigit():
error("Invalid id.")
continue
if confirm(f"Delete vulnerability id={vid} and its linked fixes?"):
delete_vulnerability(int(vid))
# ── DELETE FIX ────────────────────────
elif choice == "6":
fixes = get_fixes(sl_no)
if not fixes:
warn("No fixes to delete.")
continue
print("\n[ FIXES ]")
for f in fixes:
print(f" id={f[0]} | vuln_id={f[2]} | {f[3][:80]}")
fid = prompt("Enter fix id to delete: ")
if not fid.isdigit():
error("Invalid id.")
continue
if confirm(f"Delete fix id={fid}?"):
delete_fix(int(fid))
# ── DELETE EXPLOIT ────────────────────
elif choice == "7":
exploits = get_exploits(sl_no)
if not exploits:
warn("No exploits to delete.")
continue
print("\n[ EXPLOITS ]")
for e in exploits:
print(f" id={e[0]} | {e[2]} | result: {e[5]}")
eid = prompt("Enter exploit id to delete: ")
if not eid.isdigit():
error("Invalid id.")
continue
if confirm(f"Delete exploit id={eid}?"):
delete_exploit(int(eid))
# ── DELETE FULL SESSION ───────────────
elif choice == "8":
if confirm(f"\n\033[91mPermanently delete ENTIRE session SL# {sl_no} from all tables?\033[0m"):
delete_full_session(sl_no)
success(f"Session SL# {sl_no} wiped.")
return # go back to main menu
# ── BACK ──────────────────────────────
elif choice == "9":
break
else:
warn("Invalid choice.")
# ─────────────────────────────────────────────
# DB CONNECTION CHECK
# ─────────────────────────────────────────────
def check_db():
try:
conn = get_connection()
conn.close()
return True
except Exception as e:
error(f"MariaDB connection failed: {e}")
error("Make sure MariaDB is running: sudo systemctl start mariadb")
return False
# ─────────────────────────────────────────────
# MAIN MENU
# ─────────────────────────────────────────────
def main_menu():
while True:
banner()
print(" \033[92m[1]\033[0m New Scan")
print(" \033[92m[2]\033[0m View History")
print(" \033[92m[3]\033[0m Exit")
divider()
choice = prompt("metatron> ")
if choice == "1":
new_scan()
input("\n\033[90mPress Enter to continue...\033[0m")
elif choice == "2":
view_history()
input("\n\033[90mPress Enter to continue...\033[0m")
elif choice == "3":
print("\n\033[91m[*] Shutting down Metatron. Stay legal.\033[0m\n")
sys.exit(0)
else:
warn("Invalid choice.")
# ─────────────────────────────────────────────
# ENTRY POINT
# ─────────────────────────────────────────────
if __name__ == "__main__":
if not check_db():
sys.exit(1)
main_menu()