iT邦幫忙

2026 iThome 鐵人賽

DAY 13
2
Security

《朝 HTB CPTS 前進:資安新手的 30 天實作筆記》系列 第 13 篇

Day 13 — Command Injection:一個查庫存的按鈕,竟然能讓伺服器執行指令 👀

  • 分享至 

  • xImage
  •  

昨天整理了碰到 Web 服務時的完整測試流程——先做 Information Gathering,看看網站本身、原始碼,以及 robots.txt 有沒有可以直接利用的資訊。

真的沒找到突破口,再開始跑目錄爆破,找看看有沒有其他隱藏的目錄或檔案。

找到新的目錄或頁面後,再對它做一次 Information Gathering,繼續看看有沒有新的線索。

如果前面的資訊還不足以達到我們的目的,而網站上又有輸入點,就可以進一步針對輸入點做測試,依照輸入點的類型選擇適合的攻擊測試。

https://ithelp.ithome.com.tw/upload/images/20260927/20184189PCCTfID6OA.png

我們在Day 12已經一起看過 SQL Injection,今天就來看看Web的另一個常見攻擊Command Injection。

它跟 SQL Injection 的成因邏輯其實很像,只是這次的目標不是資料庫,而是作業系統。

也因為這樣,如果成功,影響的不只是資料庫裡的資料,還可能進一步影響到伺服器上的系統環境。

我們趕快一起來看看!


Command Injection 的成因

有些網站需要直接呼叫系統指令來完成某些功能,例如查庫存、產生報表、發送通知。

這時候,後端可能會把使用者傳進來的資料,直接帶進系統指令裡一起執行。

正常情況下,我們傳的是「商店編號」或「檔案名稱」這類資料。

但如果網站沒有做好輸入檢查,我們就可能在原本的輸入裡加入其他指令,讓伺服器一起執行。這就是 Command Injection。

不一定要有輸入框才算輸入點。按鈕、下拉選單,甚至是 URL 或 Request 裡的參數,只要資料會被送到伺服器,就有可能成為我們進一步測試的地方。

按鈕按下去,背後就有一個 HTTP 請求送出去,裡面帶著我們傳入的參數——只是前端沒有讓我們直接改。Burp 可以攔截這個請求,讓我們在送出去之前修改裡面的值。

今天的 Lab 就是這種情況——購物網站的商品頁面有個 Check stock 功能,選一個商品查庫存。看起來只是一個普通的按鈕,但按下去會把參數傳到伺服器去處理,這就是輸入點。

看到輸入點,先試 SQL Injection——為什麼?

「查庫存」這種功能,後端很可能會查詢資料庫,所以 SQL Injection 可以作為其中一個測試方向。像是先加入單引號,觀察有沒有 SQL 錯誤或其他異常反應。

如果 SQL Injection 沒有明顯反應,再換個方向想:

後端不一定是查資料庫——也可能是執行系統指令去查庫存。我們從外面看不到它怎麼做的 >< 如果後端是用系統指令的話,就可能有 Command Injection 的機會,所以SQL Injection、Command Injection都要試。

那要如何測試 Command Injection?我們可以在原本傳給伺服器的值後面「加料」——塞進我們想要執行的指令><


注入符號——怎麼在指令後面「加料」

;——不管前面成不成功,都繼續跑

check_stock 1; whoami

不管查庫存有沒有成功,whoami 都會執行。最常用,最直接。先試這個。

&&——前面成功才跑下一個

check_stock 1 && whoami

查庫存成功了,才繼續跑 whoami。如果查庫存失敗,whoami 就不跑了。

||——前面失敗才跑下一個

check_stock 1 || whoami

前面的指令成功,whoami 就不會執行;只有前面的指令失敗,才會繼續執行 whoami。這種做法輸出最一目了然。

|——把前面的輸出傳給後面

check_stock 1 | whoami

把查庫存的結果傳給 whoami 當輸入。

先試 ; 就好——不管前面的指令成功或失敗,後面的指令都會繼續執行。


