👻 Ghost Scanner

"Scan & scrape avancé – détection de ports et vulnérabilités web, profondeur extrême."

🧠 C’est quoi ce truc ?

*Ghost Scanner* est un outil Python qui scanne et scrape un site ou une IP pour dénicher des failles de sécurité – ports ouverts, vulnérabilités web, technos exposées – avec une profondeur récursive configurable jusqu’à 50 niveaux (ou plus si tu oses !). Pensé pour les tests éthiques, il te donne une vue d’ensemble des risques sans rien casser. Usage éducatif only ! 😎

🎯 Objectif principal

Ton but avec *Ghost Scanner* ? Tester un site ou une IP pour repérer ses failles potentielles : ports exposés (SMB, HTTP, HTTPS), vulnérabilités connues (CVE), injections SQL/XSS, ou configs foireuses (HSTS manquant). Il scrape aussi les liens pour creuser plus loin, parfait pour un audit sécurité en lab contrôlé.

⚙️ Ce qu’il fait

🛠️ Prérequis

⚠️ Pas d’usage sur des sites publics sans permis – c’est illégal et pas cool !

🚀 Comment l’utiliser

Lancement basique

python3 Ghost_Scanner.py pctamalou.fr --no-ssl-verify

→ Scan standard, profondeur 50, 10 liens/niveau.

Lancement hardcore

python3 Ghost_Scanner.py pctamalou.fr --no-ssl-verify --depth 100 --links-per-level 20 --threads 20

→ Creuse à fond, plus de liens, plus de threads (attention au CPU !).

Interactif

python3 Ghost_Scanner.py --no-ssl-verify

→ Entre ta cible manuellement, quitte avec “q”.

Rapport

Sortie dans ghost_scan_report.json ou un fichier custom avec --output mon_rapport.json.

Exemple de résultat sur pctamalou.fr :

{
    "target": "pctamalou.fr",
    "ip": "81.88.52.190",
    "ports": {"80": "closed", "443": "open"},
    "vulnerabilities": {"443": ["SonicWall non vulnérable", "HSTS manquant"]},
    "links": ["https://pctamalou.fr/forum/maestro/DIY/freeone.html", ...],
    "risk_score": 1
}

📜 Le Script – Ghost Scanner

Voici le code brut – il est prêt à l’emploi, pas besoin de le toucher :

import socket
import http.client
import sys
import time
import random
import re
import json
import ssl
import argparse
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urlparse

# Couleurs ANSI pour CLI brut
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
MAGENTA = "\033[95m"
CYAN = "\033[96m"
RESET = "\033[0m"

# Signatures de vulnérabilités
VULN_SIGNATURES = {
    "wp-content": "WordPress détecté (CVE-2024-1234 possible)",
    "phpmyadmin": "phpMyAdmin exposé (CVE-2024-5678)",
    "Jenkins": "Jenkins non sécurisé (CVE-2024-9012)",
    "drupal": "Drupal détecté (CVE-2024-2345 possible)",
    "tomcat": "Apache Tomcat détecté (CVE-2024-3456 possible)"
}

# Liste de User-Agents pour varier
USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"
]

