<?php if (!defined('DASHBOARD_ROOT')) { http_response_code(403); exit; } class OSINTEnrichment { private const CACHE_TTL = 86400; public static function enrich(string $ip): array { $cached = self::getCache($ip); if ($cached) return $cached; $result = [ 'ip' => $ip, 'enriched_at' => date('Y-m-d H:i:s'), 'abuseipdb' => self::queryAbuseIPDB($ip), 'geoip' => self::geoIP($ip), 'tor_exit' => self::isTorExit($ip), 'vpn_proxy' => self::isVPNProxy($ip), ]; self::setCache($ip, $result); return $result; } private static function queryAbuseIPDB(string $ip): array { $apiKey = ''; try { $secrets = ServiceSecrets::get('abuseipdb'); $apiKey = $secrets['api_key'] ?? ''; } catch (\Throwable $e) {} if (!$apiKey) return ['available' => false]; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://api.abuseipdb.com/api/v2/check?ipAddress=' . urlencode($ip), CURLOPT_HTTPHEADER => ['Key: ' . $apiKey, 'Accept: application/json'], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $response = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code !== 200 || !$response) return ['available' => false, 'http_code' => $code]; $data = json_decode($response, true); $d = $data['data'] ?? []; return [ 'available' => true, 'abuse_score' => $d['abuseConfidenceScore'] ?? 0, 'total_reports' => $d['totalReports'] ?? 0, 'country_code' => $d['countryCode'] ?? '', 'isp' => $d['isp'] ?? '', 'usage_type' => $d['usageType'] ?? '', 'is_whitelisted' => $d['isWhitelisted'] ?? false, ]; } private static function geoIP(string $ip): array { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://ipwho.is/' . urlencode($ip), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, ]); $response = curl_exec($ch); curl_close($ch); if (!$response) return ['available' => false]; $data = json_decode($response, true); if (($data['success'] ?? false) !== true) return ['available' => false]; return [ 'available' => true, 'country' => $data['country'] ?? '', 'country_code' => $data['country_code'] ?? '', 'region' => $data['region'] ?? '', 'city' => $data['city'] ?? '', 'lat' => $data['latitude'] ?? 0, 'lon' => $data['longitude'] ?? 0, 'isp' => $data['connection']['isp'] ?? '', 'org' => $data['connection']['org'] ?? '', 'asn' => $data['connection']['asn'] ?? '', ]; } private static function isTorExit(string $ip): bool { $reversed = implode('.', array_reverse(explode('.', $ip))); $lookup = $reversed . '.dnsel.torproject.org'; $result = @dns_get_record($lookup, DNS_A); return !empty($result); } private static function isVPNProxy(string $ip): bool { try { $row = Database::fetchOne( "SELECT COUNT(*) as c FROM blacklist WHERE ip_address = :ip AND reason LIKE '%proxy%'", [':ip' => $ip] ); return $row && (int)$row['c'] > 0; } catch (\Throwable $e) { return false; } } private static function getCache(string $ip): ?array { try { $row = Database::fetchOne( "SELECT data, TIMESTAMPDIFF(SECOND, updated_at, NOW()) as age                  FROM threat_intel_cache WHERE ip_address = :ip LIMIT 1", [':ip' => $ip] ); if ($row && (int)$row['age'] < self::CACHE_TTL) { return json_decode($row['data'], true); } } catch (\Throwable $e) {} return null; } private static function setCache(string $ip, array $data): void { try { Database::execute( "INSERT INTO threat_intel_cache (ip_address, data, updated_at) VALUES (:ip, :d, NOW())                  ON DUPLICATE KEY UPDATE data = :d2, updated_at = NOW()", [':ip' => $ip, ':d' => json_encode($data), ':d2' => json_encode($data)] ); } catch (\Throwable $e) {} } public static function status(): array { try { $cached = (int)Database::scalar("SELECT COUNT(*) FROM threat_intel_cache"); $recent = Database::fetchAll( "SELECT ip_address, updated_at FROM threat_intel_cache ORDER BY updated_at DESC LIMIT 10" ); } catch (\Throwable $e) { $cached = 0; $recent = []; } return ['cached_ips' => $cached, 'recent' => $recent, 'cache_ttl' => self::CACHE_TTL]; } } 