假設這個功能的後端是用系統指令做的:

storeId = request.GET['storeId']
os.system("check_stock " + storeId)

我們選商店 1,系統就跑:

check_stock 1

正常,回傳庫存數量。

但如果我們把 storeId 改成 1; whoami,系統就跑:

check_stock 1; whoami

拆開來看:

check_stock 1   ← 查了庫存
; whoami        ← 然後跑了 whoami!

; 在 Shell 裡是「跑完這個,再跑下一個」的意思——whoami 真的在伺服器上執行了,伺服器的使用者名稱就這樣跑出來了。

根本問題:跟 SQL Injection 一模一樣——網站沒有分辨我們傳的是「正常的輸入值」還是「系統指令」,直接把輸入拼進作業系統執行。


Command Injection vs. SQL Injection:哪裡像、哪裡不同

SQL Injection Command Injection
成因 輸入被拼進 SQL 查詢 輸入被拼進系統指令
主要影響 資料庫中的資料 作業系統層級的指令執行
實際影響 取決於資料庫權限與設定 取決於 Web 服務帳號的權限與環境
常見防禦 Prepared Statement 輸入驗證、避免直接呼叫系統指令

兩個漏洞實際能造成多大的影響,還是要看當下執行的帳號有哪些權限。。

記得 Bashed 嗎?

當時 Web 服務跑在 www-data,所以就算透過 Web 執行指令,也還是只有 www-data 的權限,很多需要 root 權限才能做的事情還是沒辦法執行。


今天的實驗:PortSwigger Lab

Lab:OS command injection, simple case
https://ithelp.ithome.com.tw/upload/images/20260927/201841894CDNyPfH7R.png

目標:注入whoami,讓頁面顯示目前的使用者。


進入練習的LAB

點 ACCESS THE LAB,進入今天練習的LAB
https://ithelp.ithome.com.tw/upload/images/20260927/20184189rPXxjSy0Ug.png


先設定 Burp Suite

跟 Day 12 一樣,我們用 Burp Repeater 來測試:


Step 1:點選 View details

進入 Lab 之後,點任何一個商品的 View details。
https://ithelp.ithome.com.tw/upload/images/20260927/201841892PsMsmfzNn.png


Step 2:把請求送到 Repeater

在 Burp Intercept 裡看到請求,右鍵 → Send to Repeater。

https://ithelp.ithome.com.tw/upload/images/20260927/201841896sDCOWZ8D4.png

按 Send,看看這個 Request 裡有沒有可以控制的參數,也順便看看 Response 裡有沒有洩漏什麼有用的資訊。
https://ithelp.ithome.com.tw/upload/images/20260927/20184189DBwv9EO2ge.png
目前這個 Request 看起來沒有什麼明顯可以測試的地方,Response 也沒有看到什麼明顯的資訊洩漏。

回到 Intercept,把 Intercept 關掉。
https://ithelp.ithome.com.tw/upload/images/20260927/20184189qFfog7Wkpb.png

回到瀏覽器,這時候就可以正常進入 Details 頁面
https://ithelp.ithome.com.tw/upload/images/20260927/20184189WArLiIhX3b.png

再回 Burp,把 Intercept 開起來
https://ithelp.ithome.com.tw/upload/images/20260927/20184189pMG5KuWYUl.png
回瀏覽器頁面按 Check stock。

回 Burp 可以看到我們攔截到這個請求——裡面有 storeId、productId 這類參數。

這兩個參數是用來告訴伺服器「要查哪間店、哪個商品」,而這些值會跟著 Request 傳到伺服器,我們也可以自己修改。

所以雖然畫面上沒有看到輸入框,但這些 Request 裡的參數一樣可以成為我們進一步測試的輸入點。

把這個請求右鍵 → Send to Repeater。
https://ithelp.ithome.com.tw/upload/images/20260927/201841897ouLCYoivp.png

可以在 Repeater 看到這個 Request 的原始內容。
https://ithelp.ithome.com.tw/upload/images/20260927/20184189COItRnAj7x.png


