VPNを検知する | Next.js
はじめに
Web サイトの自動化や匿名化されたユーザーに対して不快感を抱くことはありませんか?
VPN や Tor は個人のプライバシーを守りますが、同時に身元を隠す手段として悪用されやすいという課題があります。
VPN や Proxy の IP を検知する API サービスは多数ありますが、多くが有料または制限があります。
ノーコストで検知が可能な API を作成したので、共有します。
完成品 ↓
ロジック
GitHub に公開されているホスティングサービスの ASN(Autonomous System Number)リストを利用します。
-
クライアント IP の取得
- クライアントから送信された IP アドレスを取得します。
-
ASN の取得
- 取得した IP を
ipinfo.ioに送信し、対応する ASN を取得します。
- 取得した IP を
-
判定
- 取得した ASN が準備した ASN リストに含まれていれば
True(VPN/Proxy と判定)とします。
- 取得した ASN が準備した ASN リストに含まれていれば
検証回数が多い場合、手順 2 で API リミットがかけられることがあります。
他の取得方法をご存知の方はそちらを使用してください。
実装
Next.js のプロジェクトに導入したものを流用したため、Next.js で作成しています。
ASN リストを取得
GitHub に公開されている ASN リストを少し改変して使用します。
元のデータはこちらです。
クライアント IP から ASN を取得
export async function getASN(ip: string): Promise<number | null> {
try {
const response = await fetch(`https://ipinfo.io/${ip}/json`);
if (!response.ok) {
console.error("Failed to fetch ASN");
return null;
}
const data = await response.json();
const match = data.org?.match(/AS(\d+)/);
if (match && match[1]) {
return parseInt(match[1], 10);
}
console.error("No ASN found");
return null;
} catch (error) {
console.error(error);
return null;
}
}
ipinfo.ioに IP アドレスを送信してfetchし、結果を取得します。
IP アドレスを1.1.1.1とした場合のresponse.json()のサンプルは以下の通りです。
{
"ip": "1.1.1.1",
"hostname": "one.one.one.one",
"city": "Brisbane",
"region": "Queensland",
"country": "AU",
"loc": "-27.4816,153.0175",
"org": "AS13335 Cloudflare, Inc.",
"postal": "4101",
"timezone": "Australia/Brisbane",
"readme": "https://ipinfo.io/missingauth",
"anycast": true
}
このレスポンスから ASN を取得して返します。
VPN かどうかを判断する
import asnList from "@/lib/asn.json";
export async function isVpn(ip: string): Promise<boolean> {
const asn = await getASN(ip);
if (!asn) return false;
return asnList.includes(asn);
}
先ほど作成した関数getASNを使用して ASN を取得し、asn.jsonに含まれるかを検証します。
IP アドレスの検証
export function isValidIPv4(ip: string): boolean {
const IPV4_REGEX =
/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
if (!IPV4_REGEX.test(ip)) return false;
const octets = ip.split(".");
return octets.every((octet) => {
return octet === "0" || !octet.startsWith("0");
});
}
送信された IP アドレスが正常かどうかを判断します。
IPv4 アドレスに対応しています。
API の作成
import { NextResponse } from "next/server";
import { isVpn } from "@/lib/isVpn";
import { isValidIPv4 } from "@/lib/ipValidate";
export async function GET(request: Request, { params }: { params: Promise<{ ip: string }> }) {
const { ip } = await params;
if (!isValidIPv4(ip)) {
return NextResponse.json({ error: "invalid ip address" }, { status: 400 });
}
const vpnCheck = await isVpn(ip);
if (!vpnCheck) {
return NextResponse.json({
ip,
isVpn: false,
});
}
return NextResponse.json({
ip,
isVpn: true,
});
}
これまで作成した関数を使用して API ルートを作成します。
動作確認
完成した API を試しに使用してみましょう。
VPN で使用されているイスラエルの IP アドレスでテストします。
{"ip":"169.150.227.226","isVpn":true}
無事判定されました。
最後に
以上で実装は完了です。
自動化対策には必須レベルの VPN/Proxy 検知に便利だと思います。
Discord サーバーの Web 認証などに設置してみると良いでしょう。
Discussion