remove trash

This commit is contained in:
ilyastar9999
2025-12-03 11:03:37 +03:00
parent ee3a51e3cb
commit 796c03702b
3 changed files with 67 additions and 236 deletions

View File

@@ -9,13 +9,8 @@ SECRET_TOKEN=asdasdasd
# Services Directory (where managed services will be stored) # Services Directory (where managed services will be stored)
SERVICES_DIR=/root/services SERVICES_DIR=/root/services
# Scoreboard Configuration (ForcAD uses Socket.IO) # Scoreboard Configuration (ForcAD Socket.IO)
SCOREBOARD_URL=http://94.228.113.149:8080 SCOREBOARD_URL=http://94.228.113.149:8080
USE_SOCKETIO=true
USE_HTTP_POLLING=false
SCOREBOARD_WS_URL=ws://10.60.0.1:8080/api/events
SCOREBOARD_API_URL=http://10.60.0.1:8080/api
POLLING_INTERVAL=10
OUR_TEAM_ID=1 OUR_TEAM_ID=1
ALERT_THRESHOLD_POINTS=100 ALERT_THRESHOLD_POINTS=100
ALERT_THRESHOLD_TIME=300 ALERT_THRESHOLD_TIME=300

View File

@@ -41,7 +41,7 @@ services:
networks: networks:
- adctrl-network - adctrl-network
# Scoreboard Injector - monitors attacks # Scoreboard Injector - monitors attacks via Socket.IO
scoreboard-injector: scoreboard-injector:
build: ./scoreboard_injector build: ./scoreboard_injector
container_name: adctrl-scoreboard container_name: adctrl-scoreboard
@@ -49,11 +49,6 @@ services:
DATABASE_URL: postgresql://${POSTGRES_USER:-adctrl}:${POSTGRES_PASSWORD:-adctrl_secure_password}@postgres:5432/${POSTGRES_DB:-adctrl} DATABASE_URL: postgresql://${POSTGRES_USER:-adctrl}:${POSTGRES_PASSWORD:-adctrl_secure_password}@postgres:5432/${POSTGRES_DB:-adctrl}
SECRET_TOKEN: ${SECRET_TOKEN} SECRET_TOKEN: ${SECRET_TOKEN}
SCOREBOARD_URL: ${SCOREBOARD_URL:-http://10.60.0.1:8080} SCOREBOARD_URL: ${SCOREBOARD_URL:-http://10.60.0.1:8080}
USE_SOCKETIO: ${USE_SOCKETIO:-true}
USE_HTTP_POLLING: ${USE_HTTP_POLLING:-false}
SCOREBOARD_WS_URL: ${SCOREBOARD_WS_URL:-ws://10.60.0.1:8080/api/events}
SCOREBOARD_API_URL: ${SCOREBOARD_API_URL:-http://10.60.0.1:8080/api}
POLLING_INTERVAL: ${POLLING_INTERVAL:-10}
OUR_TEAM_ID: ${OUR_TEAM_ID:-1} OUR_TEAM_ID: ${OUR_TEAM_ID:-1}
ALERT_THRESHOLD_POINTS: ${ALERT_THRESHOLD_POINTS:-100} ALERT_THRESHOLD_POINTS: ${ALERT_THRESHOLD_POINTS:-100}
ALERT_THRESHOLD_TIME: ${ALERT_THRESHOLD_TIME:-300} ALERT_THRESHOLD_TIME: ${ALERT_THRESHOLD_TIME:-300}

View File

@@ -1,13 +1,12 @@
""" """
Scoreboard Injector for ForcAD Scoreboard Injector for ForcAD
Monitors WebSocket for attacks and alerts on critical situations Monitors Socket.IO events for attacks and alerts on critical situations
""" """
import os import os
import json import json
import asyncio import asyncio
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
import aiohttp
import socketio import socketio
from fastapi import FastAPI, HTTPException, Depends, Header from fastapi import FastAPI, HTTPException, Depends, Header
from pydantic import BaseModel from pydantic import BaseModel
@@ -17,12 +16,7 @@ from contextlib import asynccontextmanager
# Configuration # Configuration
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://adctrl:adctrl@postgres:5432/adctrl") DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://adctrl:adctrl@postgres:5432/adctrl")
SECRET_TOKEN = os.getenv("SECRET_TOKEN", "change-me-in-production") SECRET_TOKEN = os.getenv("SECRET_TOKEN", "change-me-in-production")
SCOREBOARD_URL = os.getenv("SCOREBOARD_URL", "http://10.60.0.1:8080") # Base URL for Socket.IO SCOREBOARD_URL = os.getenv("SCOREBOARD_URL", "http://10.60.0.1:8080")
SCOREBOARD_WS_URL = os.getenv("SCOREBOARD_WS_URL", "ws://scoreboard:8080/api/events")
SCOREBOARD_API_URL = os.getenv("SCOREBOARD_API_URL", "http://10.60.0.1:8080/api")
USE_HTTP_POLLING = os.getenv("USE_HTTP_POLLING", "false").lower() == "true"
USE_SOCKETIO = os.getenv("USE_SOCKETIO", "true").lower() == "true" # Use Socket.IO by default
POLLING_INTERVAL = int(os.getenv("POLLING_INTERVAL", "10")) # seconds
OUR_TEAM_ID = int(os.getenv("OUR_TEAM_ID", "1")) OUR_TEAM_ID = int(os.getenv("OUR_TEAM_ID", "1"))
ALERT_THRESHOLD_POINTS = float(os.getenv("ALERT_THRESHOLD_POINTS", "100")) ALERT_THRESHOLD_POINTS = float(os.getenv("ALERT_THRESHOLD_POINTS", "100"))
ALERT_THRESHOLD_TIME = int(os.getenv("ALERT_THRESHOLD_TIME", "300")) # seconds ALERT_THRESHOLD_TIME = int(os.getenv("ALERT_THRESHOLD_TIME", "300")) # seconds
@@ -178,6 +172,7 @@ async def check_and_create_alerts(conn, attacker_id: int, service_name: str):
async def send_telegram_alert(message: str): async def send_telegram_alert(message: str):
"""Send alert to telegram bot""" """Send alert to telegram bot"""
import aiohttp
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.post( async with session.post(
@@ -190,91 +185,6 @@ async def send_telegram_alert(message: str):
except Exception as e: except Exception as e:
print(f"Error sending telegram alert: {e}") print(f"Error sending telegram alert: {e}")
async def http_polling_listener():
"""Poll scoreboard HTTP API for attacks as alternative to WebSocket"""
last_check_time = datetime.utcnow()
while True:
try:
async with aiohttp.ClientSession() as session:
# ForcAD real API endpoints (not Vue.js router paths)
# These should be accessed without /api prefix or with different structure
base_url = SCOREBOARD_API_URL.rstrip('/api').rstrip('/')
endpoints_to_try = [
# Try direct API access patterns
(f'{base_url}/flags', 'GET'),
(f'{base_url}/stolen_flags', 'GET'),
(f'{base_url}/flag_stats', 'GET'),
(f'{base_url}/game_state', 'GET'),
(f'{base_url}/scoreboard', 'GET'),
# Try with /api/ prefix variations
(f'{base_url}/api/flags/', 'GET'),
(f'{base_url}/api/stolen_flags/', 'GET'),
(f'{base_url}/api/game_state/', 'GET'),
]
data_found = False
for endpoint, method in endpoints_to_try:
try:
async with session.get(endpoint, timeout=aiohttp.ClientTimeout(total=5)) as resp:
if resp.status == 200:
content_type = resp.headers.get('Content-Type', '')
# Skip HTML responses
if 'text/html' in content_type:
continue
try:
data = await resp.json()
print(f"✓ Fetched JSON from {endpoint}")
data_found = True
# Process based on response structure
if isinstance(data, list):
# List of attacks/events
for item in data:
if isinstance(item, dict):
await process_attack_event(item)
elif isinstance(data, dict):
# Try common keys for attack data
for key in ['attacks', 'stolen_flags', 'flags', 'events', 'data', 'rows']:
if key in data:
items = data[key]
if isinstance(items, list):
print(f" Found {len(items)} items in '{key}'")
for item in items:
if isinstance(item, dict):
await process_attack_event(item)
break
else:
# Might be a single event or game state
# Check if it contains attack-like data
if any(k in data for k in ['attacker_id', 'victim_id', 'team_id', 'flag']):
await process_attack_event(data)
if data_found:
break # Found valid data, no need to try other endpoints
except (json.JSONDecodeError, aiohttp.ContentTypeError):
# Not JSON, skip
continue
except aiohttp.ClientError as e:
continue
except Exception as e:
print(f"Error fetching {endpoint}: {e}")
continue
if not data_found:
print(f"No valid JSON data found from scoreboard API (last check: {datetime.utcnow().strftime('%H:%M:%S')})")
except Exception as e:
print(f"HTTP polling error: {e}")
await asyncio.sleep(POLLING_INTERVAL)
async def socketio_listener(): async def socketio_listener():
"""Listen to ForcAD scoreboard using Socket.IO""" """Listen to ForcAD scoreboard using Socket.IO"""
sio = socketio.AsyncClient(logger=False, engineio_logger=False) sio = socketio.AsyncClient(logger=False, engineio_logger=False)
@@ -340,87 +250,17 @@ async def socketio_listener():
print("Reconnecting in 5 seconds...") print("Reconnecting in 5 seconds...")
await asyncio.sleep(5) await asyncio.sleep(5)
async def websocket_listener():
"""Listen to scoreboard WebSocket for events"""
reconnect_delay = 5
max_reconnect_delay = 60
while True:
try:
print(f"Attempting to connect to scoreboard WebSocket: {SCOREBOARD_WS_URL}")
async with aiohttp.ClientSession() as session:
try:
async with session.ws_connect(
SCOREBOARD_WS_URL,
timeout=aiohttp.ClientTimeout(total=10)
) as ws:
print(f"✓ Connected to scoreboard WebSocket")
reconnect_delay = 5 # Reset delay on successful connection
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
try:
event = json.loads(msg.data)
event_type = event.get('type', 'unknown')
# Process attack-related events
# ForcAD can send: 'attack', 'flag_stolen', 'stolen_flag', 'flag_submit'
if event_type in ['attack', 'flag_stolen', 'stolen_flag', 'flag_submit', 'flag']:
await process_attack_event(event)
# Optionally log other event types for debugging
# else:
# print(f"Received event type: {event_type}")
except json.JSONDecodeError as e:
print(f"Failed to decode WebSocket message: {msg.data[:200]}")
except Exception as e:
print(f"Error processing event: {e}")
elif msg.type == aiohttp.WSMsgType.CLOSED:
print("WebSocket connection closed by server")
break
elif msg.type == aiohttp.WSMsgType.ERROR:
print(f"WebSocket error: {ws.exception()}")
break
except aiohttp.ClientError as e:
print(f"WebSocket client error: {e}")
except asyncio.TimeoutError:
print(f"Connection timeout to {SCOREBOARD_WS_URL}")
except Exception as e:
print(f"WebSocket connection error: {e}")
print(f"Reconnecting in {reconnect_delay} seconds...")
await asyncio.sleep(reconnect_delay)
# Exponential backoff
reconnect_delay = min(reconnect_delay * 1.5, max_reconnect_delay)
# Lifespan context # Lifespan context
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
global db_pool, ws_task global db_pool, ws_task
db_pool = await asyncpg.create_pool(DATABASE_URL, min_size=2, max_size=10) db_pool = await asyncpg.create_pool(DATABASE_URL, min_size=2, max_size=10)
print(f"Starting Socket.IO listener")
print(f"Scoreboard URL: {SCOREBOARD_URL}")
print(f"Our team ID: {OUR_TEAM_ID}") print(f"Our team ID: {OUR_TEAM_ID}")
# Start listener based on configuration ws_task = asyncio.create_task(socketio_listener())
if USE_SOCKETIO:
print(f"Starting Socket.IO mode")
print(f"Scoreboard URL: {SCOREBOARD_URL}")
ws_task = asyncio.create_task(socketio_listener())
elif USE_HTTP_POLLING:
print(f"Starting HTTP polling mode (interval: {POLLING_INTERVAL}s)")
print(f"Scoreboard API: {SCOREBOARD_API_URL}")
ws_task = asyncio.create_task(http_polling_listener())
else:
print(f"Starting WebSocket listener mode")
print(f"Scoreboard WS: {SCOREBOARD_WS_URL}")
ws_task = asyncio.create_task(websocket_listener())
print(f"Our team ID: {OUR_TEAM_ID}")
yield yield
@@ -443,8 +283,8 @@ async def health_check():
"status": "ok", "status": "ok",
"timestamp": datetime.utcnow().isoformat(), "timestamp": datetime.utcnow().isoformat(),
"team_id": OUR_TEAM_ID, "team_id": OUR_TEAM_ID,
"mode": "http_polling" if USE_HTTP_POLLING else "websocket", "mode": "socketio",
"scoreboard_url": SCOREBOARD_API_URL if USE_HTTP_POLLING else SCOREBOARD_WS_URL "scoreboard_url": SCOREBOARD_URL
} }
@app.get("/stats", dependencies=[Depends(verify_token)]) @app.get("/stats", dependencies=[Depends(verify_token)])
@@ -588,72 +428,73 @@ async def inject_test_attack(attacker_id: int, victim_id: int, service: str = "t
@app.get("/debug/scoreboard", dependencies=[Depends(verify_token)]) @app.get("/debug/scoreboard", dependencies=[Depends(verify_token)])
async def debug_scoreboard(): async def debug_scoreboard():
"""Check if scoreboard is reachable and show raw data""" """Check if scoreboard is reachable and show connection info"""
import aiohttp
results = { results = {
"mode": "http_polling" if USE_HTTP_POLLING else "websocket", "mode": "socketio",
"config": {
"scoreboard_url": SCOREBOARD_URL,
"our_team_id": OUR_TEAM_ID
},
"endpoints_tested": [] "endpoints_tested": []
} }
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
# Test WebSocket # Test Socket.IO endpoint
results["websocket_url"] = SCOREBOARD_WS_URL socketio_url = f"{SCOREBOARD_URL}/socket.io/?EIO=4&transport=polling"
try: try:
async with session.ws_connect(SCOREBOARD_WS_URL, timeout=aiohttp.ClientTimeout(total=5)) as ws: async with session.get(socketio_url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
results["websocket_status"] = "reachable" results["socketio_status"] = {
"url": socketio_url,
"status": resp.status,
"reachable": resp.status == 200,
"response_preview": (await resp.text())[:200] if resp.status == 200 else None
}
except Exception as e: except Exception as e:
results["websocket_status"] = f"unreachable: {str(e)}" results["socketio_status"] = {
"url": socketio_url,
"reachable": False,
"error": str(e)
}
# Test HTTP endpoints - both HTML and API paths # Test base scoreboard URL
base_url = SCOREBOARD_API_URL.rstrip('/api').rstrip('/') try:
endpoints = [ async with session.get(SCOREBOARD_URL, timeout=aiohttp.ClientTimeout(total=5)) as resp:
f"{base_url}/flags", results["base_url_status"] = {
f"{base_url}/stolen_flags", "url": SCOREBOARD_URL,
f"{base_url}/flag_stats", "status": resp.status,
f"{base_url}/game_state", "reachable": resp.status == 200
f"{base_url}/scoreboard", }
f"{base_url}/api/flags/", except Exception as e:
f"{base_url}/api/stolen_flags/", results["base_url_status"] = {
f"{base_url}/api/game_state/", "url": SCOREBOARD_URL,
f"{base_url}/api/client/attack_data", "reachable": False,
] "error": str(e)
}
for endpoint in endpoints: # Test attack_data endpoint (for reference only)
try: attack_data_url = f"{SCOREBOARD_URL}/api/client/attack_data"
async with session.get(endpoint, timeout=aiohttp.ClientTimeout(total=5)) as resp: try:
content_type = resp.headers.get('Content-Type', '') async with session.get(attack_data_url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
is_json = 'application/json' in content_type result = {
is_html = 'text/html' in content_type "url": attack_data_url,
"status": resp.status,
result = { "reachable": resp.status == 200,
"url": endpoint, "content_type": resp.headers.get('Content-Type', ''),
"status": resp.status, "note": "Contains exploit credentials, not attack events"
"reachable": resp.status == 200, }
"content_type": content_type, if resp.status == 200 and 'application/json' in resp.headers.get('Content-Type', ''):
"is_json": is_json, data = await resp.json()
"is_html": is_html result["services"] = list(data.keys()) if isinstance(data, dict) else None
} results["endpoints_tested"].append(result)
except Exception as e:
if resp.status == 200: results["endpoints_tested"].append({
if is_json: "url": attack_data_url,
try: "reachable": False,
data = await resp.json() "error": str(e)
result["json_preview"] = str(data)[:500] })
result["json_keys"] = list(data.keys()) if isinstance(data, dict) else "list"
except:
pass
else:
text = await resp.text()
result["data_preview"] = text[:200]
results["endpoints_tested"].append(result)
except Exception as e:
results["endpoints_tested"].append({
"url": endpoint,
"reachable": False,
"error": str(e)
})
except Exception as e: except Exception as e:
results["error"] = str(e) results["error"] = str(e)