前言:趕走了客廳的強盜,別忘了鎖好廚房的後門
還記得我們上一篇提到的慘痛教訓嗎?在 2025 年那個本該歡樂的 12 月週末,我們的 VPS 遭遇了挖礦病毒的無情洗劫,CPU 飆到 100%,讓我們公司的吉祥物「鍛碼匠」被迫轉職去當礦工。[2025 年底挖礦病毒驚魂記:我的 Next.js 容器是如何淪為駭客礦機的?(含Dockerfile完整防禦指南)](https://fordige.com/blog/2025-nextjs-docker-mining-malware-defense-guide)。
在上一篇文章中,我們學會了如何透過 Docker 的權限控管、唯讀檔案系統以及洋蔥式的縱深防禦,把伺服器的「底層地基」打造成鋼鐵堡壘。我們就像是把家裡的大門換成了銀行金庫級別的防爆門,任何試圖從作業系統層面(OS Level)或是容器層面鑽進來的壞蛋,都會撞得滿頭包。
但是(人生最怕就是這個但是),資安的世界就像是一場永無止境的「貓捉老鼠」。當你把大門鎖死的時候,狡猾的老鼠——也就是駭客——並不會就此放棄,他們會開始尋找其他的入口。可能是你為了通風而打開的窗戶,或者是你為了收快遞而留下的側門。
在網站開發的世界裡,這些窗戶和側門,就是我們的「應用層」(Application Layer)。具體來說,就是那些我們為了 SEO 排名而設置的 JSON-LD 資料,以及為了讓使用者上傳可愛大頭貼而開啟的檔案上傳功能。
今天,我們要從底層架構走上二樓,來談談兩個極具破壞力、卻常被開發者(特別是趕著上線的創業團隊)忽略的攻擊手段:JSON-LD 注入 與 檔案上傳漏洞。別擔心,這堂課不需要你具備駭客等級的技術,我們會用最輕鬆幽默的方式,配上本團隊的血淚經驗,帶你一步步破解這些常見的攻擊手段。
倖存者偏差:為什麼駭客會盯上我這種「小蝦米」?
在開始談技術之前,我們得先導正一個非常致命的觀念。很多中小企業網站的站長,或是剛接觸網頁開發的「小白」,心裡常會有這樣的 OS:「我的網站又不是銀行,裡面也沒幾個人,流量少得可憐,駭客吃飽太閒才會來攻擊我吧?」
這就是標準的「**倖存者偏差**」心態。這就像是你覺得自己家裡沒放黃金,所以出門連門都不鎖一樣危險。
事實上,駭客攻擊往往不是針對你個人(除非你是什麼政要名人或是有仇家)。現代的網路攻擊,大部分都是由自動化的腳本——我們可以把它們想像成網路上的「掃地機器人」——在執行的。只是這些機器人掃的不是灰塵,而是漏洞。它們不分晝夜在網海中巡邏,測試每一個網站的門把有沒有鎖好。
一旦你的網站被攻破,它可能不會立刻爆炸或消失,而是變成駭客的「殭屍基地」。
- **免費礦工**: 就像我們上次遇到的那樣,利用你的伺服器資源幫他們挖礦。
- **DDoS 跳板**: 把你的網站變成攻擊別人的武器。
- **惡意廣告看板**: 想像一下,如果有客戶來到你的企業官網,結果一進門就被強迫推銷非法博弈網站或色情內容,這對你的品牌信譽是多大的毀滅性打擊?
更有趣(應該是更可怕)的是駭客心理學。在網路世界裡,攻擊成本極低,而防禦成本相對較高。這就像是小偷可以隨機試探一千扇門,只要有一扇門沒鎖,他就賺到了;而屋主卻必須確保每一扇窗戶、每一個鎖頭都完美無缺。所以,別再賭運氣了,我們需要的是系統化的防禦。
SEO 與資安的愛恨情仇:Google 是個有潔癖的美食評論家
在深入探討那些嚇人的程式碼之前,我們先來聊聊一個讓所有站長都魂牽夢縈的話題:SEO(搜尋引擎優化)。
你可能認為 SEO 只是關於關鍵字堆砌、寫寫內容行銷、或是到處去騙反向連結。但事實上,網站的「資安狀態」已經成為 Google 排名演算法中的重要指標之一。
想像一下,Google 就像是一個極度挑剔的米其林評審員。
- **內容質量** 是你的菜好不好吃。
- **網站速度** 是你的上菜速度快不快。
- **網站安全性** 則是你的餐廳乾不乾淨、廚房有沒有老鼠、消防安檢有沒有過關。
如果你的網站經常被駭客入侵,或者被用來散播病毒,Google 這個評審員就會在搜尋結果中給你的網站貼上一個大大的紅色警告標籤:「此網站可能會損害您的電腦」。
一旦被貼上這個標籤,無論你的文章寫得再好、產品再棒,流量都會瞬間歸零。這對於依賴自然流量的網站來說,無疑是判了死刑。所以,做好資安,不只是為了保護數據,更是為了保住你的飯碗(搜尋排名)。
JSON-LD 注入:藏在名片背後的詛咒
為了讓 Google 更了解我們的網站內容,現代網站開發(特別是 Next.js)通常會使用一種叫做「結構化資料」(Structured Data)的技術。
什麼是 JSON-LD?給搜尋引擎的「食材清單」
如果沒有結構化資料,Google 的爬蟲只能透過讀取網頁文字去「猜」這是一篇食譜還是一個產品頁面。但有了結構化資料,就像是你直接給了評審員一份詳細的「食材清單」和「烹飪說明」。
目前 Google 最推薦的格式就是 JSON-LD (JavaScript Object Notation for Linked Data)。它通常長這樣:
```JavaScript
// 一個標準的 JSON-LD 結構
const jsonLd = {
"@context": "https://schema.org",
"@type": "Article",
"headline": "網站資安教學:如何防止被當成礦機",
"author": {
"@type": "Person",
"name": "資安大師 - 鍛碼匠"
}
}
```
這段資料最終會被轉化為 標籤放在網頁中。對於一般使用者來說,這段程式碼是隱形的;但對於 Google 來說,這是寶貴的資訊來源,能讓你的網站在搜尋結果中顯示出「豐富摘要」(Rich Snippets),大大提升點擊率。
然而,正是因為 JSON-LD 如此重要,它也成為了駭客眼中的一塊肥肉。
攻擊原理:駭客的「全面啟動」(Inception)
JSON-LD 注入的核心概念,在於「過度信任」。 許多開發者為了方便,會直接把用戶輸入的資料(例如:文章標題、用戶名稱、評論內容)塞進這個 JSON 物件裡。
這時候,駭客就像是電影《全面啟動》裡的造夢者,要在你的腳本中植入另一個腳本。
瀏覽器在解析 HTML 時,是非常單純且直線思考的。當它讀到
``` 這樣一串字,會發生什麼事呢?
在 Next.js 中,如果我們使用了 `dangerouslySetInnerHTML`(這名字取得真好,官方都警告你很危險了)來插入 JSON-LD,且沒有做任何防護:
```JavaScript
// 錯誤示範:危險的 Next.js 代碼
export default function UserProfile({ userData }) {
const jsonLd = {
"@context": "https://schema.org",
"@type": "Person",
"name": userData.name // 假設這裡是駭客輸入的惡意字串
};
return (
"}
```
1. 瀏覽器讀到 name":"某個駭客"。
2. 緊接著讀到了 。瀏覽器立刻切換模式,認為 JSON-LD 結束了。
3. 接下來,它看到了新的 。瀏覽器心想:「喔,主人有令,執行這段程式碼!」
4. **Boom!** 惡意腳本執行成功。這就是 XSS(跨站腳本攻擊)的一種經典變體。
這就像是有人在你的名片背面偷偷寫了一段詛咒咒語,任何拿到這張名片並且大聲朗讀的人都會中邪。
Next.js 防禦實戰:替危險字元穿上防護衣
要防禦這種攻擊,我們不需要什麼高深的防火牆,只需要做一件事:**Sanitize(淨化)**。
我們要讓 `JSON.stringify` 產生的字串中,所有的 `<` 符號都消失,替換成無害的 Unicode 轉義序列 `\u003c`。瀏覽器在解析 JSON 時,看得懂 `\u003c` 就是 `<`,但 HTML 解析器不會把它當作標籤的開始。
以下是 Next.js (App Router/Pages Router 通用) 的安全實作範例:
```JavaScript
// lib/safe-json-ld.js
/**
* 安全序列化 JSON-LD 資料
* 防止 XSS 攻擊,特別是針對 閉合標籤攻擊
* * 原理:將 < 替換為 \u003c,讓瀏覽器不將其視為 HTML 標籤的開始
*/
export function safeJsonLd(data) {
return {
__html: JSON.stringify(data).replace(/..."
const data = await getPost(params.slug);
const jsonLd = {
"@context": "https://schema.org",
"@type": "Article",
"headline": data.title,
// ...其他欄位
};
return (
{/* ✅ 使用安全函式注入 JSON-LD */}
就會變成 \u003c/script>,這在 HTML 眼裡只是一串普通的文字,完全無害。
檔案上傳漏洞:披著羊皮的狼
講完了文字的注入,我們來談談更「重量級」、更具毀滅性的威脅——檔案上傳。
現在的網站功能越來越豐富,上傳頭像、提交 PDF 履歷、上傳收據圖片,這些都是再常見不過的功能。但對於資安人員來說,每一個「上傳按鈕」都是一個潛在的核彈發射井。
偽裝的藝術:你以為是圖片,其實是炸彈
駭客的終極目標是上傳一個 Web Shell(網頁後門程式),例如 `evil.py` 或 `shell.php`,並讓你的伺服器執行它。一旦執行,駭客就獲得了你伺服器的控制權,可以直接下指令操作你的電腦。
但是,聰明的開發者通常會檢查副檔名。「我只要限制只能上傳 `.jpg` 或 `.png` 不就好了嗎?」
太天真了!駭客會使用一種叫做 Extension Spoofing(副檔名欺騙) 的手法。他們把 `evil.exe` 改名為 `evil.jpg`。 或者,他們會在 HTTP 請求中謊報 `Content-Type` 為 `image/jpeg`,試圖騙過那些只檢查 MIME Type 的伺服器。
這就像是一個強盜穿著快遞員的制服(副檔名),拿著一張寫著「我是好人」的識別證(Content-Type)來敲你的門。如果你只看制服和識別證就開門,那你家就危險了。
Magic Number:檔案的數位 DNA
要真正識別一個檔案的真面目,我們不能看外樣(檔名),也不能聽它的一面之詞(MIME Type),我們必須看它的「基因」。
這個基因,就是 **Magic Number(魔術數字)**,又稱為檔案簽名。
不管你把一個 JPEG 圖片改名成什麼,它的檔案**最開頭幾個位元組**,永遠都是固定的一串十六進位數字。這就像是人類的 DNA,不管你穿什麼衣服,DNA 是不會變的。
| 檔案類型 |
常見副檔名 |
Magic Number (Hex) |
ASCII 樣現 |
| JPEG |
.jpg / .jpeg |
FF D8 FF |
ÿØÿ |
| PNG |
.png |
89 50 4E 47 |
.PNG |
| PDF |
.pdf |
25 50 44 46 |
%PDF |
| GIF |
.gif |
47 49 46 38 |
GIF8 |
所以,我們的防禦策略就是:**驗DNA**!
Next.js API Route 防禦實戰:全端工程師的盾牌
如果你是使用 Next.js 的全端架構(API Routes 或 Server Actions),我們可以使用 `file-type` 這個純 JS 實作的庫來檢查 Buffer。
**安裝**:
```Bash
npm install file-type
```
**Next.js App Router API 範例**:
```JavaScript
// app/api/upload/route.js
import { NextResponse } from 'next/server';
import { fileTypeFromBuffer } from 'file-type';
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs/promises';
import path from 'path';
export async function POST(request) {
try {
const formData = await request.formData();
const file = formData.get('file');
if (!file) {
return NextResponse.json({ error: "No file uploaded" }, { status: 400 });
}
// 將 File 物件轉換為 Buffer 以便檢查
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
// 1. Magic Number 驗證
// file-type 會檢查二進位簽名,這是最誠實的判斷
const type = await fileTypeFromBuffer(buffer);
// 定義白名單
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
// 2. 檢查是否偵測到類型,以及類型是否在白名單內
if (!type || !allowedTypes.includes(type.mime)) {
return NextResponse.json(
{ error: `Invalid file type. Detected: ${type?.mime || 'Unknown'}` },
{ status: 400 }
);
}
// 3. 安全儲存:隨機重命名
// 永遠不要信任用戶的原始檔名,永遠!
const fileName = uuidv4() + '.' + type.ext;
// 注意:實際生產環境建議存到 S3 或專用 Object Storage
// 這裡示範存到本機,記得要配合上一篇文章提到的「禁止執行」權限設定
const uploadDir = path.join(process.cwd(), 'public/uploads');
await fs.mkdir(uploadDir, { recursive: true });
await fs.writeFile(path.join(uploadDir, fileName), buffer);
return NextResponse.json({ success: true, fileName });
} catch (error) {
console.error("Upload error:", error);
return NextResponse.json({ error: "Upload failed" }, { status: 500 });
}
}
```
這段程式碼做了三層防護:
1. **驗證 DNA**: 用 `fileTypeFromBuffer` 確保檔案內容真的是圖片。
2. **白名單機制**: 只允許特定的類型,拒絕一切不明物體。
3. **重新命名**: 用 UUID 產生亂碼檔名,防止駭客利用特殊檔名(如 `../../etc/passwd`)進行路徑攻擊。
結語:資安不是產品功能,而是一種生活態度
我們在這篇文章中深入探討了 **JSON-LD 注入** 和 **檔案上傳漏洞**。從 Next.js 的 `dangerouslySetInnerHTML` 安全處理,到 Magic Number 驗證,我們建立了一套立體的應用層防禦體系。
回顧這兩篇文章,你會發現貫穿全文的一個核心概念就是「**零信任 (Zero Trust**)」。
- **Docker**: 不信任預設權限,使用非 root 用戶。
- **Next.js**: 不信任 JSON.stringify 的輸出,手動替換危險字元。
- **File Upload**: 不信任用戶傳來的檔名與 MIME Type,堅持檢查 Magic Number。
對於開發者來說,這意味著要從單純的 DevOps(開發維運)轉向 **DevSecOps**(開發、資安、維運)。資安不應該是產品上線前最後一週才匆忙做的「滲透測試」,而應該是寫每一行程式碼時的肌肉記憶。
資安是一場永無止境的貓鼠遊戲。駭客在進步,我們也要學習。希望這份報告能成為你資安修行路上的一盞明燈,讓你的網站像一座堅不可摧的數位堡壘。
下次當我們再聊到資安時,希望是因為我們成功防禦了一次攻擊,而不是又要寫檢討報告了,也希望鍛碼匠今年不用再拿十字鎬去挖礦啦!
## 延伸閱讀
- [如何運用 Cloudflare 防禦前沿 AI 駭客攻擊以提升台灣網站的資安](https://fordige.com/blog/cloudflare-frontier-ai-cyber-defense):同樣談「資安防護」主題,延伸閱讀。
- [如何在 2026 年強化網站資安的三道防線](https://fordige.com/blog/website-security-ai-era-real-battle):同樣談「資安防護」主題,延伸閱讀。
- [2025 年底挖礦病毒驚魂記:我的 Next.js 容器是如何淪為駭客礦機的?(](https://fordige.com/blog/2025-nextjs-docker-mining-malware-defense-guide):同樣談「資安防護」主題,延伸閱讀。
- [JSON 注入攻擊完整解析:工程師必須知道的防護實務(2026)](https://fordige.com/blog/json-injection-attack-prevention-guide):同樣談「資安防護」主題,延伸閱讀。
- [SEO 排名優化服務](https://fordige.com/seo-ranking-optimization):
## 參考資源
- [OWASP Top 10 官方文件](https://owasp.org/www-project-top-ten/)
- [NIST CSF 2.0 網路安全框架](https://www.nist.gov/cyberframework)
- [Cloudflare Web Application Firewall](https://www.cloudflare.com/application-services/products/waf/)
網站設計服務
想找專業的網頁設計公司?
鍛碼匠專精企業形象網站、部落格網站、電商網站與 SEO 優化,歡迎聊聊你的需求。