SQL Injection 打不穿 WordPress 核心?真正的破口其實在這幾個地方
☰ 目錄 table-of-contents.md
一分鐘先抓重點:WordPress 怎麼擋住 SQL Injection?
SQL Injection(SQL 注入)是攻擊者把惡意的 SQL 指令「夾帶」進你的查詢語句,騙資料庫執行非預期動作,進而竊取、竄改或刪除資料。對 WordPress 來說,核心相對安全,真正的破口幾乎都在外掛、佈景主題與工程師自己寫的客製化查詢。
結論先講:開發者只要記住一條鐵律——任何代入變數的資料庫查詢,一律用 $wpdb->prepare();網站管理員則做好「保持更新、最小權限、部署 WAF」三件事。把這兩面都顧好,SQL Injection 這個老問題就能被擋在門外。
SQL Injection 是個老掉牙卻極度致命的問題,一個外掛或一段客製程式碼的小疏忽,就可能讓駭客把你的資料庫當成提款機來去自如。這篇文章我會用工程師的視角,從原理、實戰寫法到管理面防禦,帶你把 WordPress 的防火牆做到滴水不漏。
SQL Injection 到底是什麼?用白話文說給你聽
先別急著看程式碼。想像你在圖書館的查詢系統找書,系統有個欄位讓你輸入「書名」,然後拿去資料庫查。
- 正常使用者:輸入「哈利波特」,系統執行的指令是:
SELECT * FROM books WHERE title = '哈利波特'; - 搞事的駭客:輸入
' OR '1'='1' --,系統組出來的指令變成:SELECT * FROM books WHERE title = '' OR '1'='1' -- ';
看懂了嗎?因為 '1'='1' 永遠成立(true),這個指令會繞過書名檢查,把所有書全列出來;後面的 -- 是註解符號,讓原本語句剩下的部分失效、避免報錯。這只是最簡單的例子,駭客還能用更複雜的手法竊取 users 表單、刪除資料(DROP TABLE),甚至試圖取得更高的控制權。
一句話總結:SQL Injection 就是利用程式沒有嚴格區分「指令」與「資料」的漏洞,把惡意 SQL 注入到查詢語句中,欺騙資料庫執行非預期動作。這就像你叫外送,結果外送員除了送餐,還順手複製了一把你家鑰匙,後果不堪設想。
問題的根源:把「資料」和「指令」混在一起
所有 SQL Injection 的本質都一樣:程式用字串拼接的方式,把使用者輸入直接組進 SQL 語句裡。當資料和指令攪在同一條字串,資料庫就無法分辨哪段是你的查詢結構、哪段只是要被查詢的值。攻擊者只要在輸入裡塞進引號、註解符號或邏輯運算,就能改寫查詢的語意。
理解了這個根源,後面的防禦邏輯就很清楚了:只要讓資料庫永遠知道「指令是指令、資料是資料」,注入就無從發生——這正是預備陳述式(Prepared Statements)要解決的事。
WordPress 也會中招?揭開三大資安破口
很多人有個迷思:「我用的是 WordPress 這種大平台,核心應該很安全吧?」沒錯,WordPress 核心團隊在資安上下了很大功夫,核心本身相對安全。但問題往往不在主堡,而在周邊的防禦工事,也就是整個 WordPress 生態系。
破口一:萬年不更新的外掛與佈景主題
這是最常見、也最無腦的送頭方式。許多外掛或主題被發現 SQL Injection 漏洞後,開發者會很快釋出修補更新。但如果你疏於更新,網站就等於掛著一塊「歡迎駭客光臨」的牌子。看到更新通知,別囉嗦,快點按下去。
破口二:來路不明的「免費」資源
網路上找來的「開心版」、「破解版」付費外掛或主題,幾乎都被加料過,藏有後門或惡意程式碼。貪小便宜的下場,往往是付出更慘痛的代價。天下沒有白吃的午餐,在軟體世界尤其如此。
破口三:工程師自己寫的「客製化」漏洞(這最致命)
這是我最想強調的一點。專案常需要客製化功能,我們會自己寫與資料庫互動的程式碼。如果沒有遵循最佳實踐,這就是最大的漏洞來源。就算是資深工程師,趕專案時一時手滑,也可能寫出有漏洞的查詢——這比什麼都可怕,因為它繞過了平台原本的保護。
工程師的聖杯:用 $wpdb->prepare() 打造金鐘罩
前面囉嗦這麼多,終於進入正題。如果你是開發者,請把這句話刻進腦子裡:在 WordPress 中,任何要代入變數的資料庫查詢,都必須、一定、絕對要使用 $wpdb->prepare()。
這不是建議,是命令。每次在 code review 看到沒用 prepare() 的裸奔查詢,我的血壓都直接飆高。我們直接來看範例。
錯誤的示範(千萬別學)
假設我們要根據使用者傳來的 ID 查詢一篇文章:
<?php
global $wpdb;
$post_id = $_GET['id']; // 直接從使用者輸入取得 ID
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}posts WHERE ID = $post_id" );
?>
這段程式碼就是典型的 SQL Injection 漏洞。如果使用者在 URL 傳入的不是數字,而是一段惡意 SQL,你的資料庫就準備被看光光了。
正確的寫法:使用 $wpdb->prepare()
$wpdb->prepare() 扮演資料庫守門員的角色。它會把你的 SQL 指令「範本」和要填入的「資料」分開處理,確保你傳入的資料就只是資料,絕對不會被當成指令執行。這就是所謂的「預備陳述式(Prepared Statements)」。
<?php
global $wpdb;
$post_id = $_GET['id']; // 從使用者輸入取得 ID
// 使用 prepare() 來處理查詢
$query = $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}posts WHERE ID = %d",
$post_id
);
$results = $wpdb->get_results( $query );
?>
看到了嗎?我們把查詢中的變數部分用一個「預留位置(placeholder)」%d 代替,再把真正的變數 $post_id 當成第二個參數傳入。這樣一來,就算駭客傳入惡意字串,prepare() 也會把它當成一個普通的數字或字串來處理,而不是 SQL 指令的一部分,從根本上杜絕了注入的可能。
佔位符怎麼選?%d、%s、%f 各代表什麼?
選錯佔位符,防護就會打折扣。請依照資料型別正確使用:
| 佔位符 | 對應型別 | 使用時機 |
|---|---|---|
%d | 整數(decimal) | ID、數量、分頁等整數值 |
%s | 字串(string) | 標題、關鍵字、Email 等文字值 |
%f | 浮點數(float) | 價格、評分等帶小數的數值 |
實務上有個常見誤區:不要自己在 %s 外面再補引號。prepare() 會自動為字串補上必要的引號與跳脫,如果你寫成 '%s',反而可能造成多餘的引號。直接寫 WHERE post_title = %s 即可。
幾個容易被忽略、卻很常出包的進階情境
基本款學會之後,這幾個情境特別容易在客製化程式碼裡踩雷,原理仍是同一招——讓每一個動態值都走 prepare():
- 多個變數:範本裡放幾個佔位符,後面就依序傳幾個參數。例如
$wpdb->prepare( "SELECT * FROM {$wpdb->prefix}posts WHERE post_status = %s AND post_author = %d", $status, $author_id )。 - LIKE 模糊查詢:使用者輸入裡若含有
%或_這類萬用字元會干擾比對,應先用$wpdb->esc_like()跳脫,再放進%s。例如$like = '%' . $wpdb->esc_like( $keyword ) . '%';,查詢寫成WHERE post_title LIKE %s並傳入$like。 - IN (...) 多值查詢:不能把整串值塞進一個
%s。正確做法是依陣列長度動態組出對應數量的佔位符,再把陣列展開傳入,讓每個值各自被處理。
核心觀念只有一句:凡是「會變動、來自外部」的值,就交給
prepare()的佔位符;凡是「固定、由你寫死」的結構(欄位名、表單名),才直接寫進範本字串。把這條界線守住,注入就沒有縫隙可鑽。
表單名與欄位名怎麼辦?千萬別直接拼接使用者輸入
佔位符是給「值」用的,無法用來代入表單名或欄位名這類「識別碼」。所以表單名、欄位名絕對不能來自使用者輸入後直接拼接。安全的做法是用白名單比對:先準備一份允許的欄位清單,使用者傳來的值只能從清單裡選,命中才使用、沒命中就拒絕。表單前綴則一律使用 $wpdb->prefix,不要自己寫死。這樣即使有人想在欄位名上動手腳,也進不了你的查詢。
不只靠程式碼!網站管理員的防禦三部曲
就算你不是工程師,也別覺得事不關己。網站安全是每個人的責任。身為網站管理員,你可以做這幾件事,大幅降低風險。
第一部:更新、更新、再更新
很重要所以講三次。WordPress 核心、外掛、佈景主題,只要有更新就馬上處理。很多主機商也提供自動更新功能,請務必開啟。記得,許多漏洞的修補就藏在這些不起眼的小版本更新裡。
第二部:使用者權限,最小化原則
不要貪圖方便給所有人「管理員」權限。一個小編只需要「作者」或「編輯」權限就夠了,他不需要安裝外掛或修改網站設定。遵循「最小權限原則(Principle of Least Privilege)」,能在帳號萬一被盜時,有效限制損害範圍。
第三部:部署 WAF(網站應用程式防火牆)
WAF 就像你網站的保全,站在第一線過濾所有進來的請求。許多知名的 WAF 服務(如 Cloudflare)都能辨識常見的 SQL Injection 攻擊模式,在它們到達你的網站前就直接攔截。這是一層非常有效的外部防護。但要提醒:WAF 是輔助的外層防線,它不能取代程式碼層面的 prepare(),兩者要一起做才算完整。
結論:安全是日常,不是口號
SQL Injection 雖然是老派攻擊手法,但它之所以歷久不衰,正是因為它利用了最基本的人性弱點:疏忽與便宜行事。無論你是開發者還是管理者,都不能對資安掉以輕心。
對開發者來說,把 $wpdb->prepare() 養成肌肉記憶,是最基本的職業道德;對管理者來說,保持更新、謹慎授權、善用工具,是守護網站資產的基本功。資安不是一次性的工作,而是一種持續的習慣與態度。
如果你對網站安全性有疑慮,或遇到棘手的資安問題不知如何下手,別擔心。浪花科技團隊擁有多年 WordPress 開發與維運經驗,能為你提供專業的資安健檢與解決方案。歡迎與我們聯繫,一起打造更安全的網路世界。
延伸閱讀
常見問題
什麼是 SQL Injection(SQL 注入)?
WordPress 開發中如何防止 SQL Injection?
$wpdb->prepare() 的佔位符 %d、%s、%f 分別代表什麼?
為什麼用了 WordPress 核心還是可能中 SQL Injection?
表單名與欄位名也能用 prepare() 的佔位符代入嗎?
訂閱免費電子報
把 AI 自動化、企業系統設計與 WordPress / Laravel 開發的真實案例和可直接照做的技巧,整理成電子報寄給你。只寄精選內容、不灌垃圾信,一鍵就能退訂。