🧠 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
- Scan de ports : Vérifie 21, 22, 80, 443, 445, 8080, 8443 – rapide et multi-threadé.
- Détection vulnérabilités : Cherche des CVE (ex. SonicWall CVE-2024-40766, SMB CVE-2024-38021), SQLi, XSS, et technos exposées (WordPress, phpMyAdmin, etc.).
- Scraping récursif : Extrait les liens d’une page et scanne jusqu’à 50 niveaux (configurable).
- Score de risque : Calcule un niveau de dangerosité (0-10) basé sur les failles trouvées.
- Rapport JSON : Sauvegarde tout dans un fichier clair et structuré.
🛠️ Prérequis
- Système : Linux/Windows avec Python 3.8+.
- Dépendances :
pip install beautifulsoup4 requests - Permissions : Exécute en root pour le scan de ports (optionnel mais recommandé).
- Cible : Un domaine ou IP que TU contrôles ou as l’autorisation de tester.
⚠️ 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.