
みなさんこんにちは、ガノー(Twitter:Ganohr)です。
最近当サイトのアクセスが増加しており、全世界に数億あると言われるウェブサイトの中で、1万位以内のアクセス数を誇るようになりました。
私の運営サイト( https://t.co/F2giDrq5ej )、netcraftが公開しているアクセス数ランキングで1万位以内に入ってきました。 pic.twitter.com/VQISiMdyDr
— Ganohr(ガノー) (@ganohr) January 30, 2023
そうなってくると増えてくるのが、数多くのコピーサイト。
スプログ・スパムサイトの運営やブラックハットSEO等を目的として、他人のサイトのコンテンツを盗用し、検索エンジンへそのコンテンツを流し実際とは異なる内容で高評価を得ようとする不正行為です。
これによりどんなにゴミのようなサイトでも検索流入を期待できるようになります。

こうしたコピーサイトを作る上で使われるボット(不正にコンテンツの内容を収集するソフト)は、多くがVPNやPROXYの上で運用されています。
そこで、今回は特にWordPressで
VPNやPROXYやTOR経由によるRSS取得が試みられた場合に、その接続元をシャットアウトするコードを共有します。
更新履歴
対策結果例


当サイトのコンテンツをホスティングサービスが取得しており、こうしたアクセスを高確率でブロックできます。
なお、今回公開するコードではログ出力は省略しているので、必要な方は以下の記事を参考にWordPressでアクセスを実装したり必要な情報を出力してください。

VPN/PROXY/TOR検出のために利用するサービス


今回利用するのは、当サイトの運営にほぼ5年近く利用している『proxycheck.io』です。
これはIPアドレスがVPN及びPROXYやTORかをチェックできるサービスです。
利用料がとても安いながら、高精度であると実感しているため、長年愛用しています。
またWordPressなら専用プラグインも提供されています。
簡単操作でAPI利用に必要なアクセストークンを発行できる上、なんと1日1,000チェックまで完全無料です。
それを超えると判定できなくなりますが、日をまたげばすぐに利用可能になります。
WordPressのログインURLを防御したり、今回のように特定のURL(≒RSS用URL)へのアクセスを防ぐような用途には必要十分です。

そこで今回は、このプラグインを利用して発行したアクセストークンとAPIを利用します。
この記事を実践する前に、上掲のプラグインを導入しておいてください。WordPressでRSSやコメントページへのVPN/PROXY/TORを利用したアクセスを禁止するコード

proxycheck.io.php.function.php
の入手とカスタマイズ
まず、以下のURLへアクセスしproxycheck.io.php.function.php
をダウンロードしてください。
そしてダウンロードしたコードを以下のように改造します。
<?php
if (!defined('ABSPATH')) {
die();
}
/*
* A PHP Function which checks if the IP Address specified is a Proxy Server utilising the API provided by https://proxycheck.io
* This function is covered under an MIT License.
*/
function proxycheck_function($Visitor_IP) {
// ------------------------------
// SETTINGS
// ------------------------------
$API_Key = "######-######-######-######"; // Supply your API key between the quotes if you have one
$VPN = "1"; // Change this to 1 if you wish to perform VPN Checks on your visitors
$TLS = "1"; // Change this to 1 to enable transport security, TLS is much slower though!
$TAG = "1"; // Change this to 1 to enable tagging of your queries (will show within your dashboard)
// If you would like to tag this traffic with a specific description place it between the quotes.
// Without a custom tag entered below the domain and page url will be automatically used instead.
$Custom_Tag = "IP Late Check"; // Example: $Custom_Tag = "My Forum Signup Page";
// ------------------------------
// END OF SETTINGS
// ------------------------------
// Setup the correct querying string for the transport security selected.
if ( $TLS == 1 ) {
$Transport_Type_String = "https://";
} else {
$Transport_Type_String = "http://";
}
// By default the tag used is your querying domain and the webpage being accessed
// However you can supply your own descriptive tag or disable tagging altogether above.
if ( $TAG == 1 && $Custom_Tag == "" ) {
$Post_Field = "tag=" . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
} else if ( $TAG == 1 && $Custom_Tag != "" ) {
$Post_Field = "tag=" . $Custom_Tag;
} else {
$Post_Field = "";
}
// Performing the API query to proxycheck.io/v2/ using cURL
$ch = curl_init($Transport_Type_String . 'proxycheck.io/v2/' . $Visitor_IP . '?key=' . $API_Key . '&vpn=' . $VPN);
$curl_options = array(
CURLOPT_CONNECTTIMEOUT => 30,
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $Post_Field,
CURLOPT_RETURNTRANSFER => true
);
curl_setopt_array($ch, $curl_options);
$API_JSON_Result = curl_exec($ch);
curl_close($ch);
// Decode the JSON from our API
$Decoded_JSON = json_decode($API_JSON_Result);
// Check if the IP we're testing is a proxy server
if ( isset($Decoded_JSON->$Visitor_IP->proxy) && $Decoded_JSON->$Visitor_IP->proxy == "yes" ) {
// A proxy has been detected.
return true;
} else {
// No proxy has been detected.
return false;
}
}
2~4行目は、WordPressを経由せずに直接アクセスによる呼び出しを防止するコードです。
17行目に払い出したAPIのトークンを設定してください。18~20行目は、全部"1"
を指定することを推奨します。
24行目は、WordPressプラグイン画面への結果に影響しないよう識別用にタグを設定しています。
このカスタマイズが終わったら、あなたが利用しているテーマのfunctions.php
があるディレクトリへFTP等を用いて転送してください。
functions.php
のカスタマイズ
以下のコードを追加してください。
/* CC BY-SA 4.0 Copyright ganohr.net */
add_action('get_header', 'gnr_ip_uri_vpnorproxy_check', 1);
add_action('rdf_header', 'gnr_ip_uri_vpnorproxy_check', 1);
add_action('rss_head', 'gnr_ip_uri_vpnorproxy_check', 1);
add_action('rss2_head', 'gnr_ip_uri_vpnorproxy_check', 1);
add_action('atom_head', 'gnr_ip_uri_vpnorproxy_check', 1);
add_action('commentsrss_head', 'gnr_ip_uri_vpnorproxy_check', 1);
add_action('commentsrss2_head', 'gnr_ip_uri_vpnorproxy_check', 1);
add_action('comments_atom_head','gnr_ip_uri_vpnorproxy_check', 1);
function gnr_aa_die() {
header('HTTP/1.1 403 Forbidden');
die();
}
function gnr_ip_uri_vpnorproxy_check() {
$OK_CACHE_DAYS = 14; // 問題ない場合のキャッシュ日数
$NG_CACHE_DAYS = 90; // VPN/PROXYだった場合のキャッシュ日数
// IPアドレスを取得
$ip = @$_SERVER['REMOTE_ADDR'];
// ログインユーザー及び自サーバからのアクセスは許可
if (is_user_logged_in() || $ip === $_SERVER['SERVER_ADDR']) {
return;
}
// チェック結果のキャッシュを取得
$key = "gnr-banned-check-$ip";
$ret = get_transient($key);
if ($ret === 'need-ban') {
// 403 Forbidden
gnr_aa_die();
} elseif ($ret === 'ok') {
// 問題なし
return;
}
// IPアドレスのチェックを行うURIの一部(strposで簡易チェックするため留意)
$check_uri_parts = ['/feed/', '/rdf/', '/comment-page-'];
// 現在のURIを取得
$uri = get_permalink();
// URIがチェック対象になっているか確認
$need_check = false; // チェック不要
foreach ($check_uri_parts as $part) {
if (strpos($uri, $part) !== false) {
// チェック必要
$need_check = true;
break;
}
}
// チェック不要なら抜ける
if ($need_check === false) {
return;
}
// proxycheck.ioでIPアドレスをチェックする
require_once __DIR__ . '/proxycheck.io.php.function.php';
if (proxycheck_function($ip)) {
// proxy・vpn・torなのでアクセス禁止
set_transient($key, 'need-ban', DAY_IN_SECONDS * $NG_CACHE_DAYS);
// 403 Forbidden
gnr_aa_die();
}
// 問題ないのでキャッシュ
set_transient($key, 'ok', DAY_IN_SECONDS * $OK_CACHE_DAYS);
}
一応コードの解説をしておくと、
2~9行目はWordPressの各アクションフックへgnr_ip_uri_vpnorproxy_check
関数を登録しています。
特にWordPressは様々なrssのタイプに対応しているため、それぞれに応じたアクションフックが大量にあります。
17行目はVPNでもPROXYでもTORでもない場合に、そのIPのチェック結果をキャッシュしておく日数を指定できます。
18行目はVPNやPROXYやTORだった場合に、そのIPからの接続をシャットアウトする日数を指定できます。
この値は運用するサイトの管理者と相談して決めてください。
21行目で接続元のIPを取得しています。通常はこのままで大丈夫です。
ただし、CDNを利用している場合などではexplode(',', $_SERVER['SERVER_ADDR'])[1]
などのようにして、適切に接続元のIPが取得できるように変更してください。
24~26行目はログインユーザーやサーバー自体からのアクセスの場合、ブロックしないようにするコードです。
場合によっては、UAを元にGooglebot等の検索エンジンの場合もブロックしないようにする方が適切かもしれません。
28~39行目で既に判定結果がキャッシュされている場合の処理を記述しています。
'need-ban'
となっていればVPN/PROXY/TORなので403 Forbidden
で遮断します。
'ok'
となっていれば問題ないので処理を中断します。
42行目に、このチェックを行うURIの一部を指定し、42~61行目でチェックが必要か否かを判定しています。
このチェックはどうしても100ms~300msのラグが発生するため、チェックを行うべきURIを限定する必要があります。
ちなみに、RSSとしては「feed」と「rdf」の2種類しか定義していませんが、現状最新のWordPressは自動的にこの2つにリダイレクトします。
そのため、指定としてはこれだけで問題ありません。
※ 詳しくは以下のページを参照してください。

あとは64行目で先程アップロードしたproxycheck.io.php.function.php
を読み込んでいます。
実際の判定は66行目で行っており、if
が成り立つ場合はVPNやPROXYやTORと判定されているため、68行目で'need-ban'
として判定結果を$NG_CACHE_DAYS
で指定した日数分キャッシュし、403 Forbidden
で遮断します。
このif
に入らなければ問題ないことがわかるため、判定結果を'ok'
として$OK_CACHE_DAYS
で指定した日数分キャッシュします。
実際に動作しているか確認する

実際に正しく動作しているか確認しましょう。
まず、「PVB Settings」→「API Key Statisticks」を開くと、現在のAPI利用数が「Queries Today」として表示されます。

その後、自分のブラウザでVPNやPROXYやTORなどを使わず「https://ドメイン名/feed/
」などへアクセスしてください。
そして「API Key Statisticks」の画面をリロードし、「Queries Today」の値が増加することを確認できれば基本的に問題ありません。
実際にブロックできているかどうか確認したい場合は、PROXYやVPNにTORなどを用いてアクセスし、同様の手順で検証すると良いでしょう。
関連記事


コメントを書く