小牆1.81 ~ 1.84 都是 小牆 1.8 的修改版, 不是升級版, 其實攔截方式都一樣, 只是手法有不同.

各版本的修改如下:

小牆 1.81
取消了 Trackbacks/Pingbacks 的攔劫, 並且將 spam 的基本資料存進檔頭.

小牆 1.82
修改 IP 取得方式.

小牆 1.83
IP 取得方式又改回來了, 非中文語系不能留言, 沒頭像的列入待審, 自動將 spam 加入黑名單.

小牆 1.84 (2011/10/15 最新版)
不記錄 IP, 改用 url 加入黑名單.

在看代碼之前, 我想先分享我處理 spam 的經驗:

spammer 留言後, 一定會回來檢查成果
對策: 不要保留 spam.
如果你的網頁可以留下 spam, 那它還會再來, 而且越來越多.
不僅 spam 如此, 人肉 AD 也是如此.

機器 spam 基本都是從根目錄的 wp-comments-post.php 注入
(但最近發現也有從主題 comments-ajax.php 注入)
對策: 讓他找不到地方注入.
最好能用 Ajax comments 將評論目標轉移到另個文件.
如果你用的是我的 Ajax comments, 可以將 comments-ajax.php 更名為 my-comments.php, 然後在 js 頭部的
ajax_php_url = js_url.replace('-ajax.js','-ajax.php'),
也改成
ajax_php_url = js_url.replace('comments-ajax.js','my-comments.php'),
上面的 my-comments.php 只是舉例, 你也可以改成 abc.php 或 xyz.php 反正你看得懂就行.
用了 ajax 之後, wp-comments-post.php 就沒用了, 你可刪掉它. 或是有用到 my-visitors 插件的, 可用插件所附的 wp-comments-post.php 替換它, 可記錄 spam 的一些資料.

非中文的 spam 佔了很大數量
對策: 限制語言.
Some Chinese Please 插件就是檢查評論中是否含有中文, 沒有中文就留言不了.
這裏有個問題, 使用 copy 中文留言即可行騙過關.
其實 PHP 的 $_SERVER['HTTP_ACCEPT_LANGUAGE'] 就可以知道瀏覽器的語系, 中文用的是 'zh-cn' 或 'zh-tw', 一定會有 'zh'.
所以 stripos($_SERVER['HTTP_ACCEPT_LANGUAGE'], 'zh') > -1 就是中文語系.
而 stripos($_SERVER['HTTP_ACCEPT_LANGUAGE'], 'zh') === false 就是非中文語系, 我們可以在頁面送出前讓評論關閉, 不用等他送出評論才判斷.

在 functions.php 用下語句可讓非中文語系評論關閉.

1
2
if ( stripos($_SERVER['HTTP_ACCEPT_LANGUAGE'], 'zh') === false )
add_filter( 'comments_open', create_function('', "return false;") ); // 非中文語系關閉評論

如果你的評論是向全世界開放的, 這方法就不適用了.

spam 的 email 多屬虛假, 所以沒頭像
對策: 沒頭像的列入待審.
這種判斷不是很準確, 但可以屏蔽大多數的人肉 spam, 如果判斷錯誤, 還可在後台讓它獲准, 不會丟失評論.

1
2
3
4
$f = md5( strtolower($comment['comment_author_email']) );
$g = sprintf( "http://%d.gravatar.com", (hexdec($f{0}) % 2) ) .'/avatar/'. $f .'?d=404';
$headers = @get_headers( $g );
if ( !preg_match("|200|", $headers[0]) ) add_filter('pre_comment_approved', create_function('', 'return "0";'));

上面就是檢查頭像的方式, 沒頭像的列入待審. (此代碼只能用於評論檢查, 不能直接放於 functions.php)
如果 spam 有頭像, 那更簡單, 直接在黑名單加上他的 email, 這輩子他就別想留言了.

