Как создать «слепок» устройства на PHP и JS для блокировки
Размер текста: A+ A-

Как создать «слепок» устройства на PHP и JS для блокировки

Нажмите, чтобы оценить наш труд:
[Всего: 1 Средняя: 5]

Каждый раз, когда вы заходите на сайт, ваш браузер оставляет цифровой отпечаток. Даже если вы почистите куки, смените IP или откроете «инкогнито», по этим скрытым меткам вас всё равно могут узнать и заблокировать. Речь идёт о технологии «слепка» (или фингерпринта) устройства.

В этой статье я разберу, как с помощью PHP, JavaScript и HTML5 можно собрать уникальные характеристики гаджета пользователя, чтобы надёжно отличать одного посетителя от другого и, при необходимости, запрещать доступ нарушителям.

Что такое «слепок» устройства и зачем он нужен ?

Представьте, что вы на входе в магазин фотографируете не лицо человека, а его ботинки, походку, любимую руку и манеру открывать дверь.

Составить полный портрет можно и без лица. «Слепок» устройства работает так же: он собирает не логин и пароль, а «почерк» браузера и компьютера. Это нужно, чтобы, например, заблокировать навязчивого спамера, который уже создал сотню фейковых аккаунтов, или обезвредить бота, который пытается украсть данные.

В отличие от куки, которые можно просто удалить, «слепок» остаётся с устройством на гораздо более долгий срок. Специалисты EFF (Electronic Frontier Foundation) выяснили, что более 80 процентов браузеров имеют уникальный набор таких параметров. Это значит, что по совокупности мелких признаков систему можно обмануть, но сделать это технически сложно.

Прочие материалы по теме:

Все возможные критерии (метрики) для создания «слепка»

Современные методы сбора информации можно разделить на несколько слоёв. Чем больше параметров вы возьмёте, тем точнее будет отпечаток.

1. Базовые свойства браузера и ОС. Самый простой и очевидный слой. Сюда входят версия браузера, язык интерфейса, временная зона, тип операционной системы, установленные шрифты и список плагинов. Эти данные легко получить через JavaScript и объект navigator. Их можно подделать, но их сочетание с другими, более сложными параметрами даёт хорошую точность.

2. Параметры экрана и графики. Каждый монитор имеет своё разрешение, цветовую глубину и соотношение пикселей. Но самый сильный метод здесь — Canvas-фингерпринтинг. Браузер в фоновом режиме рисует на HTML5-холсте невидимую картинку, а затем превращает её в хеш. Из-за различий в видеокартах, драйверах и алгоритмах сглаживания итоговый хеш будет уникальным для каждой связки «компьютер + браузер».

3. Аппаратные и звуковые особенности. Через WebGL можно узнать модель вашей видеокарты и драйвера. А через Web Audio API — как устройство обрабатывает звук. Даже если вы никогда не слушаете музыку на сайте, скрипт может сгенерировать неслышный сигнал и проанализировать искажения, вносимые вашей звуковой картой. Это один из самых стабильных идентификаторов.

4. Поведенческие метрики (клики и движения мыши). Это уже высший пилотаж. Здесь фиксируется, как человек двигает курсором: плавно или рывками, по каким траекториям, как быстро нажимает кнопки. Боты обычно двигаются по идеально прямой линии с одинаковой скоростью, а человек — с ускорениями, замедлениями и небольшими «дрожаниями» руки.

Как это реализовать: примеры кода

Дисклеймер: Все приведённые ниже примеры кода, архитектурные схемы и рассуждения о методах сбора отпечатков устройств — это не готовое коммерческое решение, а лишь иллюстрация идей и демонстрация принципов работы. Для внедрения в реальный проект необходимо учитывать конкретную среду выполнения (браузеры, ограничения CSP, политику безопасности) и особенности инфраструктуры. Предложенные фрагменты требуют обязательной адаптации, рефакторинга и тестирования под вашу уникальную бизнес-логику и нагрузку.

Итак, всё начинается на фронтенде. С помощью JavaScript мы собираем все параметры и передаём их на сервер через AJAX.

1. Сбор Canvas-отпечатка. Один из самых надёжных методов. Скрипт создаёт скрытое изображение с текстом и фоном, а затем вычисляет его хеш.

async function getCanvasFingerprint() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 200;
canvas.height = 50;
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillStyle = '#f60';
ctx.fillRect(0, 0, 100, 30);
ctx.fillStyle = '#069';
ctx.fillText('Browser Fingerprint', 5, 15);
const dataURL = canvas.toDataURL();
const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(dataURL));
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

