remove trash
This commit is contained in:
@@ -9,13 +9,8 @@ SECRET_TOKEN=asdasdasd
|
||||
# Services Directory (where managed services will be stored)
|
||||
SERVICES_DIR=/root/services
|
||||
|
||||
# Scoreboard Configuration (ForcAD uses Socket.IO)
|
||||
# Scoreboard Configuration (ForcAD Socket.IO)
|
||||
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
|
||||
ALERT_THRESHOLD_POINTS=100
|
||||
ALERT_THRESHOLD_TIME=300
|
||||
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
networks:
|
||||
- adctrl-network
|
||||
|
||||
# Scoreboard Injector - monitors attacks
|
||||
# Scoreboard Injector - monitors attacks via Socket.IO
|
||||
scoreboard-injector:
|
||||
build: ./scoreboard_injector
|
||||
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}
|
||||
SECRET_TOKEN: ${SECRET_TOKEN}
|
||||
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}
|
||||
ALERT_THRESHOLD_POINTS: ${ALERT_THRESHOLD_POINTS:-100}
|
||||
ALERT_THRESHOLD_TIME: ${ALERT_THRESHOLD_TIME:-300}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"""
|
||||
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 json
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, Any
|
||||
import aiohttp
|
||||
import socketio
|
||||
from fastapi import FastAPI, HTTPException, Depends, Header
|
||||
from pydantic import BaseModel
|
||||
@@ -17,12 +16,7 @@ from contextlib import asynccontextmanager
|
||||
# Configuration
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://adctrl:adctrl@postgres:5432/adctrl")
|
||||
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_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
|
||||
SCOREBOARD_URL = os.getenv("SCOREBOARD_URL", "http://10.60.0.1:8080")
|
||||
OUR_TEAM_ID = int(os.getenv("OUR_TEAM_ID", "1"))
|
||||
ALERT_THRESHOLD_POINTS = float(os.getenv("ALERT_THRESHOLD_POINTS", "100"))
|
||||
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):
|
||||
"""Send alert to telegram bot"""
|
||||
import aiohttp
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
@@ -190,91 +185,6 @@ async def send_telegram_alert(message: str):
|
||||
except Exception as 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():
|
||||
"""Listen to ForcAD scoreboard using Socket.IO"""
|
||||
sio = socketio.AsyncClient(logger=False, engineio_logger=False)
|
||||
@@ -340,87 +250,17 @@ async def socketio_listener():
|
||||
print("Reconnecting in 5 seconds...")
|
||||
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
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
global db_pool, ws_task
|
||||
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}")
|
||||
|
||||
# Start listener based on configuration
|
||||
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}")
|
||||
ws_task = asyncio.create_task(socketio_listener())
|
||||
|
||||
yield
|
||||
|
||||
@@ -443,8 +283,8 @@ async def health_check():
|
||||
"status": "ok",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"team_id": OUR_TEAM_ID,
|
||||
"mode": "http_polling" if USE_HTTP_POLLING else "websocket",
|
||||
"scoreboard_url": SCOREBOARD_API_URL if USE_HTTP_POLLING else SCOREBOARD_WS_URL
|
||||
"mode": "socketio",
|
||||
"scoreboard_url": SCOREBOARD_URL
|
||||
}
|
||||
|
||||
@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)])
|
||||
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 = {
|
||||
"mode": "http_polling" if USE_HTTP_POLLING else "websocket",
|
||||
"mode": "socketio",
|
||||
"config": {
|
||||
"scoreboard_url": SCOREBOARD_URL,
|
||||
"our_team_id": OUR_TEAM_ID
|
||||
},
|
||||
"endpoints_tested": []
|
||||
}
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# Test WebSocket
|
||||
results["websocket_url"] = SCOREBOARD_WS_URL
|
||||
# Test Socket.IO endpoint
|
||||
socketio_url = f"{SCOREBOARD_URL}/socket.io/?EIO=4&transport=polling"
|
||||
try:
|
||||
async with session.ws_connect(SCOREBOARD_WS_URL, timeout=aiohttp.ClientTimeout(total=5)) as ws:
|
||||
results["websocket_status"] = "reachable"
|
||||
async with session.get(socketio_url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
|
||||
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:
|
||||
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
|
||||
base_url = SCOREBOARD_API_URL.rstrip('/api').rstrip('/')
|
||||
endpoints = [
|
||||
f"{base_url}/flags",
|
||||
f"{base_url}/stolen_flags",
|
||||
f"{base_url}/flag_stats",
|
||||
f"{base_url}/game_state",
|
||||
f"{base_url}/scoreboard",
|
||||
f"{base_url}/api/flags/",
|
||||
f"{base_url}/api/stolen_flags/",
|
||||
f"{base_url}/api/game_state/",
|
||||
f"{base_url}/api/client/attack_data",
|
||||
]
|
||||
# Test base scoreboard URL
|
||||
try:
|
||||
async with session.get(SCOREBOARD_URL, timeout=aiohttp.ClientTimeout(total=5)) as resp:
|
||||
results["base_url_status"] = {
|
||||
"url": SCOREBOARD_URL,
|
||||
"status": resp.status,
|
||||
"reachable": resp.status == 200
|
||||
}
|
||||
except Exception as e:
|
||||
results["base_url_status"] = {
|
||||
"url": SCOREBOARD_URL,
|
||||
"reachable": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
for endpoint in endpoints:
|
||||
try:
|
||||
async with session.get(endpoint, timeout=aiohttp.ClientTimeout(total=5)) as resp:
|
||||
content_type = resp.headers.get('Content-Type', '')
|
||||
is_json = 'application/json' in content_type
|
||||
is_html = 'text/html' in content_type
|
||||
|
||||
result = {
|
||||
"url": endpoint,
|
||||
"status": resp.status,
|
||||
"reachable": resp.status == 200,
|
||||
"content_type": content_type,
|
||||
"is_json": is_json,
|
||||
"is_html": is_html
|
||||
}
|
||||
|
||||
if resp.status == 200:
|
||||
if is_json:
|
||||
try:
|
||||
data = await resp.json()
|
||||
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)
|
||||
})
|
||||
# Test attack_data endpoint (for reference only)
|
||||
attack_data_url = f"{SCOREBOARD_URL}/api/client/attack_data"
|
||||
try:
|
||||
async with session.get(attack_data_url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
|
||||
result = {
|
||||
"url": attack_data_url,
|
||||
"status": resp.status,
|
||||
"reachable": resp.status == 200,
|
||||
"content_type": resp.headers.get('Content-Type', ''),
|
||||
"note": "Contains exploit credentials, not attack events"
|
||||
}
|
||||
if resp.status == 200 and 'application/json' in resp.headers.get('Content-Type', ''):
|
||||
data = await resp.json()
|
||||
result["services"] = list(data.keys()) if isinstance(data, dict) else None
|
||||
results["endpoints_tested"].append(result)
|
||||
except Exception as e:
|
||||
results["endpoints_tested"].append({
|
||||
"url": attack_data_url,
|
||||
"reachable": False,
|
||||
"error": str(e)
|
||||
})
|
||||
except Exception as e:
|
||||
results["error"] = str(e)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user