Java程式碼審計篇 | ofcms系統審計思路講解 - 篇3 | 檔案上傳漏洞審計
1 檔案上傳程式碼審計【有1處】
檔案上傳漏洞我們需要著重關注的是檔案在被java程式碼解析到儲存下來之間有無驗證過濾,因此什麼樣的上傳方式,什麼樣的儲存方式都不重要,大家著重關注程式碼對檔案的驗證過濾手段即可。
檔案上傳程式碼審計常搜尋的關鍵字如下:
file
upload
write
fileName
filePath
getPart
FileOutputStream
transferTo
getRealPath
FileItem
ServletFileUpload
DiskFileItemFactory
....
1.1 可疑點1【無漏洞】
1.1.1 直接搜尋upload關鍵字
1.1.2 選擇第一個,點進去分析一下
類名為UeditorAction
可以在當前頁面搜尋upload關鍵字,也可以同時參考該類的所有方法名稱。
大概看一下,四個方法的寫法是差不多的:
- getFile()方法的第2個引數不太相同,透過引數名稱uploadPath可以大概判斷出來,這個參數列示的是檔案上傳路徑,也就是說呼叫不同的方法會儲存到不同的目錄。
透過下方的msg.put("url", "/upload/image/" + file.getFileName())
也可以大體確定這一點;
當然msg.put不是用來儲存檔案用的,只是返回的資訊而已。
以uploadImage()
方法為例進行分析,可以看到,這個方法內部與儲存檔案相關的程式碼就前面兩行(後面的都是關於返回的訊息相關了),即
UploadFile file = this.getFile("file", "image");
file.getFile().createNewFile();
我們先分析下this.getFile()
方法
1.1.3 分析this.getFile()方法
點選進入看一下:
getFile()
方法,首先呼叫了getFiles(uploadPath)
,跟進之下:就在上面
可以看到這裡先做了個判斷,判斷request
物件是否是MultipartRequest
型別的物件,如果不是則建立一個新的MultipartRequest
型別的物件,總之就是保證request
物件是MultipartRequest
型別的物件。
然後呼叫.getFiles()
方法。
額外的:其中request物件就是HttpServletRequest,相關程式碼如下:
這裡目前來看,需要有兩個點需要分析:
- 1)
new MultipartRequest(request, uploadPath)
- 2)
request.getFiles()
接下來先分析new MultipartRequest(request, uploadPath)
1.1.4 分析new MultipartRequest(request, uploadPath)
點選進入:
一個構造方法,先呼叫了父類,然後呼叫了wrapMultipartRequest()
- 父類可以點選去看看,其實沒啥
- 著重看下
wrapMultipartRequest()
點選進入wrapMultipartRequest()
,程式碼比較長,大概意思已標註:
其中我們需要關注的是檔案的過濾程式碼,也就是圖中紅框位置:
if (isSafeFile(uploadFile)) {
uploadFiles.add(uploadFile);
}
這裡面呼叫了一個isSafeFile()
方法,進去看一看:
1.1.5 分析isSafeFile()方法
發現這裡有了驗證過濾:
- 首先是對檔名去空白字元,然後轉成小寫
- 其次是檔名如果是
.jsp
或者.jspx
結尾則刪除,返回false
師傅們可以想一下有無繞過手段
如果沒有問題,則將檔案add進入uploadFiles
列表中。
1.1.6 分析request.getFiles()方法
然後接下來點進2.1.3步驟中的this.getFile()
方法中的getFiles()
方法進行分析
進來之後,發現方法中沒有太多語句,只是直接返回了uploadFiles
,也就是上一步所說的uploadFiles
列表,這裡面存放的是經過過濾的檔案。
到此為止,其實已經差不多了,已經找到了檔案的過濾方式:
- 首先是對檔名去空白字元,然後轉成小寫
- 其次是檔名如果是
.jsp
或者.jspx
結尾則刪除,返回false
剛開始所說的
uploadImage()
方法中的第2條語句 file.getFile().createNewFile();這裡沒什麼,就是透過返回的經過過濾的file物件來建立一個新的檔案。
那麼接下來可以驗證一下:
1.1.7 驗證檔案的過濾方式,並嘗試找到繞過之法(畢竟是黑名單[\偷笑])
根據前面所說的路由機制,可以確定此uploadImage()
方法的呼叫需要訪問admin/ueditor/uploadImage.json
前端找一下唄。怎麼找?搜唄...哎真麻煩,說實話好難找啊
根據路徑大致判斷範圍:ueditor,貌似在哪見過這個詞?
原來是個富文字編輯器,還好我“見多識廣”哈哈
那就在前端找一下哪裡有富文字,同時在控制檯搜尋著ueditor:
經過了九九八十一天,終於找到了。
那admin/ueditor/uploadImage.json
應該就是在富文字上傳圖片時觸發的吧:嘗試一下
uploadImage()方法打上斷點,yakit也開啟抓包攔截
先來個普通圖片試一試
大事不妙!之前分析的路由機制不太對,這裡怎麼是/admin/ueditor/handler.html?action=uploadImage
- 這裡我懶得分析了,懂的師傅可以留個言~麼麼噠
不過沒關係,放包!uploadImage()
方法觸發了。也就是功能和方法對上了!
在下面也可以看到上傳路徑在:D:\Program Files\tomcat\apache-tomcat-8.0.17\webapps\ofcms_admin_war\upload\image
當然在返回包中,也可以看到上傳路徑
訪問一下,可以訪問到。
接下來嘗試繞過一下,已知檔案過濾方式
- 首先是對檔名去空白字元,然後轉成小寫
- 其次是檔名如果是
.jsp
或者.jspx
結尾則刪除,返回false
絞盡腦汁~貌似只有一個方式:檔名後面加.繞過,但是這種好像只適用於windows。
來都來了,試一下吧,誰讓我現在用的電腦是windows。
這裡我直接修改了資料包,將檔名改成了.jsp.
,檔案內容用的是冰蠍生成的jsp馬子。
這裡idea就先不debug了,直接放包,上傳成功。
可以到目錄中看一下,有沒有上傳成功:
嘿嘿,成功,同時字尾是.jsp。
接下來就是看看能不能連線了...
測試了下,不太行,雖然能上傳,但是不能解析。
這裡為了避免中文問題,我改成了webshell.jsp
怎麼辦?目錄穿越試一試,哈哈哈
經過三天三夜的除錯,找到了檔名處理的原始碼(這裡過程就不粘出來了,自己可以調一下,如果想看過程,評論區留個言,給兄弟們安排上[\狗頭])
- 先計算
/
的位置 - 然後進行原始檔名的擷取
即檔名為../../../webshell.jsp.
,最終檔名也將變為webshell.jsp.
1.1.8 這裡,這個點就到此結束,總結一下:
- 該位置的檔案上傳可以任意上傳除
.jsp
和.jspx
的檔案,但是沒什麼luan用。 - 當然如果是windows是可以透過加
.
繞過的,不過解析不了。 - 嘗試目錄穿越,有程式碼會將路徑接取掉。
成果:0day無,分析經驗+1
1.2 可疑點2【無漏洞】
分析第二個ComnController
類中的:
進來之後發現,寫法和之前是差不多的,同樣使用了getFile()
方法,怎麼辦?放棄!
後面使用了
getFile()
方法的,直接放棄就好了。
換目標!
這裡多說一下,其實我們搜的這些,都是jfinal元件中的upload,漏洞基本沒有,如果有,那就是元件0Day了。不過分析分析原始碼也是好的,贊同請點贊。
1.3 可疑點3【跑偏的漏洞-路徑遍歷讀取.xml檔案】
透過upload搜尋的,翻了一圈,不是UeditorAction
類,就是ComnController
類,沒有別的可疑的了。換關鍵字搜尋。
1.3.1 搜尋file關鍵字
找到一個TemplateController
類中匯入了File。進入看看
檔案中搜尋下file,85個,真不少
進來之後,大概瀏覽一遍,該類中其中兩個方法getTemplates()
和save()
方法中存在檔案相關的功能程式碼,所以這裡分開來分析下。先分析getTemplates()
【其實這裡不需要分析的,因為它是獲取檔案用的,沒有寫入檔案儲存檔案的功能,和檔案上傳無關】。
既然來都來了,看一下吧~
1.3.2 分析getTemplates()方法
方法程式碼如下:
首先看前面三行,getPara()
方法獲取引數值,獲取不到,會有預設值:
點進getPara()
方法,進來一看:欣喜若狂啊
直接透過request.getParameter(name)
獲取引數值,也就是說,目錄可控啊~
繼續往下分析
這一塊就沒啥了,就是將String型別的路徑,封裝一下,變成物件。順便還做了一下驗證處理。
- 其中畫紅框位置表示:pathFile.listFiles表示目錄下的檔案或目錄,然後透過FileFilter做了一下驗證,判斷是否是目錄,是目錄的放入dirs列表中。
- 其中的
setAttr()
方法,是將目錄物件儲存到request物件中。
繼續向下分析:這裡和之前的差不多,只不過是透過FileFilter限制了白名單檔案,即將.html
、.xml
、.css
、.js
的檔案放入到了files列表中。
繼續向下分析:這段程式碼作用就是頁面上預設顯示的模版檔案,傳入的檔名如果沒有則顯示files列表中的第一個(所以沒有任意檔案讀取~哈哈哈)
最後返回resource.html或者是index.html
沒有檔案上漏洞!
1.3.3 柳暗花明又一村
但是別急,回想下,最開始說該方法獲取的前端引數可控,即目錄可控,那我們能不能不去獲取預設路徑下的.html
、.xml
、.css
、.js
檔案。
直接驗證了,前端找到對應功能點,還是那個熟悉的模版~
隨便點個模版,yakit攔截下~
預設引數是這樣的:
- file_name=contact.html
- dir=/
- dir_name=/
對照下原始碼,dir是當前目錄,file_name是檢視的檔案
那麼修改一下,讀取網站下的web.xml試一試
而預設讀取的根目錄是webapps\ofcms_admin_war\WEB-INF\page\default
所以我們構造dir=../../../../ROOT/WEB-INF
和file_name=web.xml
成功讀取!經驗+1
1.3.4 多點腦洞
既然這裡可以讀取web.xml,並且下方可以儲存,那我們是不是就可以修改web.xml檔案了呢?
嘗試修改儲存一下,出現請求異常~
直接除錯下,在save()方法處打個斷點,發現是因為dirs引數值即dirName中存在../
因此被拒絕了。
這裡其實很好繞過,聰明的你一點發現了,前面還有個引數:res_path
我們可以修改res_path的值和dirs引數的值,來達到修改web.xml的目的。
這裡師傅們可以自己嘗試下,因為這裡和下面的save()
方法的分析是一樣的,就放一起說了。
1.4 可疑點4【有漏洞】
從上一個分析,發現save()
方法其實是有問題的,接下來著重分析一下。
1.4.1 分析save()方法
程式碼就這麼長:
首先看:
這裡從前端獲取“res_path”的引數值,前面也說過,getPara()
獲取的值是可控的。
然後透過res_path
的值決定pathFile
是什麼內容,這裡其實不用管,因為不管res_path
是什麼,pathFile
都是固定的,不是SystemUtile.getSiteTemplateResourcePath()
就是SystemUtile.getSiteTemplatePath()
,沒有可變的地方。
接下來是這段,也是核心:
首先從前端獲取“dirs”引數的值,不過這個值中不能存在../
,也就是說這個引數不能是利用點了。
然後從前端獲取“file_name”引數的值,沒有任何限制。
最後從前端獲取“file_content”引數的值,僅僅對<
和>
做了轉義。
然後就是新建檔案,呼叫FileUtils.writeString(file, fileContent)
寫入內容。
writeString
方法如下:直接呼叫FileOutputStream.write()
寫的,沒有別的過濾。
分析下來之後,可以利用的點為:file_name和file_content。
檔名和檔案內容都可控,這不就是妥妥的任意檔案寫入(可以理解為任意檔案上傳)。
1.4.2 利用漏洞寫入jsp馬
那直接寫個jsp馬進去!當然,可能解析不了。
file_name和file_content都替換掉,最後別忘了file_content的值使用URL編碼一下
- 這裡我用的是冰蠍jsp馬
成功寫入:
訪問連線:但是會發現連線失敗,看來當前目錄下不能直接訪問,或者是jsp不解析
繼續嘗試別的目錄:file_name=../../../webshell.jsp
也是失敗。
這裡嘗試在static目錄,是可以的。file_name=../../../static/webshell.jsp
小tips:static目錄是靜態目錄,一般檔案是可以直接訪問。
命令成功執行!撒花~
1.5 最後結尾
最後搜尋了其他的類和關鍵字,沒有可以分析利用的點了。師傅們看看就好。
1.5.1 例1:SystemUtile類下
1.5.2 例2:GenUtils類下,都是建立固定名稱的檔案,並非檔案上傳
1.5.3 例3:換關鍵字write搜尋,基本都是分析過的,要不就是無用的
檔案上傳漏洞程式碼審計到此為止,希望師傅們有所收穫,如有問題,評論區留言~
也可掃碼加好友,一起進步~