2. Сбор WebGL-данных (видеокарта). Позволяет узнать не только модель GPU, но и особенности рендеринга.

function getWebGLFingerprint() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
if (!gl) return 'no_webgl';
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
return `${vendor}~${renderer}`;
}
return 'no_debug_info';
}

3. Сбор Audio-отпечатка (звуковая карта). Генерируется неслышный сигнал, и анализируются искажения.

async function getAudioFingerprint() {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioCtx.createOscillator();
const analyser = audioCtx.createAnalyser();
oscillator.connect(analyser);
oscillator.type = 'sine';
oscillator.frequency.value = 1000;
const dataArray = new Uint8Array(analyser.frequencyBinCount);
oscillator.start();
analyser.getByteFrequencyData(dataArray);
oscillator.stop();
await audioCtx.close();
const hashBuffer = await crypto.subtle.digest('SHA-256', dataArray);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

4. Сбор остальных свойств. Всё это объединяется в один объект.

function getBasicFingerprint() {
return {
userAgent: navigator.userAgent,
language: navigator.language,
platform: navigator.platform,
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory,
screenResolution: `${screen.width}x${screen.height}`,
colorDepth: screen.colorDepth,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
plugins: Array.from(navigator.plugins).map(p => p.name).join(','),
canvas: await getCanvasFingerprint(),
webgl: getWebGLFingerprint(),
audio: await getAudioFingerprint()
};
}

5. Отправка данных на сервер и их обработка на PHP. После сбора объект отправляется методом POST в формате JSON. На бэкенде нужно очистить данные, чтобы избежать подделок, и сохранить их.

$data = json_decode(file_get_contents('php://input'), true);
$userAgent = preg_replace('/\s+/', ' ', trim($data['userAgent'] ?? ''));
$screen = preg_replace('/[^0-9x]/', '', $data['screenResolution'] ?? '');
$canvasHash = preg_replace('/[^a-f0-9]/', '', $data['canvas'] ?? '');
$webgl = preg_replace('/[^a-zA-Z0-9~_\-]/', '', $data['webgl'] ?? '');
$audio = preg_replace('/[^a-f0-9]/', '', $data['audio'] ?? '');
$fingerprintRaw = $userAgent . '|' . $screen . '|' . $data['colorDepth'] . '|' . $data['timezone'] . '|' . $canvasHash . '|' . $webgl . '|' . $audio;
$fingerprintHash = hash_hmac('sha256', $fingerprintRaw, 'YOUR_SECRET_KEY');

Выше, когда я писал про «слепок» пользователя, я не акцентировал IP-методы.

Ниже добавляю такие методы отдельно.

1. Получение IP через PHP (серверная сторона)

Самый простой и надёжный способ — взять IP из переменных сервера. Но нужно учитывать, что клиент может быть за прокси или CDN.

function getUserIP() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
// IP может быть списком, берём первый
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return trim($ips[0]);
} else {
return $_SERVER['REMOTE_ADDR'];
}
}

REMOTE_ADDR — это адрес, с которого установлено TCP-соединение. При использовании прокси или Cloudflare нужно смотреть HTTP_X_FORWARDED_FOR (первый IP в списке — реальный клиент). Однако эти заголовки легко подделать, поэтому для критических действий их нельзя использовать как единственный идентификатор.

2. Получение IP через JavaScript (сторонние сервисы)

Браузерный JavaScript не имеет прямого доступа к IP-адресу пользователя (из соображений безопасности). Но можно отправить запрос на специальный сервис, который вернёт IP.

fetch('https://api.ipify.org?format=json')
.then(response => response.json())
.then(data => console.log('Ваш IP:', data.ip));

Альтернативы: https://api.my-ip.io/ip.jsonhttps://ipapi.co/json/. Полученный IP можно отправить на ваш сервер и использовать для анализа (например, заблокировать диапазон или сравнить с IP из сессии).

Этот метод показывает IP, который видит внешний сервис. Он полезен, когда ваш сервер находится за прокси, а вам нужно узнать «публичный» IP клиента. Однако пользователь может использовать VPN, и тогда IP будет не его реальным местоположением.

3. Геолокация по IP и другие сетевые параметры (через API)

IP сам по себе не даёт много информации, но его можно обогатить через внешние базы данных.

fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
console.log(data.city, data.region, data.country_name);
console.log('Провайдер:', data.org);
});

Также можно получить параметры сети клиента через JavaScript API navigator.connection (экспериментальный, но поддерживается в современных браузерах):

const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (connection) {
console.log('Тип сети:', connection.effectiveType); // '4g', '3g', '2g'
console.log('RTT (мс):', connection.rtt);
console.log('Downlink (Мбит/с):', connection.downlink);
}

 Эти метрики не дают точного местоположения, но помогают отличить бота (у которого часто нет реального navigator.connection) от человека, а также выявить резкие смены типа сети (например, переход с Ethernet на Wi-Fi). Однако они могут меняться без смены устройства, поэтому их не стоит включать в основной «слепок» без специальной логики.

Определение MAC адреса устройства через PHP / JS

Прямо получить MAC-адрес пользователя через PHP или JavaScript невозможно — это серьезное ограничение безопасности.

JavaScript в браузере не имеет доступа к аппаратному адресу сетевой карты по определению. Единственное исключение — когда пользователь сам устанавливает и разрешает доступ специальному расширению или десктопному приложению, но это уже совсем другая история.

Есть также “дырявый” метод через WebRTC, который может вычислить локальный IP, но это не MAC-адрес, и современные браузеры его уже давно закрыли. На стороне сервера в PHP можно получить MAC-адрес клиента, если веб-сервер и клиент находятся в одной локальной сети.

Самый простой способ — выполнить системную команду arp -a, которая выводит таблицу соответствия IP и MAC-адресов.

Пример кода (PHP) получения MAC-адреса в локальной сети:

<?php
$ip = $_SERVER['REMOTE_ADDR'];
// Проверяем, что IP в локальной сети (для простоты)
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
// Выполняем команду arp
$output = shell_exec("arp -a " . escapeshellarg($ip));
// Ищем MAC-адрес в выводе
if (preg_match('/([a-f0-9]{2}[-:]){5}[a-f0-9]{2}/i', $output, $matches)) {
echo "MAC-адрес: " . $matches[0];
} else {
echo "MAC-адрес не найден";
}
} else {
echo "IP не является локальным";
}
?>

Этот код сработает, только если веб-сервер и клиент находятся в одной подсети (например, в корпоративной сети). В реальных веб-условиях (Интернет) это не работает и не имеет смысла. Для глобальной сети надежной идентификации по MAC не существует.

Серверная логика, хранение и блокировка

Разберем сначала методы серверной логики для «слепка» пользователя.

После того как на сервер пришёл хеш «слепка», его нужно связать с учётной записью или поместить в чёрный список.

Здесь кроется главное правило: фингерпринт — это не пароль, а сигнал. Его нельзя использовать как единственное основание для блокировки, иначе вы заблокируете полгорода, сидящего в одном общежитии.

Хранение в базе данных (MySQL)

Лучше всего хранить разрешённые устройства в JSON-поле таблицы пользователей.

ALTER TABLE users ADD COLUMN trusted_devices JSON DEFAULT NULL;

Логика работы при логине:

  1. Пользователь вводит пароль.

  2. Сервер вычисляет текущий хеш «слепка» (на основе данных, присланных с фронтенда).

  3. Сервер ищет этот хеш в колонке trusted_devices текущего пользователя.

  4. Если хеш найден — вход разрешён.

  5. Если не найден — пользователю отправляется код подтверждения (по почте или СМС). После успешного ввода кода этот хеш добавляется в массив trusted_devices.

Такой подход безопасен, так как злоумышленник не сможет добавить своё устройство без доступа к почте/телефону.

$trusted = json_decode($user['trusted_devices'] ?? '[]', true);
if (!in_array($fingerprintHash, $trusted)) {
// Отправить код подтверждения
$_SESSION['pending_fingerprint'] = $fingerprintHash;
exit('verify_required');
}
// Верификация пройдена, вход выполнен

Теперь коснемся логики блокировки по IP.

IP-адрес — это самый доступный, но и самый ненадёжный идентификатор. Его можно использовать как вспомогательный сигнал в паре с другими методами, но не как единственный критерий.

Пример. Простая блокировка по чёрному списку IP

$blacklistedIps = ['192.168.1.100', '203.0.113.5'];
$userIp = getUserIP(); // функция из предыдущего сообщения
if (in_array($userIp, $blacklistedIps)) {
die('Доступ запрещён. Ваш IP в чёрном списке.');
}

