iplogs.com

Как обнаружить VPN-пользователей на вашем сайте

Практическое руководство без привязки к вендору для команд антифрода, ad-ops, поддержки и платформенных инженеров. Готовые примеры на Node.js, Python, Go, PHP и Cloudflare Workers. SDK не нужен, API бесплатный и не требует аутентификации.

Что на самом деле означает обнаружение VPN

«Этот IP, VPN?» звучит как yes/no вопрос, но на практике вы получите бакет: clean, suspicious (дата-центр), vpn_likely или vpn_detected. Правильное продуктовое действие зависит от бакета, не от единого булева флага.

  • vpn_detected — подтверждённый VPN-выход (multi-source совпадение). Блокируйте записи, step-up auth.
  • vpn_likely, сильные сигналы, но без ground-truth подтверждения. Считайте повышенным риском.
  • suspicious, дата-центр / хостинг IP. Почти никогда не реальный потребитель; может быть скрапер, автоматизированный браузер или self-hosted VPN.
  • clean, резидентный или доверенный IP. Никаких сигналов обнаружения не сработало.

Серверная проверка (рекомендуется)

Вызовите POST /v1/check из вашего бэкенда с IP посетителя из reverse-proxy заголовков. Кэшируйте вердикт на 5 минут по IP.

Node.js (TypeScript)

async function checkVpn(ip: string) {
  const r = await fetch("https://iplogs.com/v1/check", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ ip }),
  });
  const data = await r.json();
  return {
    verdict: data.verdict,        // clean | suspicious | vpn_likely | vpn_detected
    isVpn: data.is_vpn,
    score: data.score,            // 0–1
    provider: data.ip_info?.vpn_provider ?? null,
    sources: data.ip_info?.vpn_provider_sources ?? [],
  };
}

Python

import httpx

def check_vpn(ip: str) -> dict:
    r = httpx.post(
        "https://iplogs.com/v1/check",
        json={"ip": ip},
        timeout=5.0,
    )
    r.raise_for_status()
    return r.json()  # verdict, score, is_vpn, ip_info, signals[]

Go

type Verdict struct {
    Verdict string  `json:"verdict"`
    Score   float64 `json:"score"`
    IsVPN   bool    `json:"is_vpn"`
}

func CheckVPN(ctx context.Context, ip string) (*Verdict, error) {
    body, _ := json.Marshal(map[string]string{"ip": ip})
    req, _ := http.NewRequestWithContext(ctx, "POST",
        "https://iplogs.com/v1/check", bytes.NewReader(body))
    req.Header.Set("content-type", "application/json")
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return nil, err }
    defer resp.Body.Close()
    var v Verdict
    return &v, json.NewDecoder(resp.Body).Decode(&v)
}

PHP

function checkVpn(string $ip): array {
    $ctx = stream_context_create([
        "http" => [
            "method"  => "POST",
            "header"  => "content-type: application/json",
            "content" => json_encode(["ip" => $ip]),
            "timeout" => 5,
        ],
    ]);
    $body = file_get_contents("https://iplogs.com/v1/check", false, $ctx);
    return json_decode($body, true);
}

Edge-проверка (Cloudflare Worker)

Запустите проверку на edge до того, как запросы попадут на ваш origin. Удобно для блокировки VPN на маршрутах регистрации или логина без модификации кода приложения.

export default {
  async fetch(req: Request) {
    const ip = req.headers.get("cf-connecting-ip") ?? "";
    const r = await fetch("https://iplogs.com/v1/check", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ ip }),
    });
    const { verdict } = await r.json();
    if (verdict === "vpn_detected" && req.method !== "GET") {
      return new Response("VPN-трафик заблокирован на этом маршруте", { status: 403 });
    }
    return fetch(req);
  },
};

Какие сигналы взвешивать в собственной модели риска

Не доверяйте только булеву флагу. Ответ IPLogs включает массив signals[], каждый сигнал, который сработал, с весом. Пример собственного скоринга:

function customRisk(detection) {
  let risk = detection.score;
  // Boost для подтверждённого коммерческого VPN
  if (detection.ip_info?.vpn_provider) risk += 0.1;
  // Boost для любого threat-intel hit
  if (detection.signals?.some(s => s.type === "threat_intel_listed" && s.matched))
    risk += 0.2;
  // Discount для trusted-infra IP (DNS-резолверы, CDN edge)
  if (detection.signals?.some(s => s.type === "asn_trusted_infra" && s.matched))
    risk = 0.0;
  return Math.min(1, risk);
}

Типичные ошибки

  • Не доверяйте IP от клиента. Всегда читайте из proxy-заголовка (X-Forwarded-For, CF-Connecting-IP, X-Real-IP), никогда из JavaScript или request body.
  • Кэшируйте агрессивно. 5-минутный кэш по IP снижает API-нагрузку в 100 раз и срезает ~150 мс с критического пути.
  • Apple Private Relay, отдельная категория. Многие из ваших реальных клиентов используют его по умолчанию. Жёсткая блокировка стоит вам регистраций.
  • Не показывайте вердикт пользователю. «Мы заблокировали вас, потому что вы на VPN» точно говорит атакующему, какой сигнал избегать. Используйте generic-сообщение.

Часто задаваемые вопросы

+Какой самый надёжный способ обнаружения VPN-пользователей?

Подтверждение из нескольких источников. Один фид всегда дрейфует; ground-truth импорт из API самого VPN-провайдера плюс активная проверка протоколов устраняют ~99% ложных срабатываний. Решения только на основе агрегаторов (один CIDR-фид) пропускают новые exit-узлы и ошибочно помечают CGNAT.

+Стоит ли блокировать VPN-пользователей полностью?

Почти никогда. Большинство потребительского VPN-трафика легитимно (приватность, обход геоблокировок, недоверенные сети). Блокируйте на high-friction поверхностях (регистрация, оплата, сброс пароля) и step-up auth на платежах. Жёсткая блокировка чтения теряет лояльных пользователей.

+Замедлит ли обнаружение VPN мои запросы?

Не замедлит, если делать правильно. Кэшируйте вердикты на 5 минут по IP (Redis или in-memory), медианный запрос добавляет <1 мс. Сам IPLogs API возвращает sub-2 секунды на холодный IP и <1 мс на тёплый.

+Нужно ли обрабатывать iCloud Private Relay и Cloudflare WARP отдельно?

Да. Оба «анонимизирующие», но ни один не является коммерческим VPN, и многие из ваших самых лояльных пользователей по умолчанию используют Apple Private Relay. Обрабатывайте их как отдельный тип сигнала ("private_relay") и применяйте step-up только в комбинации с другими факторами риска.

После интеграции обнаружения смотрите готовые рецепты блокировки для Cloudflare WAF, Nginx и Stripe Radar. Или прочитайте полный справочник API.