From 6b3b99b0f1584ce99011da883ab132f3a5d6513b Mon Sep 17 00:00:00 2001 From: root Date: Tue, 16 Jun 2026 04:58:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=8D=87=E7=BA=A7=E4=B8=8E?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E4=BF=AE=E5=A4=8D=E6=9C=80=E7=BB=88=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __code__/risk_analyzer_new.php | 0 check_config/indBXSXX_config.php | 0 cong.php | 5 + datatable/auth.php | 0 datatable/fp_explain.php | 0 datatable/log_blob.php | 0 datatable/sdatalist.php | 137 +++++++-- datatable/simulate.php | 0 dist/index.php | 7 +- indBXSXX.php | 0 install.php | 3 + ip_check.php | 5 + jump.txt | 8 + lib/Cloak/CloakJudgmentCache.php | 272 ++++++++++++++++++ lib/Cloak/CloakSession.php | 23 +- lib/Cloak/FCheckHandler.php | 90 +++--- lib/Cloak/Page/FpPageRenderer.php | 10 +- lib/Cloak/Pipeline/CheckPipeline.php | 180 ++++++++++-- lib/Cloak/Pipeline/RouteResolver.php | 58 +++- .../apply_geoip_country_log_only.inc.php | 30 ++ .../stages/apply_whitelist_blacklist.inc.php | 85 ++---- .../resolve_blacklist_fast_path_hit.inc.php | 15 + .../stages/resolve_fp_fast_path_hit.inc.php | 17 ++ .../resolve_whitelist_fast_path_hit.inc.php | 17 ++ .../stages/resolve_zp_fast_path_hit.inc.php | 15 + .../stages/run_main_api_fingerprint.inc.php | 38 ++- lib/Cloak/ReasonHelper.php | 45 ++- lib/Cloak/RiskSecondaryCache.php | 78 +---- lib/Cloak/RiskSecondaryGate.php | 11 + lib/Cloak/RiskSecondarySession.php | 41 +++ lib/Cloak/SimulationRouteResolver.php | 38 ++- lib/Cloak/VisitorApiAudit.php | 16 +- lib/Cloak/VisitorLogListHelper.php | 0 lib/Cloak/VisitorLogVtimes.php | 0 lib/Cloak/VisitorLogWorker.php | 79 +++++ lib/Cloak/VisitorRepository.php | 170 ++++++++--- lib/Cloak/VisitorSimulationRunner.php | 0 lib/Cloak/VisitorVisitContext.php | 175 +++++++++++ lib/Cloak/WhitelistGate.php | 106 +++++++ lib/CloakHttpHelpers.php | 198 ++++++++++++- lib/DbHelper.php | 9 +- lib/bootstrap.php | 4 + resources/virtual_device_detect.inc.php | 3 +- simulate_test.php | 0 ...5622e842e8a50778a6ed8dd1ce94732daca9e.json | 1 + ...1a80ce5876dafd7007d772e0114fddf88b682.json | 1 + ...a69820ff7a9c39ecd7ebe6413e5297d4347dd.json | 1 + table.php | 2 +- tools/fixtures/regression_whitelist_fast.php | 30 ++ tools/fixtures/regression_zp_whitelist.php | 30 ++ tools/regression_test.php | 30 ++ tools/verify_client_judgment_cache.php | 97 +++++++ tools/verify_cookie_domain.php | 56 ++++ tools/verify_datalist_helper.php | 9 + tools/verify_early_route_gate.php | 82 ++++++ tools/verify_judgment_priority.php | 84 ++++++ tools/verify_risk_cache.php | 79 +++-- tools/verify_risk_session.php | 32 +++ .../verify_simulation_zp_whitelist_route.php | 47 +++ tools/verify_visit_log_lifecycle.php | 72 +++++ tools/verify_wait_log_write.php | 97 +++++++ 61 files changed, 2405 insertions(+), 333 deletions(-) mode change 100644 => 100755 __code__/risk_analyzer_new.php mode change 100644 => 100755 check_config/indBXSXX_config.php mode change 100644 => 100755 datatable/auth.php mode change 100644 => 100755 datatable/fp_explain.php mode change 100644 => 100755 datatable/log_blob.php mode change 100644 => 100755 datatable/simulate.php mode change 100644 => 100755 indBXSXX.php create mode 100755 lib/Cloak/CloakJudgmentCache.php mode change 100644 => 100755 lib/Cloak/CloakSession.php mode change 100644 => 100755 lib/Cloak/FCheckHandler.php create mode 100755 lib/Cloak/Pipeline/stages/apply_geoip_country_log_only.inc.php create mode 100755 lib/Cloak/Pipeline/stages/resolve_blacklist_fast_path_hit.inc.php create mode 100755 lib/Cloak/Pipeline/stages/resolve_fp_fast_path_hit.inc.php create mode 100755 lib/Cloak/Pipeline/stages/resolve_whitelist_fast_path_hit.inc.php create mode 100755 lib/Cloak/Pipeline/stages/resolve_zp_fast_path_hit.inc.php mode change 100644 => 100755 lib/Cloak/RiskSecondaryCache.php create mode 100755 lib/Cloak/RiskSecondarySession.php mode change 100644 => 100755 lib/Cloak/SimulationRouteResolver.php mode change 100644 => 100755 lib/Cloak/VisitorLogListHelper.php mode change 100644 => 100755 lib/Cloak/VisitorLogVtimes.php mode change 100644 => 100755 lib/Cloak/VisitorSimulationRunner.php create mode 100755 lib/Cloak/VisitorVisitContext.php create mode 100755 lib/Cloak/WhitelistGate.php mode change 100644 => 100755 simulate_test.php create mode 100755 storage/runtime/c7f2a9e1/cc_cache/19e36255972107d42b8cecb77ef5622e842e8a50778a6ed8dd1ce94732daca9e.json create mode 100755 storage/runtime/c7f2a9e1/cc_cache/631f08140b24b7274d12df3c37a1a80ce5876dafd7007d772e0114fddf88b682.json create mode 100755 storage/runtime/c7f2a9e1/cc_cache/8959edf6884f2c2e223a9319e83a69820ff7a9c39ecd7ebe6413e5297d4347dd.json create mode 100755 tools/fixtures/regression_whitelist_fast.php create mode 100755 tools/fixtures/regression_zp_whitelist.php create mode 100755 tools/verify_client_judgment_cache.php create mode 100644 tools/verify_cookie_domain.php mode change 100644 => 100755 tools/verify_datalist_helper.php create mode 100755 tools/verify_early_route_gate.php create mode 100755 tools/verify_judgment_priority.php mode change 100644 => 100755 tools/verify_risk_cache.php create mode 100755 tools/verify_risk_session.php create mode 100755 tools/verify_simulation_zp_whitelist_route.php create mode 100755 tools/verify_visit_log_lifecycle.php create mode 100644 tools/verify_wait_log_write.php diff --git a/__code__/risk_analyzer_new.php b/__code__/risk_analyzer_new.php old mode 100644 new mode 100755 diff --git a/check_config/indBXSXX_config.php b/check_config/indBXSXX_config.php old mode 100644 new mode 100755 diff --git a/cong.php b/cong.php index 5ec1ac7..d6efd57 100755 --- a/cong.php +++ b/cong.php @@ -22,6 +22,11 @@ define('REDIS_PASSWORD', ''); // --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT)--- define('VISITOR_LOG_ASYNC', 'ON'); +// --- Cookie 域(可选)--- +// 宝塔多域名共用代码时一般留空,按每次请求的 HTTP_HOST 自动推断(如 .shili.buzz)。 +// 仅当所有落地域名需固定同一父域时再配置,例如:define('CLOAK_COOKIE_DOMAIN', '.shili.buzz'); +// define('CLOAK_COOKIE_DOMAIN', ''); + define('CLOAK_GEOIP_ENABLED', 'ON'); define('CLOAK_GEOIP_DB_PATH', __DIR__ . '/storage/runtime/c7f2a9e1/cc_idx.dat'); define('CLOAK_GEOIP_CACHE_TTL', 2592000); diff --git a/datatable/auth.php b/datatable/auth.php old mode 100644 new mode 100755 diff --git a/datatable/fp_explain.php b/datatable/fp_explain.php old mode 100644 new mode 100755 diff --git a/datatable/log_blob.php b/datatable/log_blob.php old mode 100644 new mode 100755 diff --git a/datatable/sdatalist.php b/datatable/sdatalist.php index 001b1d0..249f2d4 100755 --- a/datatable/sdatalist.php +++ b/datatable/sdatalist.php @@ -4,7 +4,7 @@ require_once(dirname(__DIR__) . "/cong.php"); require_once(dirname(__DIR__) . "/lib/DbHelper.php"); require_once(dirname(__DIR__) . "/lib/Cloak/PipelineTimer.php"); require_once(dirname(__DIR__) . "/lib/Cloak/VisitorLogSchema.php"); -require_once(dirname(__DIR__) . "/lib/Cloak/VisitorLogListHelper.php"); +require_once(dirname(__DIR__) . "/lib/Cloak/VisitorApiAudit.php"); $reasons = array( "blank_referrer" => "过滤无来源的访问", @@ -44,9 +44,15 @@ $reasons = array( "time_of_day" => "不可访问的日期", ); -$jsonResult = array(); -$conn = new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME); +$servername = "localhost"; +$username = DB_USERNAME; +$password = DB_PASSWORD; +$dbname = DB_NAME; +// Create connection +$conn = new mysqli($servername, $username, $password, $dbname); +$jsonResult = array(); +// Check connection if ($conn->connect_error) { $error = "Connection failed: " . $conn->connect_error; $handle = fopen(dirname(__FILE__) . "/err.txt", "a+"); @@ -59,40 +65,119 @@ if ($conn->connect_error) { $jsonResult["data"] = array(); } else { VisitorLogSchema::ensureColumns($conn); - VisitorLogSchema::ensureIndexes($conn); - - if(isset($_GET['clear']) && $_GET['clear'] == "true") { - $conn->query("TRUNCATE `visitor_logs`"); + if(isset($_GET['clear']) && $_GET['clear'] == "true") { //清除日志数据表 + $sql = "TRUNCATE `visitor_logs`"; + + // 执行查询并输出结果 + $result = mysqli_query($conn, $sql); $jsonResult["code"] = 0; $jsonResult["msg"] = "success"; $jsonResult["count"] = 0; $jsonResult["data"] = array(); - } else { - $pageNumber = isset($_GET['page']) ? max(1, (int) $_GET['page']) : 1; - $recordsPerPage = isset($_GET['limit']) ? max(1, min(200, (int) $_GET['limit'])) : 50; + } else { // 默认分页数据 + // 获取当前页码(默认为第1页) + $pageNumber = isset($_GET['page']) ? (int) $_GET['page'] : 1; + + // 设置每页显示的记录条数 + $recordsPerPage = isset($_GET['limit']) ? (int) $_GET['limit'] : 50; + + // 计算起始位置 $startPosition = ($pageNumber - 1) * $recordsPerPage; - $where = VisitorLogListHelper::buildWhere($conn, $_GET); - - $sql = VisitorLogListHelper::listSelectSql($where, $startPosition, $recordsPerPage, false); - $result = $conn->query($sql); - $jsonData = array(); - - if ($result) { - while ($row = $result->fetch_assoc()) { - $jsonData[] = VisitorLogListHelper::formatListRow($row, $countries, $reasons); - } - $result->free(); + $map = array(); + $where = 1; + + if(!empty($_GET['visit_date'])) { + $map[] = "DATE_FORMAT(`visit_date`, '%Y-%m-%d') LIKE '" . cloak_db_escape($conn, $_GET['visit_date']) . "'"; } + $where .= cloak_visitor_log_apply_result_filter($conn, $_GET['result'] ?? ''); + + if(!empty($_GET['country'])) { + $map[] = "country LIKE '" . cloak_db_escape($conn, $_GET['country']) . "'"; + } + + if(!empty($_GET['IP'])) { + $map[] = "IP LIKE '" . cloak_db_escape($conn, $_GET['IP']) . "'"; + } + + if(!empty($_GET['domain'])) { + $map[] = "domain LIKE '%" . cloak_db_escape($conn, $_GET['domain']) . "%'"; + } + + if(!empty($_GET['spage'])) { + $map[] = "page LIKE '%" . cloak_db_escape($conn, $_GET['spage']) . "%'"; + } + + if(!empty($map)) { + $where .= " AND " . join(" AND ", $map); + } + // SQL语句 + $sql = "SELECT `id`, `campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `fp_url`, `referer`, `vtimes`, `client`, `browser`, `page`, `language`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `fp_hdata`, `api_by_request`, `api_by_response`, `api_risk_request`, `api_risk_response`, `device`, `add_time` FROM `visitor_logs` WHERE {$where} ORDER BY `id` DESC LIMIT {$startPosition}, {$recordsPerPage}"; + + // 执行查询并输出结果 + $result = mysqli_query($conn, $sql); + $jsonData = array(); + + while ($row = mysqli_fetch_assoc($result)) { + //echo "

