
這章要學會什麼
- 建立自動化測試專案,一個指令跑完「只測單一規則」跟「串起整條路徑」兩層測試。
- 用真正的資料庫啟動整個網站,對一個獨立的測試資料庫執行「端到端」的安全案例。
- 把課程大綱列出的八種風險,各自寫成至少一個「應該被拒絕」的測試。
- 照著「先寫一個會失敗的測試→修正程式→測試變成通過」的順序,修掉本章實際找到的一個資料外洩缺陷。
- 用瀏覽器自動化工具量測三種螢幕寬度下有沒有跑版跟太小的文字,檢查鍵盤操作與表單錯誤訊息,整理成測試報告。
先備知識
要先完成第 13 章:網站已經有前臺、會員中心、後臺、AI 呼叫入口、儀表板跟紀錄查詢。要知道 HTTP 狀態碼 302、400、401、403、404 分別代表什麼意思;知道自動化測試的基本概念(一個測試案例、一個判斷式)就足夠。
觀念白話講
測什麼:成功之外,更要測「應該要失敗」的事
前面 13 章的老師示範,大多在證明「功能做得到」。安全問題剛好相反:出事的地方通常是「不該做到,卻真的做到了」。所以這一章每個測試的形式都是:「假裝自己是某種身分,去做某件不該做的事,系統一定要拒絕,而且資料不能被真的改變」。
| 風險 | 攻擊者的想法 | 測試怎麼證明 |
|---|---|---|
| 角色越權 | 一般會員直接打後臺網址、呼叫管理用程式入口;註冊時偷塞角色欄位 | 應該得到拒絕存取或權限不足;資料庫查不到多出來的管理者角色 |
| 跨會員資料 | 改網址上的編號去看別人的草稿或紀錄 | 應該得到「找不到」 |
| 防偽造請求 | 誘使管理員的瀏覽器在別的網站送出表單 | 應該被拒絕,而且對象沒有真的被停權 |
| 伺服器端請求偽造 | 讓平台去打內網或雲端內部憑證位址 | 私有位址全部被判定為擋下 |
| 密鑰遮罩 | 想從任何頁面或程式呼叫看到上游密鑰明文 | 資料庫裡只有密文,所有頁面都看不到明文 |
| 重複審核 | 同一件申請核准兩次,多拿一筆授權 | 第二次一定被拒絕;使用權只會有一筆 |
| 版本變更 | 用舊版本的授權去呼叫新版本 | 應該被當作「沒有這個工具」 |
| 額度併發 | 同時送出大量呼叫,企圖突破額度上限 | 多個同時發生的請求搶額度,成功數剛好等於剩餘額度 |
三層檢查:規則本身、串起整條路、真實瀏覽器
| 層級 | 測什麼 | 速度與範圍 |
|---|---|---|
| 單元測試 | 各個服務層的規則本身對不對,用自己寫的假資料層代替真正的資料庫 | 毫秒等級,完全不需要資料庫 |
| 整合測試 | 經過路由、驗證、授權、防偽造、控制器、服務、資料庫的完整一條請求 | 整個網站在測試程序裡真的啟動起來,但不佔用開發用的埠號 |
| 瀏覽器檢查 | 版面、字級、鍵盤操作、表單訊息——只有真的瀏覽器才看得出來的東西 | 三種螢幕寬度 × 六個頁面 |
先寫一個會失敗的測試,再修正
本章用探測腳本實際找到一個問題:某位會員可以打開另一位會員尚未公開的草稿服務的「申請使用」頁面,頁面回傳成功狀態,還顯示出草稿的服務名稱跟提供者姓名;而同一個版本在公開目錄裡卻正確地顯示「找不到」。這件事本身不會讓對方真的申請成功(送出時服務層依然會拒絕),但確實洩漏了別人還沒公開的資料。
修正的標準順序是:①先寫一個描述「正確行為」的測試(申請頁應該回傳「找不到」);②執行它,先確認它真的會失敗——這一步很關鍵,證明這個測試真的測得到這個缺陷,而不是寫錯了永遠都會通過;③修正程式;④再執行一次,確認通過,而且其他測試也都還維持通過。
老師示範做了什麼
示範情境:先用探測腳本對執行中的網站打一輪「不該成功」的請求,找出真正的缺陷;再執行自動化測試,看修正前後的差別;最後量測三種螢幕寬度下的版面與鍵盤操作。
探測腳本測了十幾種情況,大多數都正確地被擋下:一般會員打後臺網址得到拒絕;改別人的草稿編號得到「找不到」;管理員的請求沒帶防偽造權杖得到拒絕;註冊時偷塞角色欄位,新帳號依然打不進後臺;管理員頁面完全不含密鑰明文。但有兩項「意外通過」:對別人尚未公開版本的申請頁提出請求,居然得到成功狀態——這正是前面提到的那個缺陷,追下去發現送出申請本身還是會被服務層擋下(不會真的申請成功),但頁面內容確實洩漏了別人還沒公開的服務名稱。
老師先跑一次自動化測試(修正前):果然有一項測試失敗,錯誤訊息清楚顯示「預期得到找不到,實際卻得到成功」——這證明測試案例確實抓得到這個問題。接著在服務層修正申請頁的邏輯,讓它跟公開目錄用同一套「只有已發布版本才回傳內容」的規則,重新執行測試,全部項目變成通過。
瀏覽器量測第一次就發現:手機螢幕寬度下有兩處文字小於 12 像素(呼叫方法的小標籤跟表格裡的錯誤代碼),修正樣式表後全部達到至少 12 像素。三種螢幕寬度、六個頁面的組合,量測「頁面實際內容寬度」跟「可視窗寬度」的差值,全部都是 0,代表沒有任何一種組合出現不該有的整頁橫向捲動。
自己動手的步驟
步驟一:建立測試專案
用測試框架建立一個測試專案,把版本號統一寫進整個方案共用的套件版本清單裡(不要讓測試專案自己的檔案各自寫死版本號,否則會跟中央套件管理的機制衝突)。網站的啟動程式要加一行讓測試專案能夠以「啟動整個網站」的方式使用它。
步驟二:單元測試——只測規則本身
不碰資料庫,用自訂的假資料層驗證第 11 章的授權規則,例如:「只有舊版本使用權,去呼叫新版本」應該被判定為「當作沒有這個工具」;「使用權已撤銷、已到期、狀態雖然仍是有效但時間其實已過期」這三種情況都應該分別得到對應的拒絕代碼而且不會扣掉額度。同一批測試也涵蓋伺服器端請求偽造的位址判斷、轉回網址規則、「管理員不能對自己動手」、P95 算法、臺灣日期邊界這幾類規則。
步驟三:整合測試的基礎——獨立的測試資料庫
用同一台資料庫伺服器、同一組帳密,但把資料庫名稱換成專屬的測試資料庫名稱;每次測試開始前都先確認連線字串真的指向這個測試專用的資料庫名稱(多一層保險,設定錯一個字就可能誤刪開發資料庫,所以要在刪除前再次確認名稱),然後刪除重建、套用跟正式環境完全相同的一套建置腳本。所有整合測試共用同一份網站跟資料庫,只建立一次、依序執行,彼此不會互相干擾。
步驟四:安全案例與缺陷修正
先寫測試描述正確行為(例如「別人的草稿,不能透過申請頁看到名稱」),修正前執行確認它真的失敗,再修正程式讓申請頁的資料來源跟公開目錄用同一套「只回傳已發布版本」的判斷邏輯,重新執行確認通過。其他安全案例還包括:匿名或一般會員嘗試進後臺、後臺程式呼叫入口的權限判斷、註冊偷塞角色、防偽造請求測試、別人的編輯頁跟密鑰設定頁、別人的追查代碼、上游密鑰不外洩、重複核准只產生一筆使用權——額度併發的測試則是真正同時發送多個並行的資料庫操作,驗證原子預留機制在高並發下依然正確。
步驟五:版面、鍵盤、表單與可讀性
用瀏覽器自動化工具,在三種螢幕寬度下開啟每個頁面,量測「頁面實際內容寬度減掉可視窗寬度」是否為 0(不是 0 就代表有不該出現的橫向捲動),以及頁面上每個有文字的元素實際渲染出來的字級,找出小於 12 像素的項目。同時檢查第一次按 Tab 鍵的焦點落在哪裡、手機選單展開後的狀態標記、空白送出表單時錯誤訊息是否正確顯示在對應欄位下方且是中文。
常見錯誤
| 症狀 | 原因 | 怎麼處理 |
|---|---|---|
| 建置測試專案時報告版本衝突 | 測試框架的範本自動在專案檔裡寫了版本號,跟中央套件管理機制衝突 | 把版本號都移到共用的套件版本清單,專案檔只留套件名稱 |
| 某個「密鑰只存密文」的測試意外失敗 | 測試本身的假設寫錯了——挑的那筆示範資料原本就設定成「不需認證」,設定憑證被正確拒絕是預期行為 | 修正測試本身的假設,不是程式的缺陷 |
| 手機螢幕上某個小標籤字級只有 11.25 像素 | 手機的基準字級比桌面小,相對單位換算下來低於 12 像素 | 用「取兩者較大值」的寫法,確保任何螢幕寬度下都不低於 12 像素 |
怎麼驗收+反向驗證
- 提交成功與拒絕案例的證據:自動化測試全部通過;測試報告依八種風險列出每個拒絕案例的結果,並附螢幕寬度量測表。
- 修復缺陷的完整證據:「草稿名稱外洩」這項缺陷要有「修正前測試失敗→修正→測試通過」的完整紀錄;把修正暫時還原,測試要再次失敗,證明測試真的測得到這個問題。
- 反向驗證(抽查):一般會員嘗試停權別人得到拒絕;管理員不帶防偽造權杖操作得到拒絕;同一件申請嘗試核准兩次,只會有 1 筆使用權。