Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
@@ -0,0 +1,360 @@
<?php
// เปิดโชว์ Error ชั่วคราวเพื่อหาสาเหตุ 500
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../vendor/autoload.php';
use Bramus\Router\Router;
use Dotenv\Dotenv;
use App\Controllers\DashboardController;
use App\Controllers\WazuhWebhookController;
// Load environment variables
if (file_exists(__DIR__ . '/../.env')) {
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
}
require_once __DIR__ . '/../src/Engine/Language.php';
// Handle Language Switch
if (isset($_GET['lang'])) {
$lang = in_array($_GET['lang'], ['en', 'th']) ? $_GET['lang'] : 'en';
setcookie('lang', $lang, time() + (86400 * 365), "/");
$redirect = strtok($_SERVER["REQUEST_URI"], '?');
header("Location: " . $redirect);
exit;
}
// Initialize Router
$router = new Router();
// Set base path dynamically to support subfolders (e.g. /ksh_soar)
$basePath = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));
$basePath = str_replace('/public', '', $basePath);
if ($basePath === '/') {
$basePath = '';
}
$router->setBasePath($basePath);
// API / Webhook Routes
$router->mount('/api', function() use ($router) {
$router->post('/webhook/wazuh', function() {
$controller = new WazuhWebhookController();
$controller->handle();
});
$router->mount('/agent', function() use ($router) {
$router->get('/specs', function() {
$controller = new \App\Controllers\AgentController();
$controller->getSpecs();
});
$router->get('/quick-details', function() {
$controller = new \App\Controllers\AgentController();
$controller->getEndpointQuickDetails();
});
$router->get('/network', function() {
$controller = new \App\Controllers\AgentController();
$controller->getNetworkInspector();
});
$router->get('/processes', function() {
$controller = new \App\Controllers\AgentController();
$controller->getProcesses();
});
$router->get('/all-metadata', function() {
$controller = new \App\Controllers\AgentController();
$controller->getAllAgentsMetadata();
});
});
$router->post('/test-notification', function() {
require_once __DIR__ . '/../src/Integrations/Notifier.php';
header('Content-Type: application/json');
$type = $_POST['type'] ?? '';
$msg = "🛠️ [Test Message] This is a test notification from KSH SOAR.";
$success = false;
if ($type === 'line') {
if (empty($_ENV['LINE_NOTIFY_TOKEN'])) {
echo json_encode(['status' => 'error', 'message' => 'LINE_NOTIFY_TOKEN is not set in .env']);
return;
}
$success = \App\Integrations\Notifier::sendLineNotify($msg);
} else if ($type === 'telegram') {
if (empty($_ENV['TELEGRAM_BOT_TOKEN']) || empty($_ENV['TELEGRAM_CHAT_ID'])) {
echo json_encode(['status' => 'error', 'message' => 'Telegram tokens are not set in .env']);
return;
}
$appName = $_ENV['APP_NAME'] ?? 'KSH SOAR';
$tgMsg = "🧪 <b>[{$appName}] MOCKUP SECURITY ALERT</b>\n";
$tgMsg .= "━━━━━━━━━━━━━━━━━━━━\n";
$tgMsg .= "⚠️ <b>Risk Level:</b> <code>12</code> (High)\n";
$tgMsg .= "🆔 <b>Rule ID:</b> <code>5716</code>\n";
$tgMsg .= "🖥 <b>Agent:</b> <code>WEB-SERVER-01</code>\n";
$tgMsg .= "🌐 <b>IP Address:</b> <code>192.168.1.100</code>\n";
$tgMsg .= "━━━━━━━━━━━━━━━━━━━━\n";
$tgMsg .= "📝 <b>Description:</b>\n<i>เข้าสู่ระบบล้มเหลวหลายครั้งติดต่อกัน (อาจมีการ Brute-force)</i>\n\n";
$tgMsg .= "📍 <b>Action Taken:</b>\n";
$tgMsg .= "✅ Auto-created Investigation Case <b>#105</b>\n";
$tgMsg .= "🤖 AI Remediation Analysis queued.\n\n";
$tgMsg .= "✅ <b>System Integration Test Successful!</b>";
$success = \App\Integrations\Notifier::sendTelegram($tgMsg);
} else {
echo json_encode(['status' => 'error', 'message' => 'Invalid type']);
return;
}
if ($success) {
echo json_encode(['status' => 'success', 'message' => ucfirst($type) . ' notification sent successfully!']);
} else {
echo json_encode(['status' => 'error', 'message' => 'Failed to send notification. Please check your tokens.']);
}
});
$router->post('/clear-all-alerts', function() {
$controller = new DashboardController();
$controller->clearAllAlerts();
});
$router->post('/virustotal-check', function() {
header('Content-Type: application/json');
$type = $_POST['type'] ?? '';
$value = $_POST['value'] ?? '';
$apiKey = $_ENV['VIRUSTOTAL_API_KEY'] ?? '';
if (empty($apiKey)) {
echo json_encode(['status' => 'error', 'message' => 'VIRUSTOTAL_API_KEY is not configured in .env']);
return;
}
if (empty($value)) {
echo json_encode(['status' => 'error', 'message' => 'No value provided for scanning.']);
return;
}
$url = "";
if ($type === 'hash') {
$url = "https://www.virustotal.com/api/v3/files/" . urlencode($value);
} else if ($type === 'ip') {
$url = "https://www.virustotal.com/api/v3/ip_addresses/" . urlencode($value);
} else {
echo json_encode(['status' => 'error', 'message' => 'Invalid scan type.']);
return;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-apikey: {$apiKey}"
]);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpcode == 200) {
$data = json_decode($response, true);
$stats = $data['data']['attributes']['last_analysis_stats'] ?? null;
if ($stats) {
$malicious = $stats['malicious'] ?? 0;
$suspicious = $stats['suspicious'] ?? 0;
$total = array_sum($stats);
$html = "<div class='text-sm mt-2'>";
if ($malicious > 0) {
$html .= "<div class='bg-rose-100 text-rose-800 px-3 py-2 rounded font-bold'><i class='fas fa-biohazard mr-2'></i> Malicious: {$malicious} / {$total} engines detected this as a threat!</div>";
} else if ($suspicious > 0) {
$html .= "<div class='bg-amber-100 text-amber-800 px-3 py-2 rounded font-bold'><i class='fas fa-exclamation-triangle mr-2'></i> Suspicious: {$suspicious} / {$total} engines flagged this.</div>";
} else {
$html .= "<div class='bg-emerald-100 text-emerald-800 px-3 py-2 rounded font-bold'><i class='fas fa-shield-alt mr-2'></i> Clean: 0 / {$total} engines detected threats.</div>";
}
$vtLink = $type === 'hash' ? "https://www.virustotal.com/gui/file/{$value}" : "https://www.virustotal.com/gui/ip-address/{$value}";
$html .= "<a href='{$vtLink}' target='_blank' class='block mt-2 text-xs text-blue-600 underline hover:text-blue-800'><i class='fas fa-external-link-alt mr-1'></i> View full report on VirusTotal</a>";
$html .= "</div>";
echo json_encode(['status' => 'success', 'html' => $html]);
} else {
echo json_encode(['status' => 'error', 'message' => 'Could not parse VirusTotal analysis stats.']);
}
} else if ($httpcode == 404) {
echo json_encode(['status' => 'success', 'html' => "<div class='bg-slate-100 text-slate-600 px-3 py-2 rounded text-sm mt-2'><i class='fas fa-search mr-2'></i> No match found on VirusTotal.</div>"]);
} else {
echo json_encode(['status' => 'error', 'message' => "VirusTotal API Error (HTTP {$httpcode})"]);
}
});
$router->post('/ai-remediation', function() {
header('Content-Type: application/json');
$ruleId = $_POST['rule_id'] ?? '';
$ruleDesc = $_POST['rule_desc'] ?? '';
$level = (int)($_POST['level'] ?? 0);
try {
$db = \App\Database\Connection::getInstance();
// Create cache table automatically if it doesn't exist
$db->query("CREATE TABLE IF NOT EXISTS ai_remediation_cache (
rule_id INT PRIMARY KEY,
recommendation TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
$force = isset($_POST['force']) && $_POST['force'] == 'true';
// 1. Check cache first to save API calls
if (!$force) {
$stmt = $db->prepare("SELECT recommendation FROM ai_remediation_cache WHERE rule_id = ?");
$stmt->execute([$ruleId]);
$cachedHtml = $stmt->fetchColumn();
if ($cachedHtml) {
// Return cached response (you can add a small badge to indicate it's from cache if you want)
$cachedHtml .= "<div class='mt-2 text-[10px] text-indigo-400 text-right italic'><i class='fas fa-bolt mr-1'></i> Loaded from Cache</div>";
echo json_encode(['status' => 'success', 'html' => $cachedHtml]);
return;
}
}
// 2. Setup Gemini API request
$apiKey = $_ENV['GEMINI_API_KEY'] ?? '';
if (empty($apiKey)) {
echo json_encode(['status' => 'error', 'message' => 'GEMINI_API_KEY is not configured in .env file.']);
return;
}
$prompt = "You are a senior cybersecurity SOC analyst. Analyze the following Wazuh alert and provide a concise, actionable 3-step remediation guideline.
Rule ID: {$ruleId}
Level: {$level}
Description: {$ruleDesc}
Requirements:
- Answer in Thai language.
- Output ONLY valid HTML: an unordered list <ul> with 3 <li> items. Add 'list-disc pl-4 space-y-2 mt-1' class to the <ul>.
- Wrap keywords in <strong> tags.
- Do NOT wrap the output in markdown code blocks (no ```html).";
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=" . $apiKey;
$payload = [
"contents" => [
["parts" => [["text" => $prompt]]]
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Bypass local cert issues
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200 && $response) {
$data = json_decode($response, true);
$aiText = $data['candidates'][0]['content']['parts'][0]['text'] ?? '';
// Clean up possible markdown tags returned by the model
$aiText = str_replace(['```html', '```'], '', $aiText);
$html = "<div class='text-sm leading-relaxed text-indigo-900'>" . trim($aiText) . "</div>";
// 3. Save to database cache
$stmt = $db->prepare("INSERT INTO ai_remediation_cache (rule_id, recommendation) VALUES (?, ?) ON DUPLICATE KEY UPDATE recommendation = VALUES(recommendation)");
$stmt->execute([$ruleId, $html]);
echo json_encode(['status' => 'success', 'html' => $html]);
} else {
$errorMsg = 'Failed to reach Gemini API (HTTP ' . $httpCode . ')';
if ($response) {
$errData = json_decode($response, true);
if (isset($errData['error']['message'])) $errorMsg = $errData['error']['message'];
}
echo json_encode(['status' => 'error', 'message' => $errorMsg]);
}
} catch (\Exception $e) {
echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
}
});
$router->get('/dashboard/charts', function() {
$controller = new DashboardController();
$controller->getChartData();
});
$router->get('/realtime', function() {
$controller = new DashboardController();
$controller->getRealtimeUpdates();
});
});
// Web Routes
$router->get('/', function() {
$controller = new DashboardController();
$controller->index();
});
$router->get('/ai-history', function() {
$controller = new DashboardController();
$controller->aiHistory();
});
$router->get('/sysmon', function() {
$controller = new \App\Controllers\SysmonController();
$controller->index();
});
$router->post('/clear-alerts', function() {
$controller = new DashboardController();
$controller->clearOldAlerts();
});
$router->get('/search', function() {
$controller = new \App\Controllers\SearchController();
$controller->index();
});
$router->post('/api/search/ssp', function() {
$controller = new \App\Controllers\SearchController();
$controller->ssp();
});
$router->get('/settings', function() {
$controller = new DashboardController();
$controller->settings();
});
$router->get('/agents', function() {
$controller = new \App\Controllers\AgentController();
$controller->index();
});
$router->get('/api/agents/(\d+)/details', function($id) {
$controller = new \App\Controllers\AgentController();
$controller->getDetails($id);
});
$router->get('/api/agents/remote-list', function() {
$controller = new \App\Controllers\AgentController();
$controller->getRemoteList();
});
// 404 Handler
$router->set404(function() {
header('HTTP/1.1 404 Not Found');
echo "404 Not Found";
});
// Run Router
$router->run();