Step 3:在 Repeater 修改 payload,測試 Command Injection

在 Repeater 裡,把參數的值改成:

productId=1&storeId=1;whoami

按 Send。
https://ithelp.ithome.com.tw/upload/images/20260927/20184189XqbIDteGe8.png

破關哩!!!🎉

不只產品庫存,我們也成功印出 current user——確認有 Command Injection。

但……還沒結束 ><


溫故知新時間: 回顧拿到 Shell 後的偵查 SOP

雖然這次我們是透過 Command Injection 執行指令,還沒有拿到真正的interactive Shell,但剛好可以趁這個機會,把之前整理的「拿到 Shell 後的 SOP」練習一下!

現在在哪個目錄?
這也是拿到 Shell 後很基本的偵查步驟,之前在 Bashed 還沒有整理進來,今天就一起補上~

這次我們把 Repeater 中的 Body 參數改成:

productId=1&storeId=1;pwd

https://ithelp.ithome.com.tw/upload/images/20260927/20184189we5YRZK9kb.png

可以知道我們現在在的目錄。


接著我們換試其他常用的注入符號,觀察每個的反應,也順便搭配之前整理的「拿到 Shell 後的 SOP」,一起溫故知新 xdd

嘗試&&

productId=1&storeId=1&&id

https://ithelp.ithome.com.tw/upload/images/20260927/20184189Rz2rijTQ8L.png

&& 也是常用的注入符號,需要前面的指令成功執行後,才會執行後面的指令。

理論上,如果前面的查詢成功,id 應該會接著執行。

為什麼沒有出現 id 的結果?

因為這個 Lab 的 Request Body 使用 & 來分隔參數。

所以 productId=1&storeId=1&&id 會先被當成 Request 的參數來解析,而不是完整地把 &&id 當成要傳給 Shell 的內容。也就是說,&&id 沒有照我們預期的方式進到系統指令裡。

換成 ; 就成功了:

productId=1&storeId=1;id

https://ithelp.ithome.com.tw/upload/images/20260927/20184189NB1xRB92pG.png
用剛剛成功注入的;,確定 id 指令是可以執行的。


嘗試 |

productId=1&storeId=1|sudo -l

https://ithelp.ithome.com.tw/upload/images/20260927/20184189c0Vc6qaW8V.png

出現 sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper

為什麼失敗?
sudo -l 在這個執行環境下需要處理密碼/終端機相關的互動,但我們透過 Command Injection 執行指令時,並沒有提供這樣的互動式終端環境,所以這次沒有辦法直接完成。

或許可以透過 Reverse Shell 拿到一個真正的終端機,就像 Bashed 那樣——有了穩定的 shell 之後,很多受限的指令就可以執行了。


嘗試 ||:

productId=1&storeId=1||hostname

https://ithelp.ithome.com.tw/upload/images/20260927/20184189tUPaLgecN2.png
如果前面的指令失敗,才會繼續執行 hostname。所以如果我們預期前面的指令可能失敗,就可以用 || 來測試。


嘗試讓 productId=1&storeId=1 失敗:

productId=1&storeId=||hostname

https://ithelp.ithome.com.tw/upload/images/20260927/201841898TxwptW6l1.png

Response 連庫存都不見惹——hostname 可能有執行,但輸出沒有被回傳到頁面。

&& 跟 || 在這個 Lab 的環境都沒有成功——不是每個符號都適用每個環境,遇到失敗就換符號試,不代表沒有 Command Injection。

換成 ; 就成功了:

productId=1&storeId=1;hostname

https://ithelp.ithome.com.tw/upload/images/20260927/20184189x3tZDbrqLJ.png
以剛剛成功注入的符號;確定是可以找到hostname的


我們就用剛剛確認可以成功的 ;,繼續做基本的主機資訊偵查~

productId=1&storeId=1;uname -a

https://ithelp.ithome.com.tw/upload/images/20260927/20184189A7qh6WkEkt.png