同一個 spammer 會再回來
對策: 用黑名單關閉評論.
WP 內置了評論黑名單的功能, 你可以手工加入你想屏蔽的關鍵字, 系統會自動將評論標記為垃圾評論.
問題是, WP 還是准許黑名單上的人留言, 垃圾評論會一直累積. 若是遇到他們, WP 能自動關閉評論不是更好嗎?
在 comments.php 中可以加個判斷, 讓黑名單上的人無法再留言.

1
if ( comments_open() ) :

可以改成

1
if ( comments_open() && !wp_blacklist_check($comment_author, $comment_author_email, $comment_author_url, '', $_SERVER['REMOTE_ADDR'], '') ) :

這是檢查 author, email, url, IP 是否在黑名單上, 是的話只能看到 '評論已關閉', 無法留言.

如果你的主題用的是 comment_form() 函數, 就要進 /wp-includes/comment-template.php 的 1550 行, 找到 if ( comments_open() ) 加上黑名單的判斷.
以後 WP 升級, 別忘了要做這步驟.

Ajax comments 的話, 可在 comments-ajax.php 下面 setcookie 之後, $comment_depth = 1 之前, 加上:

1
2
if (wp_blacklist_check($comment_author, $comment_author_email, $comment_author_url, '', $_SERVER['REMOTE_ADDR'], ''))
err(__('Sorry, the comment form is closed at this time.'));

如此一來, 垃圾評論送出一次之後, 就不能再送出.

另外, 既然確定是 spam, 應該要自動加入黑名單.

$blacklist = get_option('blacklist_keys');
update_option('blacklist_keys', $comment['comment_author'] . "n" . $blacklist);

上面我只針對 author, 因為 spam 的 email 和 IP 不固定, 加了沒什麼意義.
用了這方法, 要經常檢查黑名單, 別人會用到的關鍵字要刪除, 否則有人會遭受池魚之殃.

後記: 我猛然想到, url 才是 spammer 最在乎的, 加入黑名單應該改用 url.
下面是將 url 加入黑名單的方法:

1
2
3
4
5
6
7
function add_black( $comment ) {
if (!($comment_author_url = $comment['comment_author_url'])) return;
if ($pos = strpos($comment_author_url, '//')) $comment_author_url = substr($comment_author_url, $pos + 2);
if ($pos = strpos($comment_author_url, '/'))  $comment_author_url = substr($comment_author_url, 0, $pos);
$comment_author_url = strtr($comment_author_url, array('www.' => ''));
if (!wp_blacklist_check('', '', $comment_author_url, '', '', '')) update_option('blacklist_keys', $comment_author_url . "n" . get_option('blacklist_keys'));
}

spammer 越來越聰明了
對策: 用小牆要自己變化.
如果你只是 copy 代碼去用, 那只是基本運用而已, 還是會被破解的.
命名應該有所變化, 人人都不同, 讓 spammer 找不到規律.
舉例說:
小牆用 name='w' 為評論欄, 你可改為 a, b2, c3f7, text543col, spam987.... 只要首字為英文, 其它字元符合命名規則的都可以.
大家用的欄位名都不一樣, 誰會知道該填哪一格, 自然不易被破解.
否則 spam 設定填入 'w' 的話, 全部用 'w' 的小牆要全倒.
要改的位置有兩個: 一個是 18 行 $2w$3 中間的 w, 一個是 24 行 $w = 'w'; 後面的 w, 改的兩個字串要完全一樣.

使用後, 萬一所有評論都被列入垃圾, 請注意你的其它插件是否也用到了$_POST['comment'], 此時插件要做適當修改,
改法請參考這裏.
還有要說的是, 小牆 1.82 所獲得的 IP 如果用了代理會抓代理之前的, 雖然比較準確, 但在屏蔽 IP 時失效.
所以小牆 1.83 用回 $_SERVER['REMOTE_ADDR'], 這 IP 才屏蔽得到.
小牆 1.84 拿掉 IP, 因為 WordPress 已有, 另外改用 url 加入黑名單.