{$row["columnName"]}

"; // 根据需要修改列名 + if($row['vtimes'] == 0) { + $row['vtimes'] = 1; // 还未统计访问次数 + $sqlVtimes = mysqli_query($conn, "SELECT count(*) as `total` FROM `visitor_logs` WHERE `IP` = '" . cloak_db_escape($conn, $row["IP"]) . "' AND `domain` = '" . cloak_db_escape($conn, $row["domain"]) . "' AND id < " . (int)$row['id']); + $vtimesRow = mysqli_fetch_assoc($sqlVtimes); + $vtimes = $vtimesRow["total"]; // 如果之前此IP有访问记录 + if($vtimes > 0) { + $row['vtimes'] += $vtimes; // 访问次数叠加 + } + + // 更新访问次数 + $updateSql = "UPDATE `visitor_logs` SET `vtimes`=" . (int)$row['vtimes'] . " WHERE id = " . $row['id']; + mysqli_query($conn, $updateSql); + } + + $row['country'] = $countries[$row['country']]; + if(isset($reasons[$row['reason']])) { + $row['reason'] = $reasons[$row['reason']]; + } + if ($row['result'] === 'wait') { + $row['result'] = '检测中'; + } + $row['judge_timing_text'] = cloak_visitor_log_format_timing($row['judge_timing'] ?? ''); + $row['fp_hdata_text'] = cloak_visitor_log_format_fp_hdata($row['fp_hdata'] ?? ''); + if (!empty($row['fp_hdata'])) { + $explain = cloak_visitor_log_fp_risk_explain( + $row['fp_hdata'], + $row['reason'] ?? '', + $row['result'] ?? '', + $row['domain'] ?? '' + ); + $row['fp_hdata_explain_summary'] = $explain['summary']; + $row['fp_hdata_explain_html'] = $explain['html']; + $row['fp_hdata_explain'] = $explain['data']; + } else { + $row['fp_hdata_explain_summary'] = ''; + $row['fp_hdata_explain_html'] = ''; + $row['fp_hdata_explain'] = null; + } + $row['api_by_request_text'] = VisitorApiAudit::displayText($row['api_by_request'] ?? ''); + $row['api_by_response_text'] = VisitorApiAudit::displayText($row['api_by_response'] ?? ''); + $row['api_risk_request_text'] = VisitorApiAudit::displayText($row['api_risk_request'] ?? ''); + $row['api_risk_response_text']= VisitorApiAudit::displayText($row['api_risk_response'] ?? ''); + $jsonData[] = $row; + } + + $sqlCount = mysqli_query($conn, "SELECT count(*) as `total` FROM `visitor_logs` WHERE {$where}"); + $countRow = mysqli_fetch_assoc($sqlCount); + $jsonResult["code"] = 0; $jsonResult["msg"] = ""; - $jsonResult["count"] = VisitorLogListHelper::countWhere($conn, $where); + $jsonResult["count"] = $countRow['total']; $jsonResult["data"] = $jsonData; } } + +// 关闭数据库连接 +mysqli_close($conn); -$conn->close(); +header('Content-Type: application/json'); -header('Content-Type: application/json; charset=utf-8'); -echo json_encode($jsonResult); -exit; +echo json_encode($jsonResult);exit; diff --git a/datatable/simulate.php b/datatable/simulate.php old mode 100644 new mode 100755 diff --git a/dist/index.php b/dist/index.php index 8986efc..12fd749 100755 --- a/dist/index.php +++ b/dist/index.php @@ -28,7 +28,7 @@ $cloak_key = CHECK_KEY; $campagin_id = COSTM_IP_SCORE; $time = time(); $ckey = sha1($campagin_id . sha1($time . $cloak_key)); -$fp_cookie_domain = cloak_fingerprint_cookie_domain(); +$fp_js_cookie_opts = cloak_fingerprint_js_cookie_options(); readfile(__DIR__ . '/cloakjs'); echo "\n"; @@ -151,10 +151,7 @@ var virtual = ; function cloakFinishFingerprint() { - var fpCookieOpts = { path: '/', expires: 1, sameSite: 'Lax' }; - - fpCookieOpts.domain = ; - + var fpCookieOpts = ; var fpCookies = (typeof Cookies !== 'undefined' && Cookies.withAttributes) ? Cookies.withAttributes(fpCookieOpts) : Cookies; diff --git a/indBXSXX.php b/indBXSXX.php old mode 100644 new mode 100755 diff --git a/install.php b/install.php index 5ed33c3..0166fca 100755 --- a/install.php +++ b/install.php @@ -135,6 +135,9 @@ if (!$isLocked && !empty($_POST['action']) && $_POST['action'] === 'install') { . '// --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT)---' . PHP_EOL . 'define(\'VISITOR_LOG_ASYNC\', \'ON\');' . PHP_EOL . PHP_EOL + . '// --- Cookie 域(可选,留空则按 HTTP_HOST 自动推断)---' . PHP_EOL + . '// define(\'CLOAK_COOKIE_DOMAIN\', \'.example.com\');' . PHP_EOL + . PHP_EOL . '// --- GeoIP 本地国家库(MaxMind GeoLite2 Country)---' . PHP_EOL . 'define(\'CLOAK_GEOIP_ENABLED\', \'ON\');' . PHP_EOL . 'define(\'CLOAK_GEOIP_DB_PATH\', __DIR__ . \'/storage/runtime/c7f2a9e1/cc_idx.dat\');' . PHP_EOL diff --git a/ip_check.php b/ip_check.php index 9803a90..7404218 100755 --- a/ip_check.php +++ b/ip_check.php @@ -4,6 +4,11 @@ require_once __DIR__ . '/lib/CloakHttpHelpers.php'; require_once __DIR__ . '/lib/Cloak/CloakSession.php'; CloakSession::start(); +require_once __DIR__ . '/lib/Cloak/VisitorVisitContext.php'; +VisitorVisitContext::evaluateAtEntry(); +if (VisitorVisitContext::isNewVisit()) { + VisitorVisitContext::markVisitActive(); +} // $begin_time = date("H:i:s"); // include("ip_check_config.php"); diff --git a/jump.txt b/jump.txt index 60797dd..be8efa3 100755 --- a/jump.txt +++ b/jump.txt @@ -113,3 +113,11 @@ 2026-06-15 20:31:20 | 写入数据库 | false | indBXSXX | async=1 2026-06-15 20:31:51 | 来源判断 | 无法识别访客国家|false | indBXSXX | 127.0.0.1 | 2026-06-15 20:31:51 | 写入数据库 | false | indBXSXX | async=1 +2026-06-15 23:59:33 | 写入数据库 | | regression_test | async=1 +2026-06-15 23:59:33 | 写入数据库 | | regression_test | async=1 +2026-06-16 03:17:21 | 来源判断 | 无法识别访客国家|false | index | 0.0.0.0 | +2026-06-16 03:17:21 | 写入数据库 | false | test | async=1 +2026-06-16 03:17:42 | 来源判断 | 无法识别访客国家|false | index | 0.0.0.0 | +2026-06-16 03:17:42 | 写入数据库 | false | indBXSXX_test | async=1 +2026-06-16 03:21:38 | 来源判断 | 无法识别访客国家|false | index | 0.0.0.0 | +2026-06-16 03:21:38 | 写入数据库 | false | indBXSXX_test | async=1 diff --git a/lib/Cloak/CloakJudgmentCache.php b/lib/Cloak/CloakJudgmentCache.php new file mode 100755 index 0000000..d60fbb4 --- /dev/null +++ b/lib/Cloak/CloakJudgmentCache.php @@ -0,0 +1,272 @@ +v_ip = $cached['ip']; + } + if (array_key_exists('country', $cached) && (string) $cached['country'] !== '') { + $v_info->v_country = (string) $cached['country']; + } + } + + public static function clear() + { + unset($_SESSION[self::SESSION_KEY]); + } + + public static function hitReason() + { + return '缓存'; + } + + /** + * @param array $cached + * @return array{result:bool,reason:string,link:string} + */ + public static function toApiResponse(array $cached) + { + $passed = ($cached['result'] ?? '') === 'true'; + $link = (string) ($cached['fp_url'] ?? ''); + + if ($passed && $link === '') { + $link = FpUrlHelper::fallbackUrl(); + } + if (!$passed && $link === '' && defined('DB_ZP')) { + $link = (string) DB_ZP; + } + + return [ + 'result' => $passed, + 'reason' => self::hitReason(), + 'link' => $link, + ]; + } + + /** + * @param string $stage + * @param array $payload + */ + private static function writeCache($stage, array $payload) + { + $ip = ClientIpResolver::normalizeIp($payload['ip'] ?? ClientIpResolver::resolve()); + if ($ip === null) { + return; + } + + $country = (string) ($payload['country'] ?? ''); + if ($country === '' && !empty($_SESSION['gcu_country'])) { + $country = (string) $_SESSION['gcu_country']; + } + if ($country === '') { + $country = (string) (GeoIpCountryResolver::resolve($ip) ?? ''); + } + + $_SESSION[self::SESSION_KEY] = [ + 'stage' => $stage, + 'ip' => $ip, + 'country' => $country, + 'result' => ($payload['result'] ?? '') === 'true' ? 'true' : 'false', + 'reason' => (string) ($payload['reason'] ?? ''), + 'fp_url' => (string) ($payload['fp_url'] ?? ''), + 'risk_number' => defined('CLOAK_RISK_NUMBER') ? (int) CLOAK_RISK_NUMBER : 0, + 'campaign_id' => defined('COSTM_IP_SCORE') ? (string) COSTM_IP_SCORE : '', + 'cached_at' => time(), + ]; + } + + /** + * @param string $currentIp + */ + private static function refreshCountryForCurrentIp($currentIp) + { + if (!self::hasSession()) { + return; + } + $cached = $_SESSION[self::SESSION_KEY]; + $cached['ip'] = $currentIp; + $cached['country'] = (string) (GeoIpCountryResolver::resolve($currentIp) ?? ''); + $_SESSION[self::SESSION_KEY] = $cached; + $_SESSION['gcu_ip'] = $currentIp; + $_SESSION['gcu_country'] = $cached['country']; + } + + /** + * @param array $cached + */ + private static function isConfigValid(array $cached) + { + if (defined('CLOAK_RISK_NUMBER') && (int) ($cached['risk_number'] ?? -1) !== (int) CLOAK_RISK_NUMBER) { + return false; + } + if (defined('COSTM_IP_SCORE') && (string) ($cached['campaign_id'] ?? '') !== (string) COSTM_IP_SCORE) { + return false; + } + return true; + } +} diff --git a/lib/Cloak/CloakSession.php b/lib/Cloak/CloakSession.php old mode 100644 new mode 100755 index e3b16b7..cb41d4a --- a/lib/Cloak/CloakSession.php +++ b/lib/Cloak/CloakSession.php @@ -18,7 +18,7 @@ class CloakSession session_set_cookie_params([ 'lifetime' => 0, 'path' => '/', - 'domain' => $domain, + 'domain' => ltrim($domain, '.'), 'secure' => function_exists('cloak_request_is_https') && cloak_request_is_https(), 'httponly' => true, 'samesite' => 'Lax', @@ -27,7 +27,7 @@ class CloakSession session_set_cookie_params( 0, '/', - $domain, + ltrim($domain, '.'), function_exists('cloak_request_is_https') && cloak_request_is_https(), true ); @@ -80,7 +80,13 @@ class CloakFcheckCors return false; } - $requestHost = (string) ($_SERVER['HTTP_HOST'] ?? ''); + $requestHost = function_exists('cloak_normalize_request_host') + ? cloak_normalize_request_host($_SERVER['HTTP_HOST'] ?? '') + : strtolower(trim((string) ($_SERVER['HTTP_HOST'] ?? ''))); + $originHost = function_exists('cloak_normalize_request_host') + ? cloak_normalize_request_host($originHost) + : strtolower(trim($originHost)); + if ($requestHost !== '' && strcasecmp($originHost, $requestHost) === 0) { return true; } @@ -103,18 +109,15 @@ class CloakFcheckCors */ private static function registrableSuffix($host) { + if (function_exists('cloak_registrable_domain')) { + return cloak_registrable_domain($host); + } + $host = strtolower(trim($host)); if ($host === '') { return ''; } - if (function_exists('cloak_fingerprint_cookie_domain')) { - $cookieDomain = ltrim(cloak_fingerprint_cookie_domain(), '.'); - if ($cookieDomain !== '' && self::hostMatchesSuffix($host, $cookieDomain)) { - return $cookieDomain; - } - } - $parts = explode('.', $host); if (count($parts) < 2) { return $host; diff --git a/lib/Cloak/FCheckHandler.php b/lib/Cloak/FCheckHandler.php old mode 100644 new mode 100755 index 4f7b687..4c27cc3 --- a/lib/Cloak/FCheckHandler.php +++ b/lib/Cloak/FCheckHandler.php @@ -6,7 +6,11 @@ require_once dirname(__DIR__) . '/DbHelper.php'; require_once __DIR__ . '/FpUrlHelper.php'; require_once __DIR__ . '/VisitorApiAudit.php'; require_once __DIR__ . '/RiskSecondaryGate.php'; +require_once __DIR__ . '/CloakJudgmentCache.php'; require_once __DIR__ . '/RiskSecondaryCache.php'; +require_once __DIR__ . '/VisitorVisitContext.php'; +require_once __DIR__ . '/ReasonHelper.php'; +require_once __DIR__ . '/VisitorRepository.php'; require_once __DIR__ . '/VisitorLogSchema.php'; require_once __DIR__ . '/ClientIpResolver.php'; @@ -46,6 +50,9 @@ class FCheckHandler } $config_name = ConfigLoader::loadByHost($fp_host); $log_id = (int) ($fingerprint_info['log_id'] ?? 0); + if ($log_id <= 0) { + $log_id = VisitorVisitContext::currentLogId(); + } if (!RiskSecondaryGate::isEnabled()) { $_SESSION['visit_to_3'] = 3; @@ -62,9 +69,9 @@ class FCheckHandler if ($log_id > 0) { self::updateLog($log_id, [ 'result' => 'true', - 'reason' => '二次风控已关闭', + 'reason' => '', 'fp_url' => $response['link'], - ], $fingerprint_info, null, null); + ], $fingerprint_info, null, null, false); } return array_merge(['ok' => true, 'status' => 200, 'fingerprint_info' => $fingerprint_info], $response); @@ -163,7 +170,7 @@ class FCheckHandler self::persistCache($update_log, $clientIp); if ($log_id > 0) { - self::updateLog($log_id, $update_log, $fingerprint_info, $jsonData, $return); + self::updateLog($log_id, $update_log, $fingerprint_info, $jsonData, $return, true); } return array_merge([ @@ -186,7 +193,7 @@ class FCheckHandler $update_log = [ 'result' => $response['result'] ? 'true' : 'false', - 'reason' => $response['reason'], + 'reason' => cloak_cache_hit_log_reason($update_log['result']), 'fp_url' => trim((string) ($response['link'] ?? '')), ]; if ($update_log['fp_url'] === '') { @@ -196,14 +203,7 @@ class FCheckHandler } if ($log_id > 0) { - $cacheMarker = [ - 'cached' => true, - 'result' => $update_log['result'], - 'reason' => $cached['reason'] ?? '', - 'api_reason' => $cached['reason'] ?? '', - 'cached_at' => $cached['cached_at'] ?? null, - ]; - self::updateLog($log_id, $update_log, $fingerprint_info, null, $cacheMarker); + self::updateLog($log_id, $update_log, $fingerprint_info, null, null, false); } return array_merge([ @@ -222,7 +222,10 @@ class FCheckHandler { $update_log = []; $update_log['result'] = !empty($response['result']) ? 'true' : 'false'; - $update_log['reason'] = $response['reason'] ?? ''; + $update_log['reason'] = cloak_normalize_log_reason( + $update_log['result'], + $response['reason'] ?? '' + ); $update_log['fp_url'] = trim((string) ($response['link'] ?? '')); if ($update_log['fp_url'] === '') { $update_log['fp_url'] = $update_log['result'] === 'true' @@ -239,11 +242,12 @@ class FCheckHandler */ private static function persistCache(array $update_log, $clientIp) { - RiskSecondaryCache::store([ - 'ip' => $clientIp, - 'result' => $update_log['result'], - 'reason' => $update_log['reason'], - 'fp_url' => $update_log['fp_url'], + CloakJudgmentCache::storeFromByApiRisk([ + 'ip' => $clientIp, + 'country' => (string) ($_SESSION['gcu_country'] ?? ''), + 'result' => $update_log['result'], + 'reason' => $update_log['reason'], + 'fp_url' => $update_log['fp_url'], ]); } @@ -253,36 +257,36 @@ class FCheckHandler * @param array $fingerprint_info * @param array|null $apiRequest * @param array|null $apiResponse + * @param bool $includeFpAndApi 缓存路径不写 fp_hdata / api_risk_* */ - public static function updateLog($log_id, array $update_log, array $fingerprint_info, $apiRequest, $apiResponse) + public static function updateLog($log_id, array $update_log, array $fingerprint_info, $apiRequest, $apiResponse, $includeFpAndApi = true) { - $conn = @new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME); - if (!$conn || $conn->connect_error) { - return false; - } - VisitorLogSchema::ensureColumns($conn); + $payload = [ + 'result' => (string) ($update_log['result'] ?? ''), + 'reason' => cloak_normalize_log_reason( + $update_log['result'] ?? '', + $update_log['reason'] ?? '' + ), + 'fp_url' => (string) ($update_log['fp_url'] ?? ''), + ]; - $resultEsc = cloak_db_escape($conn, $update_log['result'] ?? ''); - $reasonEsc = cloak_db_escape($conn, strip_tags(cloak_db_string($update_log['reason'] ?? ''))); - $fpUrlEsc = cloak_db_escape($conn, strip_tags(cloak_db_string($update_log['fp_url'] ?? ''))); - $fpHdataJson = json_encode($fingerprint_info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - if ($fpHdataJson === false) { - $fpHdataJson = ''; - } - $fpHdataEsc = cloak_db_escape($conn, $fpHdataJson); - $apiRiskReqEsc = cloak_db_escape($conn, $apiRequest !== null ? VisitorApiAudit::encode($apiRequest) : ''); - $apiRiskRespEsc = cloak_db_escape($conn, $apiResponse !== null ? VisitorApiAudit::encode($apiResponse) : ''); - $sql = "UPDATE `visitor_logs` SET `result`='{$resultEsc}',`reason`='{$reasonEsc}',`fp_url`='{$fpUrlEsc}',`fp_hdata`='{$fpHdataEsc}',`api_risk_request`='{$apiRiskReqEsc}',`api_risk_response`='{$apiRiskRespEsc}' WHERE `id`='" . (int) $log_id . "'"; - - $ok = $conn->query($sql) === true; - if (!$ok) { - $handle = @fopen(dirname(__DIR__, 2) . '/err.txt', 'a+'); - if ($handle) { - fwrite($handle, date('Y-m-d H:i:s') . ' | FCheckHandler update: ' . $conn->error . "\n"); - fclose($handle); + if ($includeFpAndApi) { + $fpHdataJson = json_encode($fingerprint_info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + if ($fpHdataJson === false) { + $fpHdataJson = ''; } + $payload['fp_hdata'] = $fpHdataJson; + $payload['api_risk_request'] = $apiRequest !== null ? VisitorApiAudit::encode($apiRequest) : ''; + $payload['api_risk_response'] = $apiResponse !== null ? VisitorApiAudit::encode($apiResponse) : ''; } - $conn->close(); + + $hadForceSync = !empty($GLOBALS['__cloak_force_sync_log']); + $GLOBALS['__cloak_force_sync_log'] = true; + $ok = cloak_update_log_db((int) $log_id, $payload, ['skip_enrich' => true]) !== false; + if (!$hadForceSync) { + unset($GLOBALS['__cloak_force_sync_log']); + } + return $ok; } } diff --git a/lib/Cloak/Page/FpPageRenderer.php b/lib/Cloak/Page/FpPageRenderer.php index 4d81fcc..d5df445 100755 --- a/lib/Cloak/Page/FpPageRenderer.php +++ b/lib/Cloak/Page/FpPageRenderer.php @@ -7,7 +7,7 @@ require_once __DIR__ . '/../FpUrlHelper.php'; class FpPageRenderer { /** - * @param array $ctx 必须包含 logs 数组 + * @param array $ctx 必须包含 logs 数组;可选 skip_log */ public static function render(array $ctx) { @@ -17,10 +17,12 @@ class FpPageRenderer $my_file_name = substr($my_url, strrpos($my_url, '/') + 1); $cong_name = str_replace('.php', '', $my_file_name); - $resolved = FpUrlHelper::resolve($cong_name, true); - $fp_url = $resolved['url']; + $resolved = FpUrlHelper::resolve($cong_name, true); + $fp_url = $resolved['url']; $logs['fp_url'] = $fp_url; - write_log_db($logs); + if (empty($ctx['skip_log'])) { + write_log_db($logs); + } $show_content = CLOAK_SHOW_CONTENT; if ($show_content == 'curl') { diff --git a/lib/Cloak/Pipeline/CheckPipeline.php b/lib/Cloak/Pipeline/CheckPipeline.php index 01ff6c5..aa23427 100755 --- a/lib/Cloak/Pipeline/CheckPipeline.php +++ b/lib/Cloak/Pipeline/CheckPipeline.php @@ -1,16 +1,21 @@ 阶段缓存续跑 > 完整判定 */ class CheckPipeline { /** - * 在调用方(ip_check 顶层)作用域执行完整判定链。 - * 依赖全局:$v_info, $config_name, $__cloak_debug_on, $reason - * * @return void */ public static function run() @@ -23,15 +28,64 @@ class CheckPipeline CloakPipelineTimer::init(); - CloakPipelineTimer::stage('Session快速路径'); - if (self::resolveSessionFastPath()) { + CloakPipelineTimer::stage('白名单链接参数'); + if (self::resolveWhitelistParamsFastPath()) { + include __DIR__ . '/stages/resolve_whitelist_fast_path_hit.inc.php'; + CloakPipelineTimer::finish(); + return; + } + + CloakPipelineTimer::stage('白名单IP'); + if (self::resolveWhitelistIpFastPath()) { + include __DIR__ . '/stages/resolve_whitelist_fast_path_hit.inc.php'; + CloakPipelineTimer::finish(); + return; + } + + CloakPipelineTimer::stage('黑名单'); + if (self::resolveBlacklistFastPath()) { + include __DIR__ . '/stages/resolve_blacklist_fast_path_hit.inc.php'; + CloakPipelineTimer::finish(); + return; + } + + CloakPipelineTimer::stage('安全页模式'); + if (self::resolveZpFastPath()) { + include __DIR__ . '/stages/resolve_zp_fast_path_hit.inc.php'; + CloakPipelineTimer::finish(); + return; + } + + CloakPipelineTimer::stage('真实页模式'); + if (self::resolveFpFastPath()) { + include __DIR__ . '/stages/resolve_fp_fast_path_hit.inc.php'; + CloakPipelineTimer::finish(); + return; + } + + CloakPipelineTimer::stage('客户端缓存快速路径'); + if (self::resolveClientCacheFastPath()) { include __DIR__ . '/stages/resolve_session_fast_path_hit.inc.php'; CloakPipelineTimer::finish(); return; } + CloakPipelineTimer::stage('客户端阶段缓存续跑'); + if (self::resolvePartialCacheResume()) { + include __DIR__ . '/stages/run_main_api_fingerprint.inc.php'; + CloakPipelineTimer::finish(); + return; + } + if ($__cloak_debug_on) { - cloak_dbg_step(__LINE__, 'Session 快速路径', 'skip', '未命中缓存,进入完整判定流程(DEBUG 已重置 visit_to_*)'); + cloak_dbg_step(__LINE__, '完整判定', 'info', '未命中早期快速路径,进入完整判定'); + } + + CloakPipelineTimer::stage('黑白名单'); + include __DIR__ . '/stages/apply_whitelist_blacklist.inc.php'; + if (!empty($_SESSION['check_result'])) { + CloakPipelineTimer::finish(); + return; } CloakPipelineTimer::stage('GeoIP国家判定'); @@ -41,8 +95,6 @@ class CheckPipeline return; } - CloakPipelineTimer::stage('黑白名单'); - include __DIR__ . '/stages/apply_whitelist_blacklist.inc.php'; CloakPipelineTimer::stage('广告来源限制'); include __DIR__ . '/stages/run_ad_source_guard.inc.php'; CloakPipelineTimer::stage('临时链接参数'); @@ -51,20 +103,108 @@ class CheckPipeline CloakPipelineTimer::finish(); } + public static function resolveWhitelistParamsFastPath() + { + return WhitelistGate::matchesWhitelistParams(); + } + + public static function resolveWhitelistIpFastPath() + { + global $v_info; + + if (WhitelistGate::matchesWhitelistParams()) { + return false; + } + + return WhitelistGate::matchesWhitelistIp(WhitelistGate::resolveVisitorIp($v_info)); + } + + public static function resolveBlacklistFastPath() + { + global $v_info; + + if (!defined('BLACKLIST_GROUP_ID') || (int) BLACKLIST_GROUP_ID <= 0) { + return false; + } + $blEntries = ip_group_list_addresses((int) BLACKLIST_GROUP_ID); + + return ip_visitor_in_blacklist_entries($v_info->v_ip, $blEntries); + } + + public static function resolveClientCacheFastPath() + { + global $__cloak_debug_on, $v_info; + + if ($__cloak_debug_on || SHOW_SITE_MODE_SWITCH !== 'ip_check') { + return false; + } + + $cached = CloakJudgmentCache::getFinal(); + if ($cached === null) { + return false; + } + + CloakJudgmentCache::applyToSession($cached); + CloakJudgmentCache::applyToVisitorInfo($v_info, $cached); + + if ($__cloak_debug_on) { + CloakDebugPage::setFlag('session_cached', true); + } + + return true; + } + + public static function resolvePartialCacheResume() + { + global $__cloak_debug_on, $v_info, $reason; + + if ($__cloak_debug_on || SHOW_SITE_MODE_SWITCH !== 'ip_check') { + return false; + } + + $cached = CloakJudgmentCache::get(); + if (!CloakJudgmentCache::isPartial($cached)) { + return false; + } + + CloakJudgmentCache::applyToSession($cached); + CloakJudgmentCache::applyToVisitorInfo($v_info, $cached); + $_SESSION['visit_to_2'] = '2'; + + if ($reason === '') { + $reason = 'Session byApi阶段缓存:待二次风控'; + } + + if ($__cloak_debug_on) { + cloak_dbg_step(__LINE__, '客户端阶段缓存续跑', 'pass', '命中 byapi 阶段缓存,跳过 GeoIP/Guard/byApi'); + } + + return true; + } + /** - * 是否命中 session 缓存结果(true = 跳过完整判定) - * - * @return bool + * @deprecated */ public static function resolveSessionFastPath() { - global $__cloak_debug_on; + return self::resolveClientCacheFastPath(); + } - return ( - !$__cloak_debug_on - && SHOW_SITE_MODE_SWITCH === 'ip_check' - && isset($_SESSION['check_result']) - && ($_SESSION['check_result'] === 'true' || $_SESSION['check_result'] === 'false') - ); + /** + * @deprecated 使用 resolveWhitelistParamsFastPath / resolveWhitelistIpFastPath + */ + public static function resolveWhitelistFastPath() + { + return self::resolveWhitelistParamsFastPath() || self::resolveWhitelistIpFastPath(); + } + + public static function resolveZpFastPath() + { + return defined('SHOW_SITE_MODE_SWITCH') && SHOW_SITE_MODE_SWITCH === 'zp'; + } + + public static function resolveFpFastPath() + { + return defined('SHOW_SITE_MODE_SWITCH') && SHOW_SITE_MODE_SWITCH === 'fp'; } } diff --git a/lib/Cloak/Pipeline/RouteResolver.php b/lib/Cloak/Pipeline/RouteResolver.php index f036088..6107294 100755 --- a/lib/Cloak/Pipeline/RouteResolver.php +++ b/lib/Cloak/Pipeline/RouteResolver.php @@ -1,9 +1,11 @@ v_ip); @@ -81,21 +102,22 @@ class RouteResolver $_SESSION['visit_to_3'] = '1'; } - if (RiskSecondaryGate::isEnabled()) { + if (RiskSecondaryGate::isEnabledForRequest()) { $cached = RiskSecondaryCache::get(ClientIpResolver::resolve()); if ($cached !== null) { - self::renderFromRiskCache($logs, $v_info, $cached); + self::renderFromRiskCache($logs, $v_info, $cached, $config_name); return; } } if (($_SESSION['visit_to_3'] ?? '1') != '3' - && RiskSecondaryGate::isEnabled()) { + && RiskSecondaryGate::isEnabledForRequest()) { // 二次风控:先写 wait 状态,f_check 完成后再更新 result / fp_url(避免 true+空跳转) $logs['result'] = 'wait'; $logs['reason'] = '二次风控检测中'; $logs['fp_url'] = ''; $log_id = write_log_db($logs); + RiskSecondarySession::markWaitLog($log_id); $riskCtx = [ 'logs' => $logs, 'v_info' => $v_info, @@ -111,22 +133,36 @@ class RouteResolver $logs['result'] = 'true'; $logs['reason'] = ''; - FpPageRenderer::render(['logs' => $logs]); + self::renderFpPage($logs, $config_name); + } + + /** + * @param array $logs + * @param string $config_name + */ + private static function renderFpPage(array $logs, $config_name = '') + { + FpPageRenderer::render([ + 'logs' => $logs, + 'config_name' => $config_name, + 'skip_log' => RiskSecondarySession::shouldSkipFpPageLog(), + ]); } /** * 二次风控 Session 缓存命中:跳过 cloakjs / byApiRisk。 * - * @param array $logs - * @param array $cached + * @param array $logs + * @param array $cached + * @param string $config_name */ - private static function renderFromRiskCache(array $logs, $v_info, array $cached) + private static function renderFromRiskCache(array $logs, $v_info, array $cached, $config_name = '') { RiskSecondaryCache::applyToSession($cached); $apiResponse = RiskSecondaryCache::toApiResponse($cached); $logs['result'] = $apiResponse['result'] ? 'true' : 'false'; - $logs['reason'] = $apiResponse['reason']; + $logs['reason'] = cloak_cache_hit_log_reason($logs['result']); $logs['fp_url'] = trim((string) ($apiResponse['link'] ?? '')); if ($logs['fp_url'] === '') { $logs['fp_url'] = $logs['result'] === 'true' @@ -139,7 +175,7 @@ class RouteResolver return; } - FpPageRenderer::render(['logs' => $logs]); + self::renderFpPage($logs, $config_name); } /** diff --git a/lib/Cloak/Pipeline/stages/apply_geoip_country_log_only.inc.php b/lib/Cloak/Pipeline/stages/apply_geoip_country_log_only.inc.php new file mode 100755 index 0000000..622492a --- /dev/null +++ b/lib/Cloak/Pipeline/stages/apply_geoip_country_log_only.inc.php @@ -0,0 +1,30 @@ +v_ip = $ip; + $_SESSION['gcu_ip'] = $ip; +} + +if (GeoIpCountryResolver::isAvailable()) { + $geoMeta = GeoIpCountryResolver::resolveWithMeta($ip); + $countryCode = $geoMeta['code']; + $geoSource = $geoMeta['source']; + if ($countryCode !== null && $countryCode !== '') { + $v_info->v_country = $countryCode; + $_SESSION['gcu_country'] = $countryCode; + } + if (!empty($GLOBALS['__cloak_debug_on'])) { + cloak_dbg_step(__LINE__, 'MaxMind国家记录', 'log', '仅记录国家,不参与判定', [ + 'ip' => $ip, + 'country' => $countryCode, + 'geo_source' => $geoSource, + ]); + } +} elseif (!empty($GLOBALS['__cloak_debug_on'])) { + cloak_dbg_step(__LINE__, 'MaxMind国家记录', 'skip', '本地 GeoIP 库不可用'); +} diff --git a/lib/Cloak/Pipeline/stages/apply_whitelist_blacklist.inc.php b/lib/Cloak/Pipeline/stages/apply_whitelist_blacklist.inc.php index c592c2c..90fe5f1 100755 --- a/lib/Cloak/Pipeline/stages/apply_whitelist_blacklist.inc.php +++ b/lib/Cloak/Pipeline/stages/apply_whitelist_blacklist.inc.php @@ -1,69 +1,46 @@ 0) { - $ips_fp_ary = ip_group_list_addresses((int) WHITELIST_GROUP_ID); - } else { - $ips_fp_ary = array_filter(array_map('trim', explode(',', SHOW_SITE_IP)), static function ($v) { - return $v !== ''; - }); - } - $reason = ""; // 判断理由 - - $whiteParamsMatched = false; - if( !empty(WHITE_PARAMS) ) { - $wparams = explode('=', WHITE_PARAMS, 2); - - if(count($wparams) == 2) { - $pk = $wparams[0]; - $pv = $wparams[1]; - } - - if(isset($_GET[$pk]) && strip_tags($_GET[$pk]) == $pv) { - $whiteParamsMatched = true; - + // 黑白名单(完整判定链内):链接参数白名单 > IP 白名单 > 黑名单 + $reason = $reason ?? ''; + + if (WhitelistGate::matchesWhitelistParams()) { + $_SESSION['check_result'] = 'true'; + $reason = '白名单链接参数'; + if ($__cloak_debug_on) { + cloak_dbg_step(__LINE__, '链接参数白名单', 'pass', defined('WHITE_PARAMS') ? WHITE_PARAMS : ''); + cloak_dbg_record(__LINE__, 'true', $reason, ['stage' => 'whitelist_params']); } + return; } - + + if (WhitelistGate::matchesWhitelistIp(WhitelistGate::resolveVisitorIp($v_info))) { + $_SESSION['check_result'] = 'true'; + $reason = '白名单'; + if ($__cloak_debug_on) { + cloak_dbg_step(__LINE__, 'IP 白名单', 'pass', 'IP 在白名单组/列表中'); + cloak_dbg_record(__LINE__, 'true', $reason, ['stage' => 'whitelist_ip']); + } + return; + } + + if ($__cloak_debug_on && defined('WHITE_PARAMS') && WHITE_PARAMS !== '') { + cloak_dbg_step(__LINE__, '链接参数白名单', 'skip', '参数不匹配: ' . WHITE_PARAMS); + } + $match = false; if (defined('BLACKLIST_GROUP_ID') && (int) BLACKLIST_GROUP_ID > 0) { $blEntries = ip_group_list_addresses((int) BLACKLIST_GROUP_ID); - $match = ip_visitor_in_blacklist_entries($v_info->v_ip, $blEntries); + $match = ip_visitor_in_blacklist_entries($v_info->v_ip, $blEntries); } if ($__cloak_debug_on) { - cloak_dbg_step(__LINE__,'黑名单检查', $match ? 'fail' : 'pass', $match ? 'IP 在黑名单组内' : '未命中黑名单', [ - 'ip' => $v_info->v_ip, + cloak_dbg_step(__LINE__, '黑名单检查', $match ? 'fail' : 'pass', $match ? 'IP 在黑名单组内' : '未命中黑名单', [ + 'ip' => $v_info->v_ip, 'group_id' => defined('BLACKLIST_GROUP_ID') ? BLACKLIST_GROUP_ID : 0, ]); } if ($match) { - $_SESSION["check_result"] = "false"; - $reason = "黑名单"; + $_SESSION['check_result'] = 'false'; + $reason = '黑名单'; if ($__cloak_debug_on) { - cloak_dbg_record(__LINE__,'false', $reason, ['stage' => 'blacklist']); + cloak_dbg_record(__LINE__, 'false', $reason, ['stage' => 'blacklist']); } } - - if(in_array($v_info->v_ip, $ips_fp_ary) ) { - $_SESSION["check_result"] = "true"; - $reason = "白名单"; - if ($__cloak_debug_on) { - cloak_dbg_step(__LINE__,'IP 白名单', 'pass', 'IP 在白名单组/列表中'); - cloak_dbg_record(__LINE__,'true', $reason, ['stage' => 'whitelist_ip']); - } - } elseif( !empty(WHITE_PARAMS) ) { - $wparams = explode('=', WHITE_PARAMS, 2); - if(count($wparams) == 2) { - $pk = $wparams[0]; - $pv = $wparams[1]; - } - if(isset($_GET[$pk]) && strip_tags($_GET[$pk]) == $pv) { - $_SESSION["check_result"] = "true"; - $reason = "白名单链接参数"; - if ($__cloak_debug_on) { - cloak_dbg_step(__LINE__,'链接参数白名单', 'pass', WHITE_PARAMS); - cloak_dbg_record(__LINE__,'true', $reason, ['stage' => 'whitelist_params']); - } - } - } elseif ($__cloak_debug_on && !empty(WHITE_PARAMS)) { - cloak_dbg_step(__LINE__,'链接参数白名单', 'skip', '参数不匹配: ' . WHITE_PARAMS); - } diff --git a/lib/Cloak/Pipeline/stages/resolve_blacklist_fast_path_hit.inc.php b/lib/Cloak/Pipeline/stages/resolve_blacklist_fast_path_hit.inc.php new file mode 100755 index 0000000..d05f35f --- /dev/null +++ b/lib/Cloak/Pipeline/stages/resolve_blacklist_fast_path_hit.inc.php @@ -0,0 +1,15 @@ + 'blacklist_fast_path']); +} diff --git a/lib/Cloak/Pipeline/stages/resolve_fp_fast_path_hit.inc.php b/lib/Cloak/Pipeline/stages/resolve_fp_fast_path_hit.inc.php new file mode 100755 index 0000000..353e9ea --- /dev/null +++ b/lib/Cloak/Pipeline/stages/resolve_fp_fast_path_hit.inc.php @@ -0,0 +1,17 @@ + 'fp_fast_path']); +} diff --git a/lib/Cloak/Pipeline/stages/resolve_whitelist_fast_path_hit.inc.php b/lib/Cloak/Pipeline/stages/resolve_whitelist_fast_path_hit.inc.php new file mode 100755 index 0000000..c34e13f --- /dev/null +++ b/lib/Cloak/Pipeline/stages/resolve_whitelist_fast_path_hit.inc.php @@ -0,0 +1,17 @@ + 'whitelist_fast_path']); +} diff --git a/lib/Cloak/Pipeline/stages/resolve_zp_fast_path_hit.inc.php b/lib/Cloak/Pipeline/stages/resolve_zp_fast_path_hit.inc.php new file mode 100755 index 0000000..e808808 --- /dev/null +++ b/lib/Cloak/Pipeline/stages/resolve_zp_fast_path_hit.inc.php @@ -0,0 +1,15 @@ + 'zp_fast_path']); +} diff --git a/lib/Cloak/Pipeline/stages/run_main_api_fingerprint.inc.php b/lib/Cloak/Pipeline/stages/run_main_api_fingerprint.inc.php index 3c63716..45bdc0c 100755 --- a/lib/Cloak/Pipeline/stages/run_main_api_fingerprint.inc.php +++ b/lib/Cloak/Pipeline/stages/run_main_api_fingerprint.inc.php @@ -22,6 +22,10 @@ } } + if (($_SESSION['visit_to_2'] ?? '') == '2' && class_exists('CloakJudgmentCache', false) && !CloakJudgmentCache::hasSession()) { + $_SESSION['visit_to_2'] = '1'; + } + if($_SESSION['visit_to_1'] == '1' && $_SESSION['visit_to_2'] != '2') // $_SESSION['visit_to_2'] 为2时表示已经判断过所有规则。 { if ($__cloak_debug_on) { @@ -86,7 +90,7 @@ // 已完成指纹跳转(URL 含 time),但 Cookie 未写入:终止再次重定向 $v_info->check_result = 'false'; - $reason = cloak_reason_or($reason, '指纹Cookie未写入(已完成前端检测)'); + $reason = cloak_reason_or($reason, '已完成前端检测,但指纹Cookie未写入(隐私模式、广告拦截等可能阻止Cookie写入)'); $_SESSION['check_result'] = 'false'; if ($__cloak_debug_on) { cloak_dbg_step(__LINE__, 'JS 指纹检测', 'fail', 'URL 含 time 但指纹 Cookie 缺失,停止重定向'); @@ -149,6 +153,14 @@ if ($__api_return !== null) { $_SESSION["check_result"] = trim($v_info->check_result); $reason = cloak_api_reason($__api_return, $_SESSION["check_result"]); + if (class_exists('CloakJudgmentCache', false)) { + CloakJudgmentCache::storeFromByApi([ + 'ip' => $v_info->v_ip, + 'country' => $v_info->v_country, + 'result' => $_SESSION['check_result'], + 'reason' => $reason, + ]); + } } else { $_SESSION["check_result"] = "false"; $reason = cloak_reason_or($reason, 'API未调用:缺少访客信息(ip/Browser/Accept-Language)'); @@ -175,15 +187,33 @@ } } } elseif (($_SESSION['visit_to_2'] ?? '') == '2') { - if (!empty($_SESSION["check_result"])) { + $cached = class_exists('CloakJudgmentCache', false) ? CloakJudgmentCache::get() : null; + if ($cached !== null) { + CloakJudgmentCache::applyToSession($cached); + CloakJudgmentCache::applyToVisitorInfo($v_info, $cached); + $_SESSION['check_result'] = trim((string) ($cached['result'] ?? 'false')); + if ($reason === '') { + if (CloakJudgmentCache::isPartial($cached)) { + $reason = 'Session byApi阶段缓存:待二次风控'; + } else { + $reason = ($_SESSION['check_result'] === 'true') + ? 'Session API缓存:判定通过' + : '缓存'; + } + } + } elseif (!empty($_SESSION["check_result"])) { if ($reason === '') { $reason = ($_SESSION['check_result'] === 'true') ? 'Session API缓存:判定通过' - : 'Session API缓存:判定拒绝'; + : '缓存'; } } else { - $_SESSION["check_result"] = 'false'; + $_SESSION['check_result'] = 'false'; $reason = cloak_reason_or($reason, 'Session状态异常(visit_to_2=2但无缓存结果),默认安全页'); + $_SESSION['visit_to_2'] = '1'; + if (class_exists('CloakJudgmentCache', false)) { + CloakJudgmentCache::clear(); + } } if ($__cloak_debug_on) { cloak_dbg_step(__LINE__,'主判定块 (API)', 'skip', 'visit_to_2=2,本块已跳过(' . $reason . ')'); diff --git a/lib/Cloak/ReasonHelper.php b/lib/Cloak/ReasonHelper.php index fb8a320..35263ce 100755 --- a/lib/Cloak/ReasonHelper.php +++ b/lib/Cloak/ReasonHelper.php @@ -41,14 +41,47 @@ if (!function_exists('cloak_api_reason')) { } } +if (!function_exists('cloak_cache_hit_log_reason')) { + /** + * 缓存判定落库 reason:true 为空;false 固定为「缓存」。 + * + * @param string $result + */ + function cloak_cache_hit_log_reason($result) + { + return cloak_normalize_log_reason((string) $result, (string) $result === 'true' ? '' : '缓存'); + } +} + +if (!function_exists('cloak_normalize_log_reason')) { + /** + * 落库 reason 规范化:true 必须为空;false 必须有值;wait 保留检测中文案。 + * + * @param string $result + * @param mixed $reason + */ + function cloak_normalize_log_reason($result, $reason) + { + $result = (string) $result; + $reason = trim((string) $reason); + + if ($result === 'true') { + return ''; + } + if ($result === 'wait') { + return $reason !== '' ? $reason : '二次风控检测中'; + } + if ($reason === '') { + return '判定拒绝(安全页)'; + } + return $reason; + } +} + if (!function_exists('cloak_finalize_reason')) { function cloak_finalize_reason($reason, $checkResult) { - $reason = trim((string) $reason); - if ($reason !== '') { - return $reason; - } - $pass = ($checkResult === 'true'); - return $pass ? '判定通过(真实页)' : '判定拒绝(安全页)'; + $result = ($checkResult === 'true') ? 'true' : (($checkResult === 'false') ? 'false' : (string) $checkResult); + return cloak_normalize_log_reason($result, $reason); } } diff --git a/lib/Cloak/RiskSecondaryCache.php b/lib/Cloak/RiskSecondaryCache.php old mode 100644 new mode 100755 index 2391fbe..9338a8b --- a/lib/Cloak/RiskSecondaryCache.php +++ b/lib/Cloak/RiskSecondaryCache.php @@ -1,86 +1,48 @@ $ip, - 'result' => ($payload['result'] ?? '') === 'true' ? 'true' : 'false', - 'reason' => (string) ($payload['reason'] ?? ''), - 'fp_url' => (string) ($payload['fp_url'] ?? ''), - 'risk_number' => defined('CLOAK_RISK_NUMBER') ? (int) CLOAK_RISK_NUMBER : 0, - 'campaign_id' => defined('COSTM_IP_SCORE') ? (string) COSTM_IP_SCORE : '', - 'cached_at' => time(), - ]; + CloakJudgmentCache::storeFromByApiRisk($payload); } /** - * @param string|null $currentIp 为空时使用 ClientIpResolver::resolve() + * 仅返回最终缓存(partial 不视为命中)。 + * + * @param string|null $currentIp * @return array|null */ public static function get($currentIp = null) { - if (empty($_SESSION[self::SESSION_KEY]) || !is_array($_SESSION[self::SESSION_KEY])) { - return null; - } - - $cached = $_SESSION[self::SESSION_KEY]; - $ip = ClientIpResolver::normalizeIp($currentIp !== null && $currentIp !== '' ? $currentIp : ClientIpResolver::resolve()); - $cachedIp = ClientIpResolver::normalizeIp($cached['ip'] ?? ''); - - $invalidate = false; - if ($ip === null || $cachedIp === null || $ip !== $cachedIp) { - $invalidate = true; - } elseif (defined('CLOAK_RISK_NUMBER') && (int) ($cached['risk_number'] ?? -1) !== (int) CLOAK_RISK_NUMBER) { - $invalidate = true; - } elseif (defined('COSTM_IP_SCORE') && (string) ($cached['campaign_id'] ?? '') !== (string) COSTM_IP_SCORE) { - $invalidate = true; - } - - if ($invalidate) { - self::clear(); - $_SESSION['visit_to_3'] = '1'; - return null; - } - - return $cached; + return CloakJudgmentCache::getFinal($currentIp); } public static function clear() { - unset($_SESSION[self::SESSION_KEY]); + CloakJudgmentCache::clear(); } /** - * 将缓存同步到 visit_to_3 / check_result,供 RouteResolver 等复用。 - * * @param array $cached */ public static function applyToSession(array $cached) { - $_SESSION['visit_to_3'] = 3; - $_SESSION['check_result'] = ($cached['result'] ?? '') === 'true' ? 'true' : 'false'; + CloakJudgmentCache::applyToSession($cached); } public static function hitReason() { - return '二次风控缓存命中'; + return CloakJudgmentCache::hitReason(); } /** @@ -89,20 +51,6 @@ class RiskSecondaryCache */ public static function toApiResponse(array $cached) { - $passed = ($cached['result'] ?? '') === 'true'; - $link = (string) ($cached['fp_url'] ?? ''); - - if ($passed && $link === '') { - $link = FpUrlHelper::fallbackUrl(); - } - if (!$passed && $link === '' && defined('DB_ZP')) { - $link = (string) DB_ZP; - } - - return [ - 'result' => $passed, - 'reason' => self::hitReason(), - 'link' => $link, - ]; + return CloakJudgmentCache::toApiResponse($cached); } } diff --git a/lib/Cloak/RiskSecondaryGate.php b/lib/Cloak/RiskSecondaryGate.php index aab93fa..319546e 100755 --- a/lib/Cloak/RiskSecondaryGate.php +++ b/lib/Cloak/RiskSecondaryGate.php @@ -12,4 +12,15 @@ class RiskSecondaryGate $riskNumber = (int) CLOAK_RISK_NUMBER; return $riskNumber > 0 && $riskNumber <= 90; } + + /** + * 当前站点模式是否应执行二次风控(真实页 fp 模式永不触发)。 + */ + public static function isEnabledForRequest() + { + if (defined('SHOW_SITE_MODE_SWITCH') && SHOW_SITE_MODE_SWITCH === 'fp') { + return false; + } + return self::isEnabled(); + } } diff --git a/lib/Cloak/RiskSecondarySession.php b/lib/Cloak/RiskSecondarySession.php new file mode 100755 index 0000000..000b41d --- /dev/null +++ b/lib/Cloak/RiskSecondarySession.php @@ -0,0 +1,41 @@ + 0) { + $_SESSION[self::KEY_WAIT_LOG] = $logId; + VisitorVisitContext::bindLogId($logId); + } + } + + /** + * f_check 已 finalize 后,FpPageRenderer 不再重复写库。 + */ + public static function shouldSkipFpPageLog() + { + if (VisitorVisitContext::isFinalized()) { + return true; + } + if (VisitorVisitContext::currentLogId() > 0 && (string) ($_SESSION['visit_to_3'] ?? '') === '3') { + return true; + } + return false; + } + + public static function clear() + { + unset($_SESSION[self::KEY_WAIT_LOG]); + } +} diff --git a/lib/Cloak/SimulationRouteResolver.php b/lib/Cloak/SimulationRouteResolver.php old mode 100644 new mode 100755 index 10fb966..7191bff --- a/lib/Cloak/SimulationRouteResolver.php +++ b/lib/Cloak/SimulationRouteResolver.php @@ -1,7 +1,9 @@ 'fp_page', + 'log_id' => $log_id, + 'logs' => $logs, + 'needs_secondary' => false, + ]; + } + /** * @param array $logs */ @@ -67,18 +92,19 @@ class SimulationRouteResolver $_SESSION['visit_to_3'] = '1'; } - if (RiskSecondaryGate::isEnabled()) { + if (RiskSecondaryGate::isEnabledForRequest()) { $cached = RiskSecondaryCache::get(ClientIpResolver::resolve()); if ($cached !== null) { return self::fpFromRiskCache($logs, $v_info, $config_name, $cached); } } - if (($_SESSION['visit_to_3'] ?? '1') != '3' && RiskSecondaryGate::isEnabled()) { + if (($_SESSION['visit_to_3'] ?? '1') != '3' && RiskSecondaryGate::isEnabledForRequest()) { $logs['result'] = 'wait'; $logs['reason'] = self::mergeReason($logs['reason'] ?? '', '二次风控检测中'); $logs['fp_url'] = ''; $log_id = write_log_db($logs); + RiskSecondarySession::markWaitLog($log_id); return [ 'branch' => 'secondary_risk', @@ -89,10 +115,10 @@ class SimulationRouteResolver } $logs['result'] = 'true'; - $logs['reason'] = $logs['reason'] ?? ''; + $logs['reason'] = ''; $resolved = FpUrlHelper::resolve($config_name, true); $logs['fp_url'] = $resolved['url'] !== '' ? $resolved['url'] : FpUrlHelper::fallbackUrl(); - $log_id = write_log_db($logs); + $log_id = RiskSecondarySession::shouldSkipFpPageLog() ? null : write_log_db($logs); return [ 'branch' => 'fp_page', @@ -112,7 +138,7 @@ class SimulationRouteResolver $apiResponse = RiskSecondaryCache::toApiResponse($cached); $logs['result'] = $apiResponse['result'] ? 'true' : 'false'; - $logs['reason'] = self::mergeReason($logs['reason'] ?? '', $apiResponse['reason']); + $logs['reason'] = cloak_cache_hit_log_reason($logs['result']); $logs['fp_url'] = trim((string) ($apiResponse['link'] ?? '')); if ($logs['fp_url'] === '') { $logs['fp_url'] = $logs['result'] === 'true' @@ -124,7 +150,7 @@ class SimulationRouteResolver return self::safeBranch($logs, $v_info); } - $log_id = write_log_db($logs); + $log_id = RiskSecondarySession::shouldSkipFpPageLog() ? null : write_log_db($logs); return [ 'branch' => 'fp_page_cache', diff --git a/lib/Cloak/VisitorApiAudit.php b/lib/Cloak/VisitorApiAudit.php index 669d736..2d8af6f 100755 --- a/lib/Cloak/VisitorApiAudit.php +++ b/lib/Cloak/VisitorApiAudit.php @@ -6,10 +6,20 @@ class VisitorApiAudit { const SESSION_BY_REQUEST = 'cloak_api_by_request'; const SESSION_BY_RESPONSE = 'cloak_api_by_response'; + const SESSION_BY_CALLED = 'cloak_api_by_called'; public static function clearByApi() { - unset($_SESSION[self::SESSION_BY_REQUEST], $_SESSION[self::SESSION_BY_RESPONSE]); + unset( + $_SESSION[self::SESSION_BY_REQUEST], + $_SESSION[self::SESSION_BY_RESPONSE], + $_SESSION[self::SESSION_BY_CALLED] + ); + } + + public static function wasByApiCalledThisRequest() + { + return !empty($_SESSION[self::SESSION_BY_CALLED]); } /** @@ -17,6 +27,7 @@ class VisitorApiAudit */ public static function recordByApiRequest($payload) { + $_SESSION[self::SESSION_BY_CALLED] = 1; $_SESSION[self::SESSION_BY_REQUEST] = self::encode($payload); } @@ -39,6 +50,9 @@ class VisitorApiAudit */ public static function mergeIntoLogs(array $logs) { + if (!self::wasByApiCalledThisRequest()) { + return $logs; + } $map = [ 'api_by_request' => self::SESSION_BY_REQUEST, 'api_by_response' => self::SESSION_BY_RESPONSE, diff --git a/lib/Cloak/VisitorLogListHelper.php b/lib/Cloak/VisitorLogListHelper.php old mode 100644 new mode 100755 diff --git a/lib/Cloak/VisitorLogVtimes.php b/lib/Cloak/VisitorLogVtimes.php old mode 100644 new mode 100755 diff --git a/lib/Cloak/VisitorLogWorker.php b/lib/Cloak/VisitorLogWorker.php index 126c13f..8231b53 100755 --- a/lib/Cloak/VisitorLogWorker.php +++ b/lib/Cloak/VisitorLogWorker.php @@ -50,6 +50,9 @@ class VisitorLogWorker if ($type === 'enrich') { return self::enrich((int) ($job['log_id'] ?? 0), $job['logs'] ?? []); } + if ($type === 'update') { + return self::updateFromLogs((int) ($job['log_id'] ?? 0), $job['logs'] ?? []); + } return false; } @@ -208,6 +211,82 @@ class VisitorLogWorker return $ok; } + /** + * 续步 UPDATE:合并同一次 visit 内的日志字段(不修改 vtimes)。 + * + * @param int $logId + * @param array $logs + */ + public static function updateFromLogs($logId, array $logs) + { + $logId = (int) $logId; + if ($logId <= 0) { + return false; + } + $conn = self::connect(); + if (!$conn) { + return false; + } + VisitorLogSchema::ensureColumns($conn); + + $data = self::escapeLogs($conn, $logs); + $sets = []; + $map = [ + 'country' => 'country', + 'referer' => 'v_referer', + 'client' => 'Client', + 'browser' => 'v_Browser', + 'page' => 'v_PageURL', + 'language' => 'accept_language', + 'user_agent' => 'user_agent', + 'http_referer' => 'http_referer', + 'accept_language_raw' => 'accept_language_raw', + 'judge_timing' => 'judge_timing', + 'fp_url' => 'fp_url', + 'device' => 'device', + 'api_by_request' => 'api_by_request', + 'api_by_response' => 'api_by_response', + 'api_risk_request' => 'api_risk_request', + 'api_risk_response' => 'api_risk_response', + ]; + foreach (['result', 'reason', 'fp_url'] as $col) { + if (array_key_exists($col, $logs)) { + $key = array_key_exists($col, $data) ? $col : $col; + $logKey = $col === 'fp_url' ? 'fp_url' : $col; + $val = array_key_exists($logKey, $data) ? $data[$logKey] : cloak_db_escape($conn, $logs[$col]); + $sets[] = "`{$col}`='" . $val . "'"; + } + } + foreach ($map as $col => $logKey) { + if (!array_key_exists($logKey, $data)) { + continue; + } + if ((string) $data[$logKey] === '') { + continue; + } + $sets[] = "`{$col}`='" . $data[$logKey] . "'"; + } + foreach (['fp_hdata', 'api_risk_request', 'api_risk_response'] as $col) { + if (!array_key_exists($col, $logs)) { + continue; + } + $val = array_key_exists($col, $data) ? $data[$col] : cloak_db_escape($conn, $logs[$col]); + $sets[] = "`{$col}`='" . $val . "'"; + } + if ($sets === []) { + $conn->close(); + return true; + } + + $sql = 'UPDATE `visitor_logs` SET ' . implode(', ', array_unique($sets)) . ' WHERE `id`=' . $logId; + $ok = $conn->query($sql) === true; + if (!$ok) { + VisitorLogQueue::logError('updateFromLogs: ' . $conn->error); + } + $conn->close(); + return $ok; + } + /** * @param mysqli $conn * @param array $logs diff --git a/lib/Cloak/VisitorRepository.php b/lib/Cloak/VisitorRepository.php index 3f7acd4..e857c77 100755 --- a/lib/Cloak/VisitorRepository.php +++ b/lib/Cloak/VisitorRepository.php @@ -6,6 +6,9 @@ require_once dirname(__DIR__) . '/DbHelper.php'; require_once dirname(__DIR__) . '/CloakHttpHelpers.php'; require_once __DIR__ . '/VisitorApiAudit.php'; require_once __DIR__ . '/VisitorLogVtimes.php'; +require_once __DIR__ . '/VisitorVisitContext.php'; +require_once __DIR__ . '/RiskSecondarySession.php'; +require_once __DIR__ . '/VisitorLogWorker.php'; if (!function_exists('cloak_session_mark_real_visitor')) { /** @@ -27,6 +30,17 @@ if (!function_exists('cloak_session_mark_real_visitor')) { } setCrossDomainCookie('gfuid', $gcuid, time() + 3600 * 24); + + if (class_exists('CloakJudgmentCache', false)) { + $ip = $_SERVER['REMOTE_ADDR'] ?? ''; + CloakJudgmentCache::storeFromByApiRisk([ + 'ip' => $ip, + 'country' => $_SESSION['gcu_country'] ?? '', + 'result' => 'true', + 'reason' => '', + 'fp_url' => '', + ]); + } } } @@ -43,11 +57,19 @@ if (!function_exists('cloak_session_clear_real_visitor')) { $_SESSION['gcu_country'], $_SESSION['gcu_Client'], $_SESSION['gcu_Browser'], - $_SESSION['gcu_referer'] + $_SESSION['gcu_referer'], + $_SESSION['cloak_whitelist_fast'] ); if (class_exists('RiskSecondaryCache', false)) { RiskSecondaryCache::clear(); } + if (class_exists('CloakJudgmentCache', false)) { + CloakJudgmentCache::clear(); + } + if (class_exists('RiskSecondarySession', false)) { + RiskSecondarySession::clear(); + } + VisitorVisitContext::clear(); setCrossDomainCookie('gfuid', '', time() - 3600); } } @@ -163,49 +185,79 @@ if (!function_exists('cloak_write_log_db_sync')) { } } -if (!function_exists('cloak_write_log_db')) { - function cloak_write_log_db($logs) +if (!function_exists('cloak_update_log_db')) { + /** + * 同 visit 续步 UPDATE(不新增 vtimes)。 + * + * @param int $logId + * @param array $logs + * @return int|false log id on success + */ + function cloak_update_log_db($logId, array $logs, array $options = []) { - $logs = cloak_visitor_log_enrich($logs); + $logId = (int) $logId; + if ($logId <= 0) { + return false; + } + if (empty($options['skip_enrich'])) { + $logs = cloak_visitor_log_enrich($logs); + } + $logs = VisitorVisitContext::normalizeLogsForDb($logs); - if (!empty($GLOBALS['__cloak_force_sync_log'])) { - if (($logs['result'] ?? '') === 'wait') { - $logId = VisitorLogWorker::insertMinimal($logs); - if ($logId !== null) { - VisitorLogWorker::enrich($logId, $logs); - } - return $logId; + $ok = false; + $forceSync = !empty($GLOBALS['__cloak_force_sync_log']) + || !cloak_visitor_log_async_enabled() + || VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? ''); + if ($forceSync) { + $ok = VisitorLogWorker::updateFromLogs($logId, $logs); + } else { + if (VisitorLogQueue::push(['type' => 'update', 'log_id' => $logId, 'logs' => $logs])) { + cloak_visitor_log_register_shutdown_drain(); + $ok = true; + } else { + $ok = VisitorLogWorker::updateFromLogs($logId, $logs); } - return cloak_write_log_db_sync($logs); } - if (defined('WRITE_LOG') && WRITE_LOG == '1') { - $my_url = $_SERVER['PHP_SELF'] ?? ''; - $my_file_name = substr($my_url, strrpos($my_url, '/') + 1); - $config_name = str_replace('.php', '', $my_file_name); - $fp = fopen(dirname(__DIR__, 2) . '/jump.txt', 'a+'); - fwrite($fp, date('Y-m-d H:i:s') . ' | 写入数据库 ' . ' | ' . ($_SESSION['check_result'] ?? '') . ' | ' . $config_name . ' | async=' . (cloak_visitor_log_async_enabled() ? '1' : '0') . "\n"); - fclose($fp); + if ($ok && VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '')) { + VisitorVisitContext::finalizeVisit($logs['result'] ?? ''); } + return $ok ? $logId : false; + } +} +if (!function_exists('cloak_write_log_db_wait_sync')) { + /** + * 二次风控 wait:同步完整 INSERT(需立即 log_id,且必须带上 byApi/页面/UA 等字段)。 + * + * @param array $logs 已 enrich + normalize + * @return int|null + */ + function cloak_write_log_db_wait_sync(array $logs) + { + $logId = VisitorLogWorker::insertFull($logs); + if ($logId === null) { + $logId = cloak_write_log_db_sync($logs); + } + return $logId; + } +} + +if (!function_exists('cloak_write_log_db_insert')) { + /** + * @param array $logs + * @return int|null + */ + function cloak_write_log_db_insert(array $logs) + { $result = isset($logs['result']) ? (string) $logs['result'] : ''; - // 二次风控 wait:同步最小 INSERT 获取 log_id,TEXT 字段异步 enrich if ($result === 'wait') { - $logId = VisitorLogWorker::insertMinimal($logs); - if ($logId === null) { - return cloak_write_log_db_sync($logs); - } - if (cloak_visitor_log_async_enabled()) { - VisitorLogQueue::push([ - 'type' => 'enrich', - 'log_id' => $logId, - 'logs' => $logs, - ]); - } else { - VisitorLogWorker::enrich($logId, $logs); - } - return $logId; + return cloak_write_log_db_wait_sync($logs); + } + + if (!empty($GLOBALS['__cloak_force_sync_log'])) { + return cloak_write_log_db_sync($logs); } if (!cloak_visitor_log_async_enabled()) { @@ -284,6 +336,56 @@ if (!function_exists('cloak_write_log_db')) { } } +if (!function_exists('cloak_write_log_db')) { + function cloak_write_log_db($logs) + { + $logs = cloak_visitor_log_enrich($logs); + $logs = VisitorVisitContext::normalizeLogsForDb($logs); + + if (VisitorVisitContext::shouldPersistAsUpdate()) { + return cloak_update_log_db(VisitorVisitContext::currentLogId(), $logs); + } + + VisitorVisitContext::prepareForNewInsert(); + + if (!empty($GLOBALS['__cloak_force_sync_log'])) { + if (($logs['result'] ?? '') === 'wait') { + $logId = cloak_write_log_db_wait_sync($logs); + if ($logId !== null) { + VisitorVisitContext::bindLogId($logId); + } + return $logId; + } + $logId = cloak_write_log_db_sync($logs); + if ($logId !== null) { + VisitorVisitContext::bindLogId($logId); + if (VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '')) { + VisitorVisitContext::finalizeVisit($logs['result'] ?? ''); + } + } + return $logId; + } + + if (defined('WRITE_LOG') && WRITE_LOG == '1') { + $my_url = $_SERVER['PHP_SELF'] ?? ''; + $my_file_name = substr($my_url, strrpos($my_url, '/') + 1); + $config_name = str_replace('.php', '', $my_file_name); + $fp = fopen(dirname(__DIR__, 2) . '/jump.txt', 'a+'); + fwrite($fp, date('Y-m-d H:i:s') . ' | 写入数据库 ' . ' | ' . ($_SESSION['check_result'] ?? '') . ' | ' . $config_name . ' | async=' . (cloak_visitor_log_async_enabled() ? '1' : '0') . "\n"); + fclose($fp); + } + + $logId = cloak_write_log_db_insert($logs); + if ($logId !== null) { + VisitorVisitContext::bindLogId($logId); + if (VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '')) { + VisitorVisitContext::finalizeVisit($logs['result'] ?? ''); + } + } + return $logId; + } +} + if (!function_exists('write_log_db')) { function write_log_db($logs) { diff --git a/lib/Cloak/VisitorSimulationRunner.php b/lib/Cloak/VisitorSimulationRunner.php old mode 100644 new mode 100755 diff --git a/lib/Cloak/VisitorVisitContext.php b/lib/Cloak/VisitorVisitContext.php new file mode 100755 index 0000000..9203303 --- /dev/null +++ b/lib/Cloak/VisitorVisitContext.php @@ -0,0 +1,175 @@ + 0 && !self::isFinalized()) { + return true; + } + if (!empty($_SESSION[self::KEY_ACTIVE]) && self::currentLogId() <= 0) { + return true; + } + return false; + } + + /** + * INSERT 前:已 finalize 或刷新后的新 visit 清除旧绑定。 + */ + public static function prepareForNewInsert() + { + if (!self::isNewVisit()) { + return; + } + $hadFinalized = self::isFinalized(); + unset($_SESSION[self::KEY_FINALIZED]); + if ($hadFinalized || self::isRefreshNavigation()) { + unset($_SESSION[self::KEY_LOG_ID]); + } + if (empty($_SESSION[self::KEY_ACTIVE])) { + self::markVisitActive(); + } + if (class_exists('VisitorApiAudit', false)) { + VisitorApiAudit::clearByApi(); + } + } + + public static function shouldPersistAsUpdate() + { + return self::currentLogId() > 0 && !self::isFinalized() && !self::isNewVisit(); + } + + /** + * @param int $logId + */ + public static function bindLogId($logId) + { + $logId = (int) $logId; + if ($logId > 0) { + $_SESSION[self::KEY_LOG_ID] = $logId; + } + } + + /** + * @param string|null $result + */ + public static function finalizeVisit($result = null) + { + $_SESSION[self::KEY_FINALIZED] = 1; + unset($_SESSION[self::KEY_ACTIVE]); + } + + /** + * @param string $result + */ + public static function shouldFinalizeOnResult($result) + { + $r = (string) $result; + return $r === 'true' || $r === 'false'; + } + + /** + * @param array $logs + * @return array + */ + public static function normalizeLogsForDb(array $logs) + { + $result = (string) ($logs['result'] ?? ''); + if (function_exists('cloak_normalize_log_reason')) { + $logs['reason'] = cloak_normalize_log_reason($result, $logs['reason'] ?? ''); + } + return $logs; + } +} diff --git a/lib/Cloak/WhitelistGate.php b/lib/Cloak/WhitelistGate.php new file mode 100755 index 0000000..caff2a8 --- /dev/null +++ b/lib/Cloak/WhitelistGate.php @@ -0,0 +1,106 @@ + 0) { + return ip_group_list_addresses((int) WHITELIST_GROUP_ID); + } + if (!defined('SHOW_SITE_IP') || SHOW_SITE_IP === '') { + return []; + } + + return array_values(array_filter(array_map('trim', explode(',', SHOW_SITE_IP)), static function ($v) { + return $v !== ''; + })); + } + + /** + * @param string $ip + */ + public static function matchesWhitelistIp($ip) + { + $ip = trim((string) $ip); + if ($ip === '') { + return false; + } + + foreach (self::getWhitelistIps() as $entry) { + if (function_exists('ip_visitor_matches_list_entry') && ip_visitor_matches_list_entry($ip, $entry)) { + return true; + } + if ($ip === $entry) { + return true; + } + } + + return false; + } + + public static function matchesWhitelistParams() + { + if (!defined('WHITE_PARAMS') || WHITE_PARAMS === '') { + return false; + } + $wparams = explode('=', WHITE_PARAMS, 2); + if (count($wparams) !== 2) { + return false; + } + $pk = $wparams[0]; + $pv = $wparams[1]; + + return isset($_GET[$pk]) && strip_tags((string) $_GET[$pk]) === $pv; + } + + /** + * @param visitorInfo|object|null $v_info + */ + public static function resolveVisitorIp($v_info = null) + { + $ip = ''; + if ($v_info !== null && isset($v_info->v_ip)) { + $ip = (string) $v_info->v_ip; + } + if ($ip === '' || $ip === '0.0.0.0') { + $ip = ClientIpResolver::resolve(); + } + return $ip; + } + + /** + * @param visitorInfo|object|null $v_info + */ + public static function isMatched($v_info = null) + { + if (self::matchesWhitelistParams()) { + return true; + } + + return self::matchesWhitelistIp(self::resolveVisitorIp($v_info)); + } + + /** + * 优先级:链接参数白名单 > IP 白名单 + * + * @param visitorInfo|object|null $v_info + */ + public static function matchReason($v_info = null) + { + if (self::matchesWhitelistParams()) { + return '白名单链接参数'; + } + if (self::matchesWhitelistIp(self::resolveVisitorIp($v_info))) { + return '白名单'; + } + + return '白名单'; + } +} diff --git a/lib/CloakHttpHelpers.php b/lib/CloakHttpHelpers.php index bd762c5..31d1bc8 100755 --- a/lib/CloakHttpHelpers.php +++ b/lib/CloakHttpHelpers.php @@ -165,34 +165,208 @@ if (!function_exists('write_nt_list')) { } } +if (!function_exists('cloak_normalize_request_host')) { + /** + * 规范化请求 Host:小写、去端口、去首尾空白。 + * + * @param string|null $host + */ + function cloak_normalize_request_host($host = null) + { + $host = strtolower(trim((string) ($host ?? ($_SERVER['HTTP_HOST'] ?? '')))); + if ($host === '') { + return ''; + } + if ($host[0] === '[') { + $end = strpos($host, ']'); + if ($end !== false) { + return substr($host, 0, $end + 1); + } + } + $colon = strpos($host, ':'); + if ($colon !== false) { + $host = substr($host, 0, $colon); + } + return rtrim($host, '.'); + } +} + +if (!function_exists('cloak_host_is_ip')) { + /** + * @param string $host + */ + function cloak_host_is_ip($host) + { + $host = trim($host); + if ($host === '') { + return false; + } + if ($host[0] === '[' && substr($host, -1) === ']') { + return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP) !== false; + } + return filter_var($host, FILTER_VALIDATE_IP) !== false; + } +} + +if (!function_exists('cloak_host_is_local')) { + /** + * localhost / 单标签主机名(不含点)视为 host-only Cookie。 + * + * @param string $host + */ + function cloak_host_is_local($host) + { + $host = strtolower(trim($host)); + if ($host === '' || strpos($host, '.') === false) { + return true; + } + return $host === 'localhost' + || substr($host, -6) === '.local' + || substr($host, -5) === '.test' + || substr($host, -13) === '.localhost'; + } +} + +if (!function_exists('cloak_cookie_public_suffixes')) { + /** + * 常见多级公共后缀(co.uk / com.cn 等),避免 Cookie domain 算成 .co.uk。 + * + * @return string[] + */ + function cloak_cookie_public_suffixes() + { + return [ + 'co.uk', 'org.uk', 'ac.uk', 'gov.uk', 'net.uk', + 'com.cn', 'net.cn', 'org.cn', 'gov.cn', + 'com.au', 'net.au', 'org.au', 'edu.au', + 'co.jp', 'ne.jp', 'or.jp', + 'com.br', 'com.mx', 'com.tw', 'com.hk', 'co.nz', 'co.kr', 'com.sg', + 'com.tr', 'com.pl', 'com.ar', 'com.co', 'co.in', 'com.my', + ]; + } +} + +if (!function_exists('cloak_normalize_cookie_domain_config')) { + /** + * 配置项 CLOAK_COOKIE_DOMAIN → Cookie 用 domain(带点前缀,如 .shili.buzz) + * + * @param string $value + */ + function cloak_normalize_cookie_domain_config($value) + { + $value = strtolower(trim($value)); + if ($value === '') { + return ''; + } + $value = ltrim($value, '.'); + if ($value === '' || cloak_host_is_ip($value) || cloak_host_is_local($value)) { + return ''; + } + return '.' . $value; + } +} + +if (!function_exists('cloak_registrable_domain')) { + /** + * 从 Host 推断可注册域(不含前导点),IP/localhost 返回空。 + * + * @param string|null $host + */ + function cloak_registrable_domain($host = null) + { + if (defined('CLOAK_COOKIE_DOMAIN') && (string) CLOAK_COOKIE_DOMAIN !== '') { + $configured = cloak_normalize_cookie_domain_config((string) CLOAK_COOKIE_DOMAIN); + return $configured !== '' ? ltrim($configured, '.') : ''; + } + + $host = cloak_normalize_request_host($host); + if ($host === '' || cloak_host_is_ip($host) || cloak_host_is_local($host)) { + return ''; + } + + $parts = explode('.', $host); + $count = count($parts); + if ($count < 2) { + return ''; + } + + $suffix2 = $parts[$count - 2] . '.' . $parts[$count - 1]; + if (in_array($suffix2, cloak_cookie_public_suffixes(), true)) { + if ($count < 3) { + return ''; + } + return $parts[$count - 3] . '.' . $suffix2; + } + + return $suffix2; + } +} + if (!function_exists('cloak_fingerprint_cookie_domain')) { /** - * 指纹 Cookie 使用的跨子域 domain(与 setCrossDomainCookie 一致) + * 指纹 / Session Cookie 跨子域 domain(带点,如 .shili.buzz);IP/localhost 返回空(host-only)。 */ function cloak_fingerprint_cookie_domain() { - if (empty($_SERVER['HTTP_HOST'])) { - return ''; + if (defined('CLOAK_COOKIE_DOMAIN') && (string) CLOAK_COOKIE_DOMAIN !== '') { + return cloak_normalize_cookie_domain_config((string) CLOAK_COOKIE_DOMAIN); } - $host_names = explode('.', $_SERVER['HTTP_HOST']); - if (count($host_names) < 2) { - return ''; - } + $registrable = cloak_registrable_domain(); + return $registrable !== '' ? '.' . $registrable : ''; + } +} - return '.' . $host_names[count($host_names) - 2] . '.' . $host_names[count($host_names) - 1]; +if (!function_exists('cloak_fingerprint_js_cookie_options')) { + /** + * 前端 js-cookie 写入指纹 Cookie 的选项(与 PHP setcookie 策略一致)。 + * + * @return array{path:string,expires:int,sameSite:string,domain?:string,secure?:bool} + */ + function cloak_fingerprint_js_cookie_options() + { + $opts = [ + 'path' => '/', + 'expires' => 1, + 'sameSite' => 'Lax', + ]; + $domain = cloak_fingerprint_cookie_domain(); + if ($domain !== '') { + $opts['domain'] = $domain; + } + if (cloak_request_is_https()) { + $opts['secure'] = true; + } + return $opts; } } if (!function_exists('cloak_set_cross_domain_cookie')) { function cloak_set_cross_domain_cookie($name, $value, $expire) { - $bottom_host_name = cloak_fingerprint_cookie_domain(); - if ($bottom_host_name === '') { - setcookie($name, $value, $expire, '/'); + $cookieDomain = cloak_fingerprint_cookie_domain(); + $secure = cloak_request_is_https(); + + if (PHP_VERSION_ID >= 70300) { + $opts = [ + 'expires' => (int) $expire, + 'path' => '/', + 'secure' => $secure, + 'httponly' => false, + 'samesite' => 'Lax', + ]; + if ($cookieDomain !== '') { + $opts['domain'] = ltrim($cookieDomain, '.'); + } + setcookie($name, $value, $opts); return; } - setcookie($name, $value, $expire, '/', $bottom_host_name); + + if ($cookieDomain === '') { + setcookie($name, $value, (int) $expire, '/', '', $secure, false); + return; + } + setcookie($name, $value, (int) $expire, '/', $cookieDomain, $secure, false); } } diff --git a/lib/DbHelper.php b/lib/DbHelper.php index af910f1..8444a6e 100755 --- a/lib/DbHelper.php +++ b/lib/DbHelper.php @@ -107,19 +107,22 @@ if (!function_exists('cloak_visitor_log_fp_risk_explain')) { if (!function_exists('cloak_visitor_log_apply_result_filter')) { /** - * 日志列表 result 筛选:默认排除 wait(检测中);显式选 wait/true/false 时精确匹配 + * 日志列表 result 筛选:默认显示全部(含 wait/检测中);显式筛选时精确匹配 * * @return string SQL 片段(含前导 AND) */ function cloak_visitor_log_apply_result_filter(mysqli $conn, $resultParam): string { $resultParam = trim((string) $resultParam); - if ($resultParam === 'wait') { + if ($resultParam === '') { + return ''; + } + if ($resultParam === 'wait' || $resultParam === '检测中') { return " AND `result` = 'wait'"; } if ($resultParam === 'true' || $resultParam === 'false') { return " AND `result` = '" . cloak_db_escape($conn, $resultParam) . "'"; } - return " AND (`result` IS NULL OR `result` <> 'wait')"; + return ''; } } diff --git a/lib/bootstrap.php b/lib/bootstrap.php index d24f2ce..c3a67e9 100755 --- a/lib/bootstrap.php +++ b/lib/bootstrap.php @@ -25,12 +25,16 @@ require_once __DIR__ . '/Cloak/GeoIpCountryCache.php'; require_once __DIR__ . '/Cloak/GeoIpCountryResolver.php'; require_once __DIR__ . '/Cloak/CountryCodeNormalizer.php'; require_once __DIR__ . '/Cloak/CountryAllowlist.php'; +require_once __DIR__ . '/Cloak/WhitelistGate.php'; require_once __DIR__ . '/Cloak/RiskSecondaryGate.php'; +require_once __DIR__ . '/Cloak/CloakJudgmentCache.php'; require_once __DIR__ . '/Cloak/RiskSecondaryCache.php'; require_once __DIR__ . '/Cloak/VisitorLogSchema.php'; require_once __DIR__ . '/Cloak/VisitorApiAudit.php'; require_once __DIR__ . '/Cloak/VisitorLogQueue.php'; require_once __DIR__ . '/Cloak/VisitorLogWorker.php'; +require_once __DIR__ . '/Cloak/VisitorVisitContext.php'; +require_once __DIR__ . '/Cloak/RiskSecondarySession.php'; require_once __DIR__ . '/Cloak/Pipeline/CheckPipeline.php'; require_once __DIR__ . '/Cloak/Page/FingerprintRedirectRenderer.php'; require_once __DIR__ . '/Cloak/Page/FpPageRenderer.php'; diff --git a/resources/virtual_device_detect.inc.php b/resources/virtual_device_detect.inc.php index 72fbaff..e602661 100755 --- a/resources/virtual_device_detect.inc.php +++ b/resources/virtual_device_detect.inc.php @@ -1,7 +1,7 @@ - diff --git a/simulate_test.php b/simulate_test.php old mode 100644 new mode 100755 diff --git a/storage/runtime/c7f2a9e1/cc_cache/19e36255972107d42b8cecb77ef5622e842e8a50778a6ed8dd1ce94732daca9e.json b/storage/runtime/c7f2a9e1/cc_cache/19e36255972107d42b8cecb77ef5622e842e8a50778a6ed8dd1ce94732daca9e.json new file mode 100755 index 0000000..7a29d15 --- /dev/null +++ b/storage/runtime/c7f2a9e1/cc_cache/19e36255972107d42b8cecb77ef5622e842e8a50778a6ed8dd1ce94732daca9e.json @@ -0,0 +1 @@ +{"value":"__NONE__","expires_at":1784140663} \ No newline at end of file diff --git a/storage/runtime/c7f2a9e1/cc_cache/631f08140b24b7274d12df3c37a1a80ce5876dafd7007d772e0114fddf88b682.json b/storage/runtime/c7f2a9e1/cc_cache/631f08140b24b7274d12df3c37a1a80ce5876dafd7007d772e0114fddf88b682.json new file mode 100755 index 0000000..7a29d15 --- /dev/null +++ b/storage/runtime/c7f2a9e1/cc_cache/631f08140b24b7274d12df3c37a1a80ce5876dafd7007d772e0114fddf88b682.json @@ -0,0 +1 @@ +{"value":"__NONE__","expires_at":1784140663} \ No newline at end of file diff --git a/storage/runtime/c7f2a9e1/cc_cache/8959edf6884f2c2e223a9319e83a69820ff7a9c39ecd7ebe6413e5297d4347dd.json b/storage/runtime/c7f2a9e1/cc_cache/8959edf6884f2c2e223a9319e83a69820ff7a9c39ecd7ebe6413e5297d4347dd.json new file mode 100755 index 0000000..7a29d15 --- /dev/null +++ b/storage/runtime/c7f2a9e1/cc_cache/8959edf6884f2c2e223a9319e83a69820ff7a9c39ecd7ebe6413e5297d4347dd.json @@ -0,0 +1 @@ +{"value":"__NONE__","expires_at":1784140663} \ No newline at end of file diff --git a/table.php b/table.php index bcfc580..4592939 100755 --- a/table.php +++ b/table.php @@ -373,7 +373,7 @@ if(empty($_SESSION['password']) || $_SESSION['password'] != LOGIN_PASSWORD) {