uname -a 可以看到 Kernel 版本、主機名稱、系統架構等資訊。

productId=1&storeId=1;cat /etc/os-release

os-release是檔案,不是指令,所以要用cat讀
https://ithelp.ithome.com.tw/upload/images/20260927/20184189phg6b68U1I.png


今天的偵查做到這裡——從確認「我是誰」到了解「目標機器的作業系統是什麼」

但這只是最基本的><拿到 Shell 之後其實有很多資訊要做偵查。這裡將今天補充的步驟也納入我們之前整理的 Shell 拿到後的 SOP:
https://ithelp.ithome.com.tw/upload/images/20260927/20184189d3tPO1hu1T.png
今天沒碰到的部分,在前面打 Bashed 的時候有走過一遍,可以參考我們Day 8到Day 10 的Bashed靶機操作記錄


防禦:怎麼避免 Command Injection

最根本的:不要直接呼叫系統指令

直接呼叫系統指令,就等於把使用者的輸入丟給 Shell 去解析——Shell 遇到 ;、&& 這些符號就會照著執行,這就是問題所在。

很多語言都有內建的方式可以做同樣的事,而且不需要經過 Shell。例如要測試某台機器有沒有在線,Python 可以用 socket 直接建立連線,不用呼叫系統的 ping。這樣使用者的輸入只是純資料,不會被當成指令執行。

一定要呼叫的話:嚴格驗證輸入

如果真的沒辦法避免,就要確認使用者傳進來的值是我們預期的格式——商店 ID 只能是數字、檔名只能有英數字,不符合就拒絕。

而且前端後端都要驗證。前端驗證用 Burp 一秒繞過,後端沒有驗證等於沒有驗證。

把 Web 服務跑在低權限帳號下

就算真的被注入了,低權限帳號能做的事也有限。這也是為什麼 Bashed 拿到 shell 之後還要提權——www-data 不是 root,能做的事受限。


回顧時間

今天的 Command Injection,跟昨天的 SQL Injection,其實有一個很核心的共同點:

網站把使用者的輸入,當成了「程式的一部分」執行,而不是單純當成資料處理。

SQL Injection 的輸入,最後被拼進了 SQL 查詢;
Command Injection 的輸入,則可能被拼進系統指令。

所以根本的問題其實很像,只是「輸入最後被帶到哪裡」不一樣。

今天還有一個重要發現:

沒有輸入框,不代表沒有輸入點。

以前看到一個按鈕,我可能完全不會把它跟 Injection 聯想在一起。

但實際透過 Burp Suite 攔下 HTTP Request 之後才發現,
按鈕背後其實一樣有資料送到伺服器。

只要 Request 裡有我們可以控制的參數,就有可能成為進一步測試的地方。

這次實際操作過之後,才比較理解:

不要只看畫面上有沒有輸入框,而是要去看資料實際怎麼傳。

搞清楚這件事之後,以後看到 Web 服務,腦袋就會慢慢開始出現一些問題:

「這裡有沒有資料會傳到伺服器?」

「這個輸入最後會被拿去做什麼?」

「它有沒有可能被拼進 SQL 查詢裡?」

「有沒有可能被當成系統指令執行?」

我覺得這就是 Web 漏洞測試很基本、但也很重要的思路:

看到輸入 → 追它去了哪裡 → 判斷它最後被怎麼使用。

明天 Day 14,我們來回顧這兩週走過的路~

從 Nmap 開始,一路到 Web 漏洞,看看哪些觀念已經透過這兩週的練習慢慢變成我們的 DNA 🧬哪些還是看到會滿頭問號 xddd

再看看接下來的路,怎麼一起繼續往 CPTS 前進!

明天見啦~


上一篇
Day 12 — SQL Injection:一個單引號,就能撬開整個資料庫
下一篇
Day 14 — 兩週回顧 & SQL Injection 補強:搞懂 INFORMATION_SCHEMA,自己把資料撈出來
系列文
《朝 HTB CPTS 前進:資安新手的 30 天實作筆記》 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言