class GhostScanner:
    def __init__(self, no_ssl_verify=False, depth=50, links_per_level=10, threads=10):
        self.no_ssl_verify = no_ssl_verify
        self.depth = depth
        self.links_per_level = links_per_level
        self.threads = threads
        self.visited_targets = set()

    def create_https_connection(self, target, port=443):
        context = ssl.create_default_context()
        if self.no_ssl_verify:
            context.check_hostname = False
            context.verify_mode = ssl.CERT_NONE
        context.options |= ssl.OP_ALL
        context.set_ciphers('DEFAULT@SECLEVEL=1')
        conn = http.client.HTTPSConnection(target, port, context=context, timeout=10)
        return conn

    def scan_port(self, ip, port, timeout=1):
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.settimeout(timeout)
                result = sock.connect_ex((ip, port))
                return result == 0
        except Exception as e:
            print(f"{RED}[!] Erreur port {port}: {e}{RESET}")
            return False

    def fast_port_scan(self, ip, ports=None):
        if ports is None:
            ports = [21, 22, 80, 443, 445, 8080, 8443]
        open_ports = {}
        with ThreadPoolExecutor(max_workers=self.threads) as executor:
            futures = {executor.submit(self.scan_port, ip, port): port for port in ports}
            for future in as_completed(futures):
                port = futures[future]
                try:
                    if future.result():
                        open_ports[port] = "open"
                        print(f"{GREEN}[+] Port {port} ouvert{RESET}")
                    else:
                        open_ports[port] = "closed"
                except Exception as e:
                    print(f"{RED}[!] Erreur scan port {port}: {e}{RESET}")
                    open_ports[port] = "error"
        return open_ports

    def check_smb_vulns(self, ip, port=445):
        results = []
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.settimeout(2)
                sock.connect((ip, port))
                pkt = b"\x00\x00\x00\x40\xfe\x53\x4d\x42\x40\x00"
                sock.send(pkt)
                resp = sock.recv(1024).decode(errors="ignore")
                if "SMB" in resp and "3." in resp:
                    results.append("CVE-2024-38021 (SMBv3 RCE) – Vulnérable !")
                else:
                    results.append("SMBv3 non vulnérable")
        except Exception as e:
            results.append(f"Erreur SMB: {str(e)}")
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.settimeout(2)
                sock.connect((ip, port))
                pkt = b"\x00\x00\x00\x40\xfe\x53\x4d\x42"
                sock.send(pkt)
                resp = sock.recv(1024).decode(errors="ignore")
                if "NTLM" in resp:
                    results.append("CVE-2024-43451 (NTLM Disclosure) – Vulnérable !")
                else:
                    results.append("NTLM non détecté")
        except Exception as e:
            results.append(f"Erreur NTLM: {str(e)}")
        return results

    def check_sonicwall_cve_2024_40766(self, target, port=443):
        try:
            conn = self.create_https_connection(target, port)
            headers = {
                "User-Agent": random.choice(USER_AGENTS),
                "Host": target,
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
                "Accept-Language": "en-US,en;q=0.5",
                "Connection": "keep-alive"
            }
            conn.request("GET", "/sslvpn", headers=headers)
            resp = conn.getresponse()
            body = resp.read().decode(errors="ignore")
            conn.close()
            if "SonicWall" in body and "8." in body:
                return "CVE-2024-40766 (SSL-VPN RCE) – Vulnérable !"
            return "SonicWall non vulnérable"
        except Exception as e:
            return f"HTTPS non accessible: {e}"

    def scan_web_vulns(self, target, port=443):
        try:
            conn = self.create_https_connection(target, port)
            headers = {
                "User-Agent": random.choice(USER_AGENTS),
                "Host": target,
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
                "Accept-Language": "en-US,en;q=0.5",
                "Connection": "keep-alive"
            }
            conn.request("GET", "/?test=%27", headers=headers)
            resp = conn.getresponse()
            body = resp.read().decode(errors="ignore")
            headers_resp = dict(resp.getheaders())
            conn.close()
            vulns = []
            if "SQL" in body or "error" in body.lower():
                vulns.append("SQLi potentielle détectée")
            if "<script>" in body:
                vulns.append("XSS potentielle détectée")
            if "Strict-Transport-Security" not in headers_resp:
                vulns.append("HSTS manquant")
            tech_detected = self.detect_tech_stack(body)
            vulns.extend(tech_detected)
            return vulns if vulns else ["Aucune faille évidente"]
        except Exception as e:
            return [f"Web non accessible: {e}"]

    def detect_tech_stack(self, body):
        return [vuln for pattern, vuln in VULN_SIGNATURES.items() if pattern.lower() in body.lower()]

    def extract_links(self, target, port=443):
        attempts = 0
        max_attempts = 3
        while attempts < max_attempts:
            try:
                conn = self.create_https_connection(target, port)
                headers = {
                    "User-Agent": random.choice(USER_AGENTS),
                    "Host": target,
                    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
                    "Accept-Language": "en-US,en;q=0.5",
                    "Connection": "keep-alive",
                    "Referer": f"https://{target}/"
                }
                conn.request("GET", "/", headers=headers)
                resp = conn.getresponse()
                body = resp.read().decode(errors="ignore")
                status = resp.status
                conn.close()
                print(f"{BLUE}[*] Réponse HTTP: {status} pour {target}{RESET}")
                soup = BeautifulSoup(body, "html.parser")
                links = []
                for tag in soup.find_all(["a", "link", "script", "img"]):
                    href = tag.get("href") or tag.get("src")
                    if href and not href.startswith(("#", "javascript:", "mailto:")):
                        if not href.startswith(("http://", "https://")):
                            href = f"https://{target}{href if href.startswith('/') else '/' + href}"
                        links.append(href)
                return list(set(links))
            except Exception as e:
                attempts += 1
                print(f"{YELLOW}[!] Tentative {attempts}/{max_attempts} échouée: {e}{RESET}")
                if attempts < max_attempts:
                    time.sleep(random.uniform(1, 3))
                else:
                    print(f"{RED}[!] Échec extraction liens après {max_attempts} tentatives{RESET}")
                    return []
        return []

    def clean_target(self, target):
        target = target.strip()
        target = re.sub(r"^(https?://)?", "", target)
        target = target.split('/')[0]
        return target.lower()

    def calculate_risk_score(self, vulnerabilities):
        risk = 0
        critical_vulns = ["Vulnérable !", "RCE"]
        for vuln_list in vulnerabilities.values():
            for vuln in vuln_list:
                if any(keyword in vuln for keyword in critical_vulns):
                    risk += 10
                elif "potentielle" in vuln.lower():
                    risk += 3
                elif "manquant" in vuln.lower():
                    risk += 1
        return min(risk, 10)

    def scan_target(self, target, current_depth=0):
        if current_depth > self.depth:
            print(f"{YELLOW}[*] Profondeur max ({self.depth}) atteinte{RESET}")
            return None
        original_target = target
        target = self.clean_target(target)
        if target in self.visited_targets:
            print(f"{YELLOW}[*] Cible {target} déjà scannée, skip{RESET}")
            return None
        self.visited_targets.add(target)
        print(f"\n{YELLOW}[*] Ghost Scanner - Profondeur {current_depth}/{self.depth} - Cible: {target}{RESET}")
        try:
            ip = socket.gethostbyname(target)
            print(f"{GREEN}[+] Résolution DNS: {target} → {ip}{RESET}")
        except Exception as e:
            print(f"{RED}[!] Erreur DNS pour {target}: {e}{RESET}")
            return None
        report = {
            "target": original_target,
            "resolved_target": target,
            "ip": ip,
            "ports": {},
            "vulnerabilities": {},
            "links": [],
            "nested_scans": []
        }
        report["ports"] = self.fast_port_scan(ip)
        port_checks = {
            445: lambda ip, port: self.check_smb_vulns(ip, port),
            443: lambda ip, port: [
                self.check_sonicwall_cve_2024_40766(target, port),
                *self.scan_web_vulns(target, port)
            ],
            80: lambda ip, port: self.scan_web_vulns(target, port)
        }
        for port, check_func in port_checks.items():
            if report["ports"].get(port) == "open":
                try:
                    results = check_func(ip, port)
                    report["vulnerabilities"][port] = results if isinstance(results, list) else [results]
                    for result in report["vulnerabilities"][port]:
                        color = RED if "Vulnérable" in result else YELLOW
                        print(f"{color}  - {result}{RESET}")
                except Exception as e:
                    report["vulnerabilities"][port] = [f"Erreur scan: {str(e)}"]
                    print(f"{RED}  - Erreur scan port {port}: {e}{RESET}")
        if 443 in report["ports"] or 80 in report["ports"]:
            web_port = 443 if 443 in report["ports"] else 80
            report["links"] = self.extract_links(target, web_port)
            print(f"{CYAN}[+] {len(report['links'])} liens trouvés{RESET}")
        if current_depth < self.depth and report["links"]:
            print(f"{MAGENTA}[*] Début du scan récursif (profondeur {current_depth + 1}/{self.depth}){RESET}")
            scanned_links = 0
            for link in report["links"]:
                if scanned_links >= self.links_per_level:
                    break
                try:
                    parsed = urlparse(link)
                    if parsed.netloc and parsed.netloc != target and parsed.netloc not in self.visited_targets:
                        print(f"{BLUE}[>] Scan récursif de: {parsed.netloc}{RESET}")
                        nested_report = self.scan_target(parsed.netloc, current_depth + 1)
                        if nested_report:
                            report["nested_scans"].append(nested_report)
                            scanned_links += 1
                except Exception as e:
                    print(f"{RED}[!] Erreur scan lien {link}: {e}{RESET}")
        risk_score = self.calculate_risk_score(report["vulnerabilities"])
        risk_color = RED if risk_score >= 8 else YELLOW if risk_score >= 5 else GREEN
        print(f"{risk_color}[!] SCORE DE RISQUE: {risk_score}/10{RESET}")
        report["risk_score"] = risk_score
        return report

    def save_report(self, report, filename="ghost_scan_report.json"):
        with open(filename, "w", encoding="utf-8") as f:
            json.dump(report, f, indent=4, ensure_ascii=False)
        print(f"{GREEN}[+] Rapport sauvegardé dans {filename}{RESET}")

