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

252 lines
8.4 KiB
Python

#!/usr/bin/env python3
"""
METATRON - tools.py
Recon tool runners — all output returned as strings to feed into the LLM.
Tools used: nmap, whois, whatweb, curl, dig, nikto
OS: Parrot OS (all these tools are pre-installed or easily available)
"""
import subprocess
# ─────────────────────────────────────────────
# BASE RUNNER
# ─────────────────────────────────────────────
def run_tool(command: list, timeout: int = 120) -> str:
"""
Execute a shell command, return combined stdout + stderr as string.
Never crashes the program — always returns something.
"""
try:
result = subprocess.run(
command,
capture_output=True,
text=True,
timeout=timeout
)
output = result.stdout.strip()
errors = result.stderr.strip()
if output and errors:
return output + "\n[STDERR]\n" + errors
elif output:
return output
elif errors:
return errors
else:
return "[!] Tool returned no output."
except subprocess.TimeoutExpired:
return f"[!] Timed out after {timeout}s: {' '.join(command)}"
except FileNotFoundError:
return f"[!] Tool not found: {command[0]} — install it with: sudo apt install {command[0]}"
except Exception as e:
return f"[!] Unexpected error running {command[0]}: {e}"
# ─────────────────────────────────────────────
# INDIVIDUAL TOOLS
# ─────────────────────────────────────────────
def run_nmap(target: str) -> str:
"""
nmap -sV -sC -T4 --open
-sV : detect service versions
-sC : run default scripts (basic vuln checks)
-T4 : aggressive timing (faster)
--open : only show open ports
"""
print(f" [*] nmap -sV -sC -T4 --open {target}")
return run_tool(["nmap", "-sV", "-sC", "-T4", "--open", target], timeout=180)
def run_whois(target: str) -> str:
"""
whois — domain registration, registrar, IP ownership info
"""
print(f" [*] whois {target}")
return run_tool(["whois", target], timeout=30)
def run_whatweb(target: str) -> str:
"""
whatweb -a 3 — fingerprint web technologies, CMS, frameworks, headers
-a 3 : aggression level 3 (active but not destructive)
"""
print(f" [*] whatweb -a 3 {target}")
return run_tool(["whatweb", "-a", "3", target], timeout=60)
def run_curl_headers(target: str) -> str:
"""
curl -sI — fetch HTTP headers only
Reveals: server software, X-Powered-By, cookies, security headers (or lack of them)
"""
print(f" [*] curl -sI http://{target}")
output = run_tool([
"curl", "-sI",
"--max-time", "10",
"--location", # follow redirects
f"http://{target}"
], timeout=20)
# also try https
https_output = run_tool([
"curl", "-sI",
"--max-time", "10",
"--location",
"-k", # ignore cert errors
f"https://{target}"
], timeout=20)
return f"[HTTP Headers]\n{output}\n\n[HTTPS Headers]\n{https_output}"
def run_dig(target: str) -> str:
"""
dig — DNS records: A, MX, NS, TXT
Useful for subdomains, mail servers, SPF/DKIM info
"""
print(f" [*] dig {target} ANY")
a_record = run_tool(["dig", "+short", "A", target], timeout=15)
mx_record = run_tool(["dig", "+short", "MX", target], timeout=15)
ns_record = run_tool(["dig", "+short", "NS", target], timeout=15)
txt_record= run_tool(["dig", "+short", "TXT", target], timeout=15)
return (
f"[A Records]\n{a_record}\n\n"
f"[MX Records]\n{mx_record}\n\n"
f"[NS Records]\n{ns_record}\n\n"
f"[TXT Records]\n{txt_record}"
)
def run_nikto(target: str) -> str:
"""
nikto -h — web server vulnerability scanner
Checks for outdated software, dangerous files, misconfigurations
WARNING: noisy tool, only run with permission
"""
print(f" [*] nikto -h {target} (this may take a while...)")
return run_tool(["nikto", "-h", target, "-nointeractive"], timeout=300)
# ─────────────────────────────────────────────
# MAIN RECON PIPELINE
# ─────────────────────────────────────────────
TOOLS_MENU = {
"1": ("nmap", run_nmap),
"2": ("whois", run_whois),
"3": ("whatweb", run_whatweb),
"4": ("curl headers", run_curl_headers),
"5": ("dig DNS", run_dig),
"6": ("nikto", run_nikto),
}
def run_default_recon(target: str) -> dict:
"""
Run the standard recon pipeline (everything except nikto).
Returns a dict of {tool_name: output_string}.
Nikto is excluded by default — too slow/noisy for auto-run.
"""
print(f"\n[*] Starting recon on: {target}")
print("─" * 50)
results = {}
results["nmap"] = run_nmap(target)
results["whois"] = run_whois(target)
results["whatweb"] = run_whatweb(target)
results["curl_headers"] = run_curl_headers(target)
results["dig"] = run_dig(target)
print("─" * 50)
print("[+] Recon complete.\n")
return results
def run_single_tool(tool_key: str, target: str) -> str:
"""Run one tool by its menu key. Used by AI tool dispatch."""
if tool_key in TOOLS_MENU:
name, func = TOOLS_MENU[tool_key]
return func(target)
return f"[!] Unknown tool key: {tool_key}"
def format_recon_for_llm(results: dict) -> str:
"""
Flatten the recon results dict into one clean string
to paste into the LLM prompt.
"""
output = ""
for tool, data in results.items():
output += f"\n{'='*50}\n"
output += f"[ {tool.upper()} OUTPUT ]\n"
output += f"{'='*50}\n"
output += data.strip() + "\n"
return output
ALLOWED_TOOLS = {"nmap", "whois", "whatweb", "curl", "dig", "nikto"}
def run_tool_by_command(command_str: str) -> str:
parts = command_str.strip().split()
if not parts:
return "[!] Empty command."
# allowlist only — reject anything not in the list
tool = parts[0].lower().split("/")[-1] # handles /bin/nmap etc
if tool not in ALLOWED_TOOLS:
return f"[!] Tool '{parts[0]}' is not permitted. Allowed: {ALLOWED_TOOLS}"
return run_tool(parts)
# ─────────────────────────────────────────────
# INTERACTIVE TOOL SELECTOR (called from CLI)
# ─────────────────────────────────────────────
def interactive_tool_run(target: str) -> str:
"""
Let user manually pick which tools to run.
Returns combined output string.
"""
print("\n[ SELECT TOOLS TO RUN ]")
for key, (name, _) in TOOLS_MENU.items():
print(f" [{key}] {name}")
print(" [a] Run all (except nikto)")
print(" [n] Run all + nikto (slow)")
choice = input("\nChoice(s) e.g. 1 2 4 or a: ").strip().lower()
if choice == "a":
results = run_default_recon(target)
return format_recon_for_llm(results)
if choice == "n":
results = run_default_recon(target)
results["nikto"] = run_nikto(target)
return format_recon_for_llm(results)
combined = {}
for key in choice.split():
if key in TOOLS_MENU:
name, func = TOOLS_MENU[key]
print(f"\n[*] Running {name}...")
combined[name] = func(target)
else:
print(f"[!] Unknown option: {key}")
return format_recon_for_llm(combined)
# ─────────────────────────────────────────────
# QUICK TEST
# ─────────────────────────────────────────────
if __name__ == "__main__":
target = input("Enter test target (IP or domain): ").strip()
results = run_default_recon(target)
print(format_recon_for_llm(results))