Этот метод полезен для точечной блокировки конкретных нарушителей. Но многие пользователи имеют динамические IP (меняются при перезагрузке роутера) или используют VPN/прокси, поэтому чёрный список быстро устаревает. Кроме того, один IP может быть разделён между сотнями людей (NAT в офисах или у мобильных операторов), и вы заблокируете невинных.

Пример. Ограничение количества попыток с одного IP (rate limiting)

$ip = getUserIP();
$key = 'login_attempts_' . $ip;
$attempts = apcu_fetch($key) ?: 0;
if ($attempts > 5) {
die('Слишком много попыток входа с вашего IP. Попробуйте позже.');
}
apcu_store($key, ++$attempts, 300); // блокировка на 5 минут

Это защита от брутфорса (подбора паролей). Она не блокирует пользователя навсегда, а лишь замедляет атаки. Однако если злоумышленник использует сеть из множества IP (ботнет), то такая защита не сработает. Её хорошо дополнять капчей после нескольких неудачных попыток.

Пример. Сравнение IP с предыдущим для обнаружения аномалий

session_start();
$currentIp = getUserIP();
if (isset($_SESSION['user_ip']) && $_SESSION['user_ip'] !== $currentIp) {
// IP изменился, возможно, угон сессии
error_log("Предупреждение: IP сменился с {$_SESSION['user_ip']} на $currentIp для пользователя {$_SESSION['user_id']}");
// Не блокируем сразу, но требуем повторную аутентификацию
session_destroy();
header('Location: /login?reason=ip_changed');
exit;
}
$_SESSION['user_ip'] = $currentIp;

Если в процессе активной сессии IP внезапно меняется (например, с российского на украинский), это может говорить о том, что злоумышленник украл куки сессии и пытается её использовать с другого устройства. Однако у мобильных операторов IP может меняться при переходе между вышками, а у пользователей VPN может меняться страна при переподключении. Поэтому такое поведение не всегда является атакой, но должно вызывать повышенное внимание (например, запрос дополнительного пароля).

Но помните всегда, что:

  1. IP не является персональным идентификатором. Один IP может принадлежать целому офису, общественной Wi-Fi сети или мобильному оператору. Блокировка по IP может задеть тысячи обычных пользователей.
  2. Прокси и VPN легко меняют IP. Злоумышленники часто используют их для обхода блокировок.
  3. IPv6 усложняет анализ. У каждого устройства может быть свой публичный IP, но их диапазоны огромны, и динамические изменения там тоже часты.

IP-адрес следует использовать только как один из многих сигналов в системе оценки риска, а не как единственную причину для блокировки. Комбинируйте его с «слепком» устройства, поведенческими метриками и двухфакторной аутентификацией для надёжной защиты.

Как заблокировать пользователя полностью

Если нужно запретить доступ всем устройствам нарушителя (а не только одному аккаунту), можно вести глобальную таблицу банов.