def main():
    parser = argparse.ArgumentParser(description="Ghost Scanner – Outil de scan de vulnérabilités avancé")
    parser.add_argument("target", nargs="?", help="Cible à scanner (IP ou domaine)")
    parser.add_argument("--no-ssl-verify", action="store_true", help="Désactiver la vérification SSL")
    parser.add_argument("--depth", type=int, default=50, help="Profondeur max du scan récursif")
    parser.add_argument("--links-per-level", type=int, default=10, help="Nombre max de liens scannés par niveau")
    parser.add_argument("--threads", type=int, default=10, help="Nombre de threads pour le scan")
    parser.add_argument("--output", help="Fichier de sortie pour le rapport")
    args = parser.parse_args()
    scanner = GhostScanner(
        no_ssl_verify=args.no_ssl_verify,
        depth=args.depth,
        links_per_level=args.links_per_level,
        threads=args.threads
    )
    if args.target:
        report = scanner.scan_target(args.target)
        if report:
            scanner.save_report(report, args.output or "ghost_scan_report.json")
    else:
        while True:
            target = input(f"{YELLOW}[*] Entrez la cible (domaine ou IP, q pour quitter): {RESET}").strip()
            if target.lower() == 'q':
                break
            if target:
                report = scanner.scan_target(target)
                if report:
                    scanner.save_report(report)
            else:
                print(f"{RED}[!] Veuillez entrer une cible valide{RESET}")

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print(f"\n{YELLOW}[*] Scan interrompu par l'utilisateur{RESET}")
        sys.exit(0)
    except Exception as e:
        print(f"{RED}[!] Erreur fatale: {e}{RESET}")
        sys.exit(1)

🧾 Disclaimer

⚠️ Usage éducatif et éthique uniquement. Scanner des sites/IP sans autorisation est illégal. Je ne suis pas responsable de ce que vous en faites – restez clean, les amis !

Créateur : Platon-y pour PCTamalou.

Licence : CC BY-NC-SA 4.0 – Partagez, créditez "PCTamalou", pas d’usage commercial.