|
|
@@ -0,0 +1,320 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+
|
|
|
+"""
|
|
|
+=============================================================================
|
|
|
+PROXY VPN DUAL (TCP + TLS) - ULTIMATE PYTHON EDITION
|
|
|
+Características: IPv4 & IPv6 (Dual-Stack), Multiplexación 80/443,
|
|
|
+Fake Web Nginx, Anti-Flood en RAM, Mensajes Rotativos y Custom Headers.
|
|
|
+=============================================================================
|
|
|
+"""
|
|
|
+
|
|
|
+import socket
|
|
|
+import ssl
|
|
|
+import threading
|
|
|
+import select
|
|
|
+import time
|
|
|
+import datetime
|
|
|
+import os
|
|
|
+import sys
|
|
|
+
|
|
|
+# --- CONFIGURACIÓN BASE ---
|
|
|
+PORT_TCP = 80
|
|
|
+PORT_TLS = 443
|
|
|
+SSH_HOST = "127.0.0.1"
|
|
|
+SSH_PORT = 22
|
|
|
+CERT_FILE = "/root/cert.pem"
|
|
|
+KEY_FILE = "/root/key.pem"
|
|
|
+LOG_FILE = "/root/proxy-dual-python.log"
|
|
|
+
|
|
|
+# --- SEGURIDAD ANTI-FLOOD Y BANEO ---
|
|
|
+AUTO_BAN_STRIKES = 20 # Límite de conexiones por segundo exacto (tolera navegadores)
|
|
|
+BAN_TIME = 3600 # Castigo de 1 hora en segundos
|
|
|
+ip_database = {} # Base de datos en memoria RAM
|
|
|
+db_lock = threading.Lock()
|
|
|
+
|
|
|
+# --- FAKE WEB RESPONSES ---
|
|
|
+FAKE_WEB_TCP = (
|
|
|
+ b"HTTP/1.1 400 Bad Request\r\n"
|
|
|
+ b"Server: nginx/1.24.0\r\n"
|
|
|
+ b"Content-Type: text/html\r\n"
|
|
|
+ b"Connection: close\r\n\r\n"
|
|
|
+ b"<html><body><center><h1>400 Bad Request</h1></center><hr><center>nginx/1.24.0</center></body></html>\r\n"
|
|
|
+)
|
|
|
+
|
|
|
+FAKE_WEB_TLS = (
|
|
|
+ b"HTTP/1.1 400 OK\r\n"
|
|
|
+ b"Server: nginx/1.21.0\r\n"
|
|
|
+ b"Content-Type: text/html\r\n"
|
|
|
+ b"Connection: close\r\n\r\n"
|
|
|
+ b"<html><body><center><h1>400 Bad Request</h1></center></body></html>\r\n"
|
|
|
+)
|
|
|
+
|
|
|
+# --- MENSAJES ROTATIVOS ---
|
|
|
+MENSAJES = [
|
|
|
+ "🚀 CONEXION ESTABLECIDA",
|
|
|
+ "🛡️ CIFRADO MILITAR ACTIVO",
|
|
|
+ "🔋 MODO SIGILO SSL OK",
|
|
|
+ "Pfsense",
|
|
|
+ "OPNsense",
|
|
|
+ "VyOS",
|
|
|
+ "Claro",
|
|
|
+ "Google",
|
|
|
+ "TNSR",
|
|
|
+ "🌐 BYPASS DE FIREWALL OK"
|
|
|
+]
|
|
|
+msg_idx = 0
|
|
|
+msg_lock = threading.Lock()
|
|
|
+
|
|
|
+
|
|
|
+# --- FUNCIONES DE REGISTRO (LOGS) ---
|
|
|
+def write_log(ip, proto, msg):
|
|
|
+ try:
|
|
|
+ # Limpiar la etiqueta de IPv4-mapped-IPv6 para que el log se vea limpio
|
|
|
+ if ip and ip.startswith("::ffff:"):
|
|
|
+ ip = ip.replace("::ffff:", "")
|
|
|
+
|
|
|
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
+ log_line = f"[{timestamp}] [{proto}] [{ip if ip else 'SISTEMA'}] {msg}"
|
|
|
+
|
|
|
+ print(log_line)
|
|
|
+ with open(LOG_FILE, "a") as f:
|
|
|
+ f.write(log_line + "\n")
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+# --- MOTOR ANTI-FLOOD (AUTO-BAN) ---
|
|
|
+def check_and_update_ip(ip):
|
|
|
+ global ip_database
|
|
|
+ now = time.time()
|
|
|
+
|
|
|
+ with db_lock:
|
|
|
+ # Limpieza periódica de IPs viejas para no saturar la RAM
|
|
|
+ if len(ip_database) > 1000:
|
|
|
+ ip_database = {k: v for k, v in ip_database.items() if v["ban_until"] > now or (now - v["last_connect"] < 60)}
|
|
|
+
|
|
|
+ if ip not in ip_database:
|
|
|
+ ip_database[ip] = {"last_connect": now, "strikes": 1, "ban_until": 0}
|
|
|
+ return 1
|
|
|
+
|
|
|
+ record = ip_database[ip]
|
|
|
+
|
|
|
+ # ¿Está baneado?
|
|
|
+ if record["ban_until"] > now:
|
|
|
+ return 0
|
|
|
+
|
|
|
+ # Tolerancia por segundo exacto
|
|
|
+ if int(now) == int(record["last_connect"]):
|
|
|
+ record["strikes"] += 1
|
|
|
+ if record["strikes"] > AUTO_BAN_STRIKES:
|
|
|
+ record["ban_until"] = now + BAN_TIME
|
|
|
+ return -1 # Acaba de ser baneado
|
|
|
+ else:
|
|
|
+ # Nuevo segundo, resetear contador
|
|
|
+ record["strikes"] = 1
|
|
|
+ record["last_connect"] = now
|
|
|
+
|
|
|
+ return 1
|
|
|
+
|
|
|
+
|
|
|
+# --- MANEJADOR PRINCIPAL DE CONEXIONES ---
|
|
|
+def connection_handler(client_sock, client_addr, is_tls):
|
|
|
+ proto_name = "TLS" if is_tls else "TCP"
|
|
|
+ client_ip = client_addr[0]
|
|
|
+
|
|
|
+ # Extraer IP limpia para los logs
|
|
|
+ clean_ip = client_ip.replace("::ffff:", "") if client_ip.startswith("::ffff:") else client_ip
|
|
|
+
|
|
|
+ # 1. Filtro de Seguridad
|
|
|
+ sec_status = check_and_update_ip(client_ip)
|
|
|
+ if sec_status == 0:
|
|
|
+ client_sock.close()
|
|
|
+ return
|
|
|
+ elif sec_status == -1:
|
|
|
+ write_log(clean_ip, proto_name, "⛔ IP Baneada (Intento de Flood bloqueado)")
|
|
|
+ client_sock.close()
|
|
|
+ return
|
|
|
+
|
|
|
+ # 2. Envoltura SSL (Solo para el puerto 443)
|
|
|
+ if is_tls:
|
|
|
+ try:
|
|
|
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
|
+ context.load_cert_chain(certfile=CERT_FILE, keyfile=KEY_FILE)
|
|
|
+ client_sock.settimeout(5.0) # Tiempo máximo para el handshake
|
|
|
+ client_sock = context.wrap_socket(client_sock, server_side=True)
|
|
|
+ except ssl.SSLError:
|
|
|
+ # Probablemente un escáner escaneando puertos SSL con texto plano
|
|
|
+ client_sock.close()
|
|
|
+ return
|
|
|
+ except Exception as e:
|
|
|
+ client_sock.close()
|
|
|
+ return
|
|
|
+
|
|
|
+ # 3. Lectura del Payload (Con Timeout para soportar NetMod)
|
|
|
+ client_sock.settimeout(3.0)
|
|
|
+ try:
|
|
|
+ buffer = client_sock.recv(16384)
|
|
|
+ except socket.timeout:
|
|
|
+ buffer = b"" # Túnel Silencioso (NetMod)
|
|
|
+ except Exception:
|
|
|
+ client_sock.close()
|
|
|
+ return
|
|
|
+
|
|
|
+ # 4. Conectar al destino SSH Local
|
|
|
+ try:
|
|
|
+ target_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
+ target_sock.settimeout(5.0)
|
|
|
+ target_sock.connect((SSH_HOST, SSH_PORT))
|
|
|
+ except Exception:
|
|
|
+ write_log(clean_ip, proto_name, "❌ Error conectando a servidor SSH interno")
|
|
|
+ client_sock.close()
|
|
|
+ return
|
|
|
+
|
|
|
+ # 5. Lógica de Enrutamiento y Camuflaje
|
|
|
+ if buffer:
|
|
|
+ if buffer.startswith(b"SSH-"):
|
|
|
+ write_log(clean_ip, proto_name, "✅ Túnel Directo SSH")
|
|
|
+ target_sock.sendall(buffer)
|
|
|
+
|
|
|
+ elif b"HTTP/" in buffer and b"Upgrade: websocket" not in buffer:
|
|
|
+ # Es un navegador web o un bot escáner
|
|
|
+ write_log(clean_ip, proto_name, "🕵️ Escáner detectado. Respondiendo Fake Web (400 OK).")
|
|
|
+ fake_resp = FAKE_WEB_TLS if is_tls else FAKE_WEB_TCP
|
|
|
+ client_sock.sendall(fake_resp)
|
|
|
+ client_sock.close()
|
|
|
+ target_sock.close()
|
|
|
+ return
|
|
|
+
|
|
|
+ else:
|
|
|
+ # Es una petición de inyección (NetMod / HTTP Custom)
|
|
|
+ global msg_idx
|
|
|
+ with msg_lock:
|
|
|
+ status_msg = MENSAJES[msg_idx]
|
|
|
+ msg_idx = (msg_idx + 1) % len(MENSAJES)
|
|
|
+
|
|
|
+ srv_header = "nginx/1.21.0" if is_tls else "nginx/1.24.0"
|
|
|
+ agent_header = "Gemini-Ultimate-Python-TLS" if is_tls else "Gemini-Ultimate-Python-TCP"
|
|
|
+
|
|
|
+ response = (
|
|
|
+ f"HTTP/1.1 101 {status_msg}\r\n"
|
|
|
+ f"Server: {srv_header}\r\n"
|
|
|
+ f"X-Forwarded-For: 127.0.0.1\r\n"
|
|
|
+ f"Content-Type: text/html; charset=UTF-8\r\n"
|
|
|
+ f"Proxy-Connection: keep-alive\r\n"
|
|
|
+ f"Cache-Control: no-cache\r\n"
|
|
|
+ f"X-Proxy-Agent: {agent_header}\r\n"
|
|
|
+ f"Connection: Upgrade\r\n"
|
|
|
+ f"Upgrade: websocket\r\n\r\n"
|
|
|
+ )
|
|
|
+
|
|
|
+ client_sock.sendall(response.encode('utf-8'))
|
|
|
+ write_log(clean_ip, proto_name, f"✅ Túnel Inyectado Establecido: {status_msg}")
|
|
|
+
|
|
|
+ # Reenviar si el cliente mandó carga SSH extra junto con el Header HTTP
|
|
|
+ partes = buffer.split(b"\r\n\r\n", 1)
|
|
|
+ if len(partes) > 1 and partes[1]:
|
|
|
+ target_sock.sendall(partes[1])
|
|
|
+ else:
|
|
|
+ # Buffer vacío (Timeout)
|
|
|
+ write_log(clean_ip, proto_name, "✅ Túnel Modo Silencioso (Stunnel)")
|
|
|
+
|
|
|
+ # 6. Bucle Multiplexor (El Puente de Red)
|
|
|
+ client_sock.settimeout(None)
|
|
|
+ target_sock.settimeout(None)
|
|
|
+ sockets = [client_sock, target_sock]
|
|
|
+
|
|
|
+ tx_bytes = 0
|
|
|
+ rx_bytes = 0
|
|
|
+
|
|
|
+ try:
|
|
|
+ while True:
|
|
|
+ r, _, _ = select.select(sockets, [], [], 300) # 5 minutos de inactividad max
|
|
|
+ if not r:
|
|
|
+ break
|
|
|
+
|
|
|
+ if client_sock in r:
|
|
|
+ data = client_sock.recv(16384)
|
|
|
+ if not data:
|
|
|
+ break
|
|
|
+ target_sock.sendall(data)
|
|
|
+ rx_bytes += len(data)
|
|
|
+
|
|
|
+ if target_sock in r:
|
|
|
+ data = target_sock.recv(16384)
|
|
|
+ if not data:
|
|
|
+ break
|
|
|
+ client_sock.sendall(data)
|
|
|
+ tx_bytes += len(data)
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+ finally:
|
|
|
+ total_mb = (tx_bytes + rx_bytes) / (1024 * 1024)
|
|
|
+ if total_mb > 0.05:
|
|
|
+ write_log(clean_ip, proto_name, f"[*] Conexión finalizada. Tráfico: {total_mb:.2f} MB")
|
|
|
+ client_sock.close()
|
|
|
+ target_sock.close()
|
|
|
+
|
|
|
+
|
|
|
+# --- SERVIDORES DE ESCUCHA (DUAL-STACK IPv4 e IPv6) ---
|
|
|
+def server_listener(port, is_tls):
|
|
|
+ proto_name = "TLS" if is_tls else "TCP"
|
|
|
+ try:
|
|
|
+ # MAGIA DUAL-STACK: AF_INET6 con IPV6_V6ONLY en 0 escucha IPv4 e IPv6 simultáneamente
|
|
|
+ server = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
|
|
+ server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
+
|
|
|
+ # Desactivar exclusividad IPv6 (Permite mapear IPv4 a IPv6)
|
|
|
+ try:
|
|
|
+ server.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
|
|
+ except AttributeError:
|
|
|
+ pass # Si el sistema no lo soporta, sigue adelante
|
|
|
+
|
|
|
+ server.bind(("", port))
|
|
|
+ server.listen(500)
|
|
|
+
|
|
|
+ write_log(None, proto_name, f"Escuchando en puerto {port} (IPv4 e IPv6)...")
|
|
|
+
|
|
|
+ while True:
|
|
|
+ client_sock, client_addr = server.accept()
|
|
|
+ # Desplegar un hilo por cada cliente conectado
|
|
|
+ t = threading.Thread(target=connection_handler, args=(client_sock, client_addr, is_tls))
|
|
|
+ t.daemon = True
|
|
|
+ t.start()
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ write_log(None, "SISTEMA", f"❌ Error fatal en puerto {port}: {e}")
|
|
|
+ sys.exit(1)
|
|
|
+
|
|
|
+
|
|
|
+# --- HILO MAESTRO ORQUESTADOR ---
|
|
|
+if __name__ == "__main__":
|
|
|
+ # Ignorar errores de "Broken Pipe" que crashean los servidores
|
|
|
+ if hasattr(signal, 'SIGPIPE'):
|
|
|
+ signal.signal(signal.SIGPIPE, signal.SIG_IGN)
|
|
|
+
|
|
|
+ if not os.path.exists(CERT_FILE) or not os.path.exists(KEY_FILE):
|
|
|
+ write_log(None, "SISTEMA", f"⚠️ ADVERTENCIA: No se encontraron los certificados en {CERT_FILE}. TLS fallará.")
|
|
|
+
|
|
|
+ write_log(None, "SISTEMA", "=========================================================")
|
|
|
+ write_log(None, "SISTEMA", "🚀 PROXY DUAL PYTHON INICIADO (Ultimate Edition)")
|
|
|
+ write_log(None, "SISTEMA", "🛡️ IPv4/IPv6 Dual-Stack | Anti-Flood RAM | Fake Web Nginx")
|
|
|
+ write_log(None, "SISTEMA", "=========================================================")
|
|
|
+
|
|
|
+ # Lanzar servidor TCP (Puerto 80)
|
|
|
+ t_tcp = threading.Thread(target=server_listener, args=(PORT_TCP, False))
|
|
|
+ t_tcp.daemon = True
|
|
|
+ t_tcp.start()
|
|
|
+
|
|
|
+ # Lanzar servidor TLS (Puerto 443)
|
|
|
+ t_tls = threading.Thread(target=server_listener, args=(PORT_TLS, True))
|
|
|
+ t_tls.daemon = True
|
|
|
+ t_tls.start()
|
|
|
+
|
|
|
+ # Mantener el script vivo infinitamente
|
|
|
+ try:
|
|
|
+ while True:
|
|
|
+ time.sleep(86400)
|
|
|
+ except KeyboardInterrupt:
|
|
|
+ write_log(None, "SISTEMA", "Apagando el servidor Proxy Dual...")
|
|
|
+ sys.exit(0)
|