CREATE TABLE banned_fingerprints (
fingerprint_hash VARCHAR(64) PRIMARY KEY,
reason TEXT,
banned_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

При каждом запросе сервер вычисляет хеш (смотри выше про хеш) и проверяет его наличие в этой таблице. Если хеш найден — возвращается ошибка доступа.

Ограничения метода и практические советы

Ни один «слепок» не даёт 100-процентной гарантии. Вот с чем вы столкнётесь на практике.

  • Изменчивость параметров. Пользователь обновил браузер, поставил новую видеокарту или включил режим «инкогнито» — некоторые параметры могут измениться, и легальный владелец не пройдёт проверку. Выход — не заворачивать проверку в жёсткую блокировку, а использовать её как один из сигналов риска наряду с другими (например, с IP-адресом или аномальным поведением).

  • Сбор данных требует осторожности. Согласно законодательству многих стран, использование подобных методов для отслеживания без явного согласия пользователя может нарушать закон. Всегда добавляйте на сайт политику конфиденциальности и предупреждайте о сборе технической информации.

  • Современные браузеры усложняют жизнь. В 2026 году Firefox и Safari ужесточили политику: они могут возвращать зашумлённые данные Canvas или блокировать WebGL для неавторизованных скриптов. Ваш код должен уметь корректно обрабатывать ситуации, когда браузер отказывается отдавать параметры.

Лучшая стратегия — комбинировать «слепок» с другими данными: IP-адресом, историей переходов, частотой запросов и капчей. Так вы получите систему, которую сложно обойти, и при этом не будете блокировать обычных пользователей по ложному срабатыванию.

Если пользователь перешёл с LAN на Wi-Fi в той же сети

При смене способа подключения (например, отключил кабель и включил Wi-Fi) внутри одного дома или офиса многие параметры «слепка» могут остаться неизменными, но некоторые — измениться.

Ниже два примера, как это можно обрабатывать.

Пример 1. Игнорирование сетевых параметров в стабильном отпечатке

Что происходит: При переключении с Ethernet на Wi-Fi:

  • Внешний IP-адрес чаще всего остаётся тем же (если маршрутизатор не меняет его).
  • Однако внутренние параметры, доступные через JavaScript, могут измениться: navigator.connection.type может переключиться с 'ethernet' на 'wifi', а также могут измениться rtt (задержка) и downlink (скорость).

Риск: Если вы включили эти параметры в хеш «слепка», то после переключения пользователь получит новый отпечаток и будет вынужден заново проходить верификацию (например, вводить код из письма). Это вызовет недовольство.

Решение: Исключить из хеша любые параметры, которые могут меняться в пределах одной сессии или одного пользователя без смены устройства. К стабильным параметрам относятся: Canvas-отпечаток, WebGL-данные, Audio-отпечаток, список шрифтов, версия браузера, часовой пояс, разрешение экрана (если не меняется). Сетевые параметры (тип соединения, RTT, IP) либо вообще не использовать, либо использовать как отдельные сигналы для анализа риска, но не для формирования основного идентификатора.

Пример 2. Объединение отпечатков через «пул доверенных» устройств

Что происходит: 

  • В некоторых сценариях (например, публичный Wi-Fi в кафе, где IP меняется часто) нельзя полагаться даже на частичную стабильность.
  • Но если переключение происходит в пределах одной частной сети (квартира, офис), можно применить более умную логику.

Решение: При каждой авторизации сервер вычисляет хеш отпечатка. Если хеш не совпадает ни с одним из сохранённых для этого пользователя, но часть параметров (например, Canvas+WebGL+Audio) совпадает с уже известным устройством, а изменились только сетевые данные, то сервер может:

  • Не требовать повторной верификации, а просто добавить новый хеш в список доверенных устройств этого пользователя.
  • Или, если разошлись только navigator.connection.type и rtt, считать это тем же устройством и автоматически обновить хеш.

Технически это реализуется так: Храните в базе не один хеш, а несколько полей: stable_hash (без сетевых параметров) и full_hash (со всеми параметрами). При смене сети full_hash изменится, но если stable_hash остался, то можно обновить full_hash без блокировки.

Пример кода на сервере (условный PHP):

$stableFingerprint = hash('sha256', $canvas . $webgl . $audio . $userAgent);
$fullFingerprint = hash('sha256', $stableFingerprint . $networkType . $rtt);

// Проверяем, есть ли stableFingerprint в списке доверенных
if (in_array($stableFingerprint, $userTrustedStable)) {
// Устройство то же, просто обновляем полный отпечаток
$user->updateFullFingerprint($fullFingerprint);
// Вход разрешён
} else {
// Полная верификация
}

Ни один метод не даёт 100% гарантии, но комбинация стабильных аппаратных метрик и разумной логики обновления позволяет минимизировать ложные срабатывания при смене сети внутри одного помещения.

Нажмите, чтобы оценить наш труд:
[Всего: 1 Средняя: 5]
Ethan Carter

Я, Итан Картер – американский разработчик и технический автор с более чем 20-летним опытом в системном и прикладном программировании. Мой основной профиль — низкоуровневая разработка на Assembler: 22 года практики, включая глубокую работу с оптимизацией кода, архитектурой процессоров и производительностью критичных по скорости решений. Я защитил PhD dissertation по Assembler, а также более 18 лет работаю с ASP.NET, создавая корпоративные веб-системы, API и масштабируемые backend-решения.

Дополнительно я имею 9 лет опыта в C++ и C#, а также 7 лет практики программирования микроконтроллеров на Assembler. Благодаря моему сочетанию академической подготовки и прикладного инженерного опыта я могу писать статьи на стыке архитектуры ПО, низкоуровневой оптимизации и современной разработки, делая сложные технические темы понятными для профессиональной аудитории.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *


Срок проверки reCAPTCHA истек. Перезагрузите страницу.

О нас | Контакты


Прокрутить вверх