以下就是小牆 1.84

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/* <<小牆>> Anti-Spam v1.84 by Willin Kan. */
class anti_spam {
function anti_spam() {
if ( !current_user_can('read') ) {
add_action('template_redirect', array($this, 'w_tb'), 1);
add_action('init', array($this, 'gate'), 1);
add_action('preprocess_comment', array($this, 'sink'), 1);
}
}
// 設欄位
function w_tb() {
if ( is_singular() ) {
// 非中文語系
if ( stripos($_SERVER['HTTP_ACCEPT_LANGUAGE'], 'zh') === false ) {
add_filter( 'comments_open', create_function('', "return false;") ); // 關閉評論
} else {
ob_start(create_function('$input','return preg_replace("#textarea(.*?)name=(["'])comment(["'])(.+)/textarea>#",
"textarea$1name=$2w$3$4/textarea><textarea name="comment" cols="100%" rows="4" style="display:none"></textarea>",$input);') );
}
}
}
// 檢查
function gate() {
$w = 'w';
if ( !empty($_POST[$w]) && empty($_POST['comment']) ) {
$_POST['comment'] = $_POST[$w];
} else {
$request = $_SERVER['REQUEST_URI'];
$way     = isset($_POST[$w]) ? '手動操作' : '未經評論表格';
$spamcom = isset($_POST['comment']) ? $_POST['comment'] : '';
$_POST['spam_confirmed'] = "請求: ". $request. "n方式: ". $way. "n內容: ". $spamcom. "n -- 記錄成功 --";
}
}
// 處理
function sink( $comment ) {
// 不管 Trackbacks/Pingbacks
if ( in_array( $comment['comment_type'], array('pingback', 'trackback') ) ) return $comment;

// 已確定為 spam
if ( !empty($_POST['spam_confirmed']) ) {
// 方法一: 直接擋掉, 將 die(); 前面兩斜線刪除即可.
//die();
// 方法二: 標記為 spam, 留在資料庫檢查是否誤判.
add_filter('pre_comment_approved', create_function('', 'return "spam";'));
$comment['comment_content'] = "[ 小牆判斷這是Spam! ]n". $_POST['spam_confirmed'];
$this->add_black( $comment );
} else {
// 檢查頭像
$f = md5( strtolower($comment['comment_author_email']) );
$g = sprintf( "http://%d.gravatar.com", (hexdec($f{0}) % 2) ) .'/avatar/'. $f .'?d=404';
$headers = @get_headers( $g );
if ( !preg_match("|200|", $headers[0]) ) {
// 沒頭像的列入待審
add_filter('pre_comment_approved', create_function('', 'return "0";'));
//$this->add_black( $comment );
}
}
return $comment;
}
// 列入黑名單
function add_black( $comment ) {
if (!($comment_author_url = $comment['comment_author_url'])) return;
if ($pos = strpos($comment_author_url, '//')) $comment_author_url = substr($comment_author_url, $pos + 2);
if ($pos = strpos($comment_author_url, '/'))  $comment_author_url = substr($comment_author_url, 0, $pos);
$comment_author_url = strtr($comment_author_url, array('www.' => ''));
if (!wp_blacklist_check('', '', $comment_author_url, '', '', '')) update_option('blacklist_keys', $comment_author_url . "n" . get_option('blacklist_keys'));
}
}
$anti_spam = new anti_spam();
// -- END ----------------------------------------

如果不想屏蔽非中文語系, 可參考 1.8 的原始寫法.
國內服務器檢查頭像會比較慢, 要不要用可自己斟酌. 不用的話可以刪掉 47 ~ 56 行.
第 46, 55 行的 $this->add_black( $comment ); 是自動加入黑名單, 不用的話可注釋掉.

我知道我在網路上得罪了很多 spammer, 他們恨我入骨, 會想辦法詆毀我, 這個大家可以理解, 我就不多解釋了.