У PHP дуже легко визначити IP-адресу на базовому рівні:
echo 'Ви знаєте, що ваша IP-адреса' . $_SERVER['REMOTE_ADDR'] . '?';
Попередження: Отримання IP-адреси як ключа поля
$_SERVER['REMOTE_ADDR']
можливе лише у випадку, якщо PHP було викликано з браузера. У режимі CLI (наприклад, при запуску з терміналу за допомогою cron) IP-адреса недоступна (це логічно, оскільки не виконується запит до мережі).
Після багатьох років розробки я нарешті зупинився на цій реалізації:
function getIp(): string{if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) { // Підтримка Cloudflare$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];} elseif (isset($_SERVER['REMOTE_ADDR']) === true) {$ip = $_SERVER['REMOTE_ADDR'];if (preg_match('/^(?:127|10)\.0\.0\.[12]?\d{1,2}$/', $ip)) {if (isset($_SERVER['HTTP_X_REAL_IP'])) {$ip = $_SERVER['HTTP_X_REAL_IP'];} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];}}} else {$ip = '127.0.0.1';}if (in_array($ip, ['::1', '0.0.0.0', 'localhost'], true)) {$ip = '127.0.0.1';}$filter = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);if ($filter === false) {$ip = '127.0.0.1';}return $ip;}
Тоді набагато краще:
echo 'Ви знаєте, що ваша IP-адреса' . getIp() . '?';
Якщо IP можна визначити безпосередньо, або він є тільки IPv6, або знаходиться в режимі CLI (наприклад, cron), він повертає 127.0.0.1
(localhost).
Реалізації, які враховують заголовки X-Forwarded-For
і X-Real-IP
, вкрай небезпечні безпосередньо в PHP, оскільки дані можуть бути легко змінені і зловмисник може підмінити фальшиву IP-адресу, щоб, наприклад, переглянути адміністрацію або активувати режим налагодження сайту (Nette Tracy). З іншого боку, нам доводиться приймати деякі проксі-запити (наприклад, при проксі трафіку через Cloudflare, або при запуску Apache і Ngnix на одній машині, коли вони викликаються локально один за одним).
У випадку прямого доступу користувача до сервера є лише одне правильне рішення - переконатися, що в Apache (через розширення RemoteIP
) і в Nginx через розширення remote_ip
, що X-Forwarded-For
встановлюється з фактичної IP-адреси відвідувача, і що IP-адреса не може бути встановлена за допомогою HTTP-заголовка.
Поле $_SERVER['REMOTE_ADDR']
автоматично отримує правильну IP-адресу (тобто IP-адресу, з якої запит прийшов безпосередньо до PHP), і нам не потрібно з ним працювати.
Часто трапляється, що користувач отримує доступ через проксі. Потім фактична IP-адреса зберігається у змінній $_SERVER['HTTP_X_FORWARDED_FOR']
.
Такий випадок може виникнути, наприклад, коли маршрутизація на сервері вирішується методом Ngnix -> Apache -> PHP
, де Ngnix
служить зворотним проксі перед Apache
. У цьому випадку PHP бачить тільки IP-адресу у внутрішній мережі (зазвичай виду 127.0.0.*
).
Наприклад, сервіс Cloudflare може поводитися таким чином, тому слід звернути увагу на те, чи працюємо ми з IP-адресою реального користувача, чи з проксі-сервером. Для мене найкращим способом є використання функції getIp()
, згаданої на початку цієї статті. Ми можемо забезпечити виявлення Cloudflare, перевіривши наявність ключа $_SERVER['HTTP_CF_CONNECTING_IP']
, який автоматично передається в кожному проксі-запиті.
Надійного виявлення використання проксі або VPN не існує, але в реальному середовищі ми можемо відфільтрувати принаймні частину трафіку.
Є кілька способів зробити це: Візьміть діапазон IP-адрес і порівняйте IP-адресу, з якої надійшов запит.
У деяких VPN-провайдерів списки IP-адрес доступні неофіційно (див., наприклад, https://gist.github.com/JamoCA…), у випадку вихідних вузлів Tor - офіційно (https://blog.torproject.org/changes-tor-exit-list-service, але мостів Tor там немає).
Інший варіант - зробити кудись онлайн-запит, що може як затримати завантаження сторінки, якщо сервіс не працює, так і "злити" IP-адреси відвідувачів третій стороні. Починаючи з 2023 року, я б наполегливо рекомендував відмовитися від такого підходу, оскільки він починає більше стосуватися захисту та маніпулювання даними користувачів.
Цей онлайн-запит може бути "наївним", і потрібно лише побачити, кому належить діапазон або чи це проксі/ VPN (деякі сервіси можуть повертати цю інформацію, але за замовчуванням вона не є частиною "IP-інформації", наприклад, від сервісу whois).
(Найчастіше) використовується якийсь рейтинг репутації, де деякі IP-адреси "смітять" більше, ніж інші. За статистикою, з різних проксі-серверів, VPN і Tor приходить більше лайна, ніж з домашніх IP-адрес (за винятком, можливо, "заражених" домашніх IP-адрес). Таку оцінку репутації пропонують деякі списки блокування DNS (див. випадковий список, https://en.m.wikipedia.org/wiki/Comparison_of_DNS… і колонку "Мета включення до списку"), або її надають безпосередньо компанії на кшталт Cloudflare у вигляді "управління ботами" тощо.
Багато залежить від того, яка мета виявлення.
Це залежить від того, яку IP-адресу ви маєте.
ip2long
,Якщо ваш сервер баз даних безпосередньо не підтримує тип даних для IP-адреси, я рекомендую зберігати IP-адресу у вигляді varchar(39)
, де обидві версії помістяться у вигляді рядка і будуть читабельними для людини.
Зберігаючи IP-адресу, подумайте, чи є сенс зберігати також доменне ім'я, визначене функцією
gethostbyaddr
. Ви не можете дізнатися імена при складанні списку та пошуку, тому що це займає дуже багато часу, і вони можуть змінюватися з часом.
Ідеальним рішенням є створення списку заблокованих IP-адрес і порівняння цього списку з поточною IP-адресою при кожному запиті. Якщо адреси збігаються, запит буде негайно зупинено.
$blackList = ['first-ip','druha-ip',];if (\in_array(getIp(), $blackList, true) === true) {echo 'На жаль, ваша IP-адреса заблокована :-(';die; // Запит на вихід}
У прикладі передбачається реалізація функції getIp()
як у прикладі вище.
Більш потужним рішенням є перевірка на входження індексу в масив:
$blackList = ['first-ip' => true,'druha-ip' => true,];if (isset($blackList[getIp()]) === true) {echo 'На жаль, ваша IP-адреса заблокована :-(';die; // Запит на вихід}
IP-адреса сервера зазвичай зберігається у полі $_SERVER['SERVER_ADDR']
, а його назву можна отримати за допомогою конструкції gethostbyaddr($_SERVER['SERVER_ADDR'])
.
Однак, якщо використовується концепція Ngnix -> Apache -> PHP
і Ngnix
виступає в ролі зворотного проксі, реальна IP-адреса сервера не відображається.
У цьому випадку ім'я сервера можна знайти в полі $_SERVER['SERVER_NAME']
або за допомогою функції php_uname('n')
. Офіційна документація функції uname.
Потім ми можемо використати цей трюк, щоб дізнатися публічну IP-адресу сервера: gethostbyname(php_uname('n'))
.
Jan Barášek Více o autorovi
Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.
Rád vám pomůžu:
Články píše Jan Barášek © 2009-2024 | Kontakt | Mapa webu
Status | Aktualizováno: ... | uk