奇淫技巧之突破Runtime.exec限制獲取shell 命令執行環境
0x00. 前言
在一次內部安全測試中,碰到個java 站點,有一處任意程式碼執行漏洞,還可以回顯,心理頓時美滋滋,但是當我執行稍微複雜點shell 命令的時候,發現回顯明顯不對,執行 ls -l /opt
和 ls -l /opt/ |grep tomcat
兩個命令輸出結果完全一樣, grep
完全沒有生效,處於好奇,找到了相關開發,看了下漏洞問題出的原始碼,原來是引數沒有過濾,直接丟進 Runtime.getRuntime().exec()
中執行了。雖然可以通過遠端download 指令碼直接都給 Runtime.exec()
執行繞過這個限制,但是出於好奇心,我google不少資料,查閱一番資料之後,終於找到了問題的原因,然後發現了個繞過限制的小技巧並獲取完整的shell 執行命令環境,詳文如下。
0x01. 先介紹線下Runtime類執行外部命令的方法介紹
要執行JVM中外的程式, Runtime
類提供瞭如下方法:
簡單解釋如下:
`exec(String command)
在單獨的程序中執行指定的字串命令。
exec(String[] cmdarray)
在單獨的程序中執行指定命令和變數。
exec(String[] cmdarray, String[] envp)
在指定環境的獨立程序中執行指定命令和變數。
exec(String[] cmdarray, String[] envp, File dir)
在指定環境和工作目錄的獨立程序中執行指定的命令和變數。
exec(String command, String[] envp)
在指定環境的單獨程序中執行指定的字串命令。
exec(String command, String[] envp, File dir)
在有指定環境和工作目錄的獨立程序中執行指定的字串命令。`
0x00. 中涉及的案例的環境就是第一個 exec(String command)
,直接執行外部傳進來的命令字串, Runtime.getRuntime().exec()
執行外部命令的原理就是fork一個單獨的程序,然後直接執行這個命令。 exec(String command)
這個方法是沒法指定shell為命令上下文環境,所以這也就解釋了為啥
ls -l /opt
和 ls -l /opt |grep tomtcat
結果一樣
因為 |
是shell環境下的管道命令,只有shell的執行上下文環境才識別,直接fork程序執行ls 命令是不識別 |
、 >
重定向等shell中的複雜命令
如何突破exec() 無shell上下文執行環境限制獲取完整shell執行環境呢,0x02 或有詳細演示與分析
0x02. 突破限制之旅
我現在沒了內部系統的那個測試環境,自己寫了個演示程式碼,如下:
1、演示程式碼
執行 javac Test.java
進行編譯就可以了
2、演示分析 ls -l /opt與ls -l /opt |grep anquanke 為啥結果一樣
1) 執行 ls -l /opt
2) 執行 ls -l /opt |grep anquanke
提示 當前目錄中檔案或目錄:|grep 不存在
提示 當前目錄下檔案或目錄:anquanke 不存在
為啥會這樣呢?
這是因為 Runtime.exec
對傳入的字串是按照空格進行引數區分的,在這裡
|grep
、 anquanke
都被認為是檔案或者目錄,這裡沒有shell 上下文環境,管道命令 |是無法識別的
注:這裡要補充說明下,為啥 ls -l /opt |grep anquanke
會列印錯誤資訊,因為程式碼就也把錯誤資訊打印出來了,如果把錯誤資訊去掉,那麼兩個命令執行結果就一樣了
3、第一次突破嘗試
既然 Runtime.exec()
無shell上下文環境,那麼我呼叫sh -c 用sh直接執行命令,這下總可以了吧
1)執行 java Test 'sh -c /usr/bin/ls -l /opt |grep anquanke'
但是上圖的執行結果直接顯示當前目錄的內容,而不是 /opt
目錄下的
這是為啥?
sh -c
根據空格進行區分要執行的命令,如上圖,執行的命令就是 /usr/bin/ls
,後面的引數都會被忽略(因為 -l
、 /opt
、 |grep
、 anquanke
都被認為是sh的引數),要想讓後面的 -l
之類引數也被識別,那就用引號括起來,如下
2)執行 java Test 'sh -c "/usr/bin/ls -l /opt |grep anquanke"'
我用引號將傳遞給sh 執行的命令括起來了啊,為啥還是報錯呢?
這是因為 Rntime.exec()
區分命令的依據是空格, 上面的命令傳遞給 Runtime.exec
後相當於相當於:
sh -c '"/usr/bin/ls' '-l' '/opt|grep' 'anquanke'
這就明白為啥會報錯了吧
4、第二次嘗試突破限制,獲取完整shell執行環境
上面的突破之所以失敗,是因為受限於 Runtime.exec
依據空格劃分引數的規則,sh -c 有時候不能直接執行復雜的命令,於是想到可以用管道傳遞給sh自身然後間接執行復雜命令嘛
於是有了下面的突破命令:
java Test 'sh -c $@|sh 0 echo /usr/bin/ls -l /opt|grep anquanke'
解釋下為啥這樣就行
sh -c $@|sh 0 echo /usr/bin/ls -l /opt|grep anquanke
這個命令字串傳給 Runtime.exec()
之後,按照 Runtime.exec
依據空格劃分引數的規則,命令就變成:
sh -c '$@|sh' '0' 'echo' '/usr/bin/ls' '-l' '/opt|grep' 'anquanke'
就相當於:
這個要補充下知識點了: sh -c 'command' x1 x2 x3
x1 被認為是指令碼名稱,相當於$0, x2、x3 相當於 $1、$2
命令 sh -c '$@|sh' 0 echo /usr/bin/ls -l /opt|grep anquanke
中的 0 可以換成任意名稱 ,比如xx:
sh -c '$@|sh' xx echo /usr/bin/ls -l /opt|grep anquanke
在shell 語法中 $@ 表示所有傳遞過來的位置引數,不包括$0, $0代表指令碼名稱
所以 sh -c $@|sh xx echo /usr/bin/ls -l /opt|grep anquanke
這裡的$@ 就相當於 echo /usr/bin/ls -l /opt|grep anquanke
最終就相當於執行如下命令
sh -c 'echo "/usr/bin/ls -l /opt|grep anquanke"|sh'
5、搞個shell吧,不然這樣收尾稍顯突兀
0x03. 總結
分析完就一個字爽, 學到了不少東東哈哈
0x04. 參考資料
ofollow,noindex" target="_blank">http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/tip/src/solaris/classes/java/lang/UNIXProcess.java.linux
https://www.jianshu.com/p/af4b3264bc5d