튜토리얼: Node.js Express에서 VPN 및 Tor 사용자를 감지하는 방법

발행: (2025년 12월 16일 오후 12:46 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

개요

공개 API, SaaS, 포럼 등 어떤 서비스를 운영하든 이미 겪고 있는 고통: 봇 트래픽.
스팸을 이유로 사용자를 차단하면, 몇 초 뒤에 VPN을 전환해서 새 계정으로 다시 돌아옵니다. IP를 차단하면, 그들은 Tor exit 노드로 전환합니다.

이 튜토리얼에서는 Node.js 애플리케이션에서 비거주 IP(VPN, 프록시, 호스팅 센터)를 감지하는 방법을 보여드리겠습니다. 이를 통해 데이터베이스에 접근하기 전에 차단하거나 최소한 CAPTCHA로 도전하도록 할 수 있습니다.

목표

우리는 Express에서 다음과 같은 미들웨어 함수를 원합니다:

app.use((req, res, next) => {
  if (isHighRisk(req.ip)) {
    return res.status(403).send("VPNs are not allowed.");
  }
  next();
});

다음은 이를 구축하는 방법입니다.

Method 1: The “Hard” Way (Self‑Hosted Lists)

Step 1: Get the Data

  • Tor Exit Nodes – Tor 프로젝트는 종료 주소 목록을 공개합니다.
  • Cloud Ranges – AWS와 Google은 대용량 JSON 파일 형태로 IP 범위를 공개합니다.

이 파일들(예: tor-exit-nodes.txt, aws-ip-ranges.json)을 다운로드하고 최신 상태로 유지해야 합니다.

Step 2: The Code

const fs = require('fs');
const ipRangeCheck = require('ip-range-check'); // npm install ip-range-check

// Load the massive lists into memory (careful with RAM!)
const torNodes = fs.readFileSync('tor-exit-nodes.txt', 'utf8')
  .split('\n')
  .filter(Boolean);
const awsRanges = JSON.parse(
  fs.readFileSync('aws-ip-ranges.json', 'utf8')
).prefixes.map(p => p.ip_prefix);

function isHighRisk(userIp) {
  // Check if IP is in the Tor list
  if (torNodes.includes(userIp)) return true;

  // Check if IP is in a Cloud Range (CPU intensive)
  if (ipRangeCheck(userIp, awsRanges)) return true;

  return false;
}

The Problem with Method 1

  • Stale data – VPN 제공업체는 IP를 매일 교체합니다. 시간당 업데이트가 없으면 많은 공격을 놓치게 됩니다.
  • Memory hog – 수백만 개의 IP를 Node.js 메모리에 로드하면 서버가 크래시될 수 있습니다.
  • False positives – 정상적인 데이터센터 IP와 악의적인 VPN IP를 구분하는 것이 어려울 수 있습니다.

Method 2: The “Easy” Way (Live API Lookup)

Step 1: Get a Free API Key

Grab a free key from the provider’s website (no credit card required).

Step 2: The Middleware

The API returns a trustScore (0‑100) along with flags for Tor and VPN.

const axios = require('axios');

async function checkRiskScore(req, res, next) {
  const userIp = req.ip;

  try {
    const response = await axios.get('https://candycorndb.com/api/public/ip-score', {
      params: { ip: userIp }
    });

    const { score, isTor, isVPN } = response.data;

    // BLOCK if it's a confirmed Tor node or very high risk
    if (isTor || score > 85) {
      return res.status(403).json({ error: 'Anonymizers not allowed.' });
    }

    // CHALLENGE if it's suspicious (e.g., DigitalOcean droplet)
    if (score >= 50) {
      // Insert CAPTCHA logic here…
      console.log(`Suspicious traffic from ${userIp}`);
    }

    next();
  } catch (err) {
    // Fail open: if the API is down, let the user in so you don’t block real people
    next();
  }
}

// Apply to your sensitive routes
app.post('/api/signup', checkRiskScore, (req, res) => {
  res.send("Account created!");
});

Why This Is Better

  • Just‑in‑time scanning – If the API hasn’t seen the IP before, it scans open ports and ISP data in < 500 ms, so you never get “unknown.”
  • No maintenance – No need to download daily CSV dumps.
  • Saves RAM – Your Node server handles logic, not massive IP storage.

Summary

악성 IP 차단은 일종의 무기 경쟁입니다. 작은 취미 프로젝트를 만들고 있다면 방법 1은 재미있는 학습 연습이 될 수 있습니다. 프로덕션 앱의 경우, 위험 감지를 전용 API(방법 2)로 넘기는 것이 스팸 계정을 해제하는 데 드는 시간보다 보통 더 저렴합니다.

IP 필터링 로직에 대해 언제든지 질문해 주세요! 😅

Back to Blog

관련 글

더 보기 »

첫 번째 MCP 앱 만들기

TL;DR MCP Apps는 대화형 에이전트와 기타 MCP 클라이언트에 인터랙티브 UI를 제공합니다. 이 튜토리얼에서는 간단하면서도 강력한 앱 소스 코드를 만드는 방법을 보여줍니다.

실험적인 Hono auth npm 패키지

제가 만들고 있는 것은 개발자들이 일반적인 보일러플레이트인 login, register, JWT, email verification 등을 작성하지 않고도 앱에 바로 넣을 수 있는 auth 패키지입니다.