您好,登錄后才能下訂單哦!
Nexus Repository Manager 2.x命令注入漏洞CVE-2019-5475示例分析,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
2019年9月初我們應急了Nexus Repository Manager 2.x 命令注入漏洞(CVE-2019-5475),其大致的原因和復現步驟在 hackerone 上公布了,在應急完這個漏洞之后,我們分析該漏洞的修復補丁,發現修復不完全,仍然可以繞過,本篇文章記錄該漏洞的兩次繞過。雖然早發布了兩次的修復版本,由于官方第二次更新公告太慢https://support.sonatype.com/hc/en-us/articles/360033490774,所以現在才發。
幾次更新時間線:
CVE-2019-5475(2019-08-09)
第一次繞過,CVE-2019-15588(2019-10-28)
第二次繞過,未分配CVE,更新了公告影響版本(2020-3-25)
注:原始漏洞分析、第一次繞過分析、第二次繞過分析部分主要由Badcode師傅編寫,第二次繞過分析+、最新版本分析主要由Longofo添加。
需管理員權限(默認認證:admin/admin123)
以下分析的代碼基于 2.14.9-01 版本。
漏洞點是出現在 Yum Repository 插件中,當配置 Yum 的createrepo
或者mergerepo
時
代碼層面會跳到YumCapability
的activationCondition
方法中。
在上面Path of "createrepo"
中設置的值會通過getConfig().getCreaterepoPath()
獲取到,獲取到該值之后,調用this.validate()
方法
傳進來的path
是用戶可控的,之后將path
拼接--version
之后傳遞給commandLineExecutor.exec()
方法,看起來像是執行命令的方法,而事實也是如此。跟進CommandLineExecutor
類的exec
方法
在執行命令前先對命令解析,CommandLine.parse()
,會以空格作為分隔,獲取可執行文件及參數。
最終是調用了Runtime.getRuntime().exec()
執行了命令。
例如,用戶傳入的 command 是cmd.exe /c whoami
,最后到getRuntime().exec()
方法就是Runtime.getRuntime().exec({"cmd.exe","/c","whoami"})
。
所以漏洞的原理也很簡單,就是在createrepo
或者mergerepo
路徑設置的時候,該路徑可以由用戶指定,中途拼接了--version
字符串,最終到了getRuntime.exec()
執行了命令。
在Path of "createrepo"
里面傳入 payload。
在Status
欄可以看到執行的結果
官方補丁改了幾個地方,關鍵點在這里
常規做法,在執行命令前對命令進行過濾。新增加了一個getCleanCommand()
方法,對命令進行過濾。
allowedExecutables
是一個 HashSet,里面只有兩個值,createrepo
和mergerepo
。先判斷用戶傳入的command
是否在allowedExecutables
里面,如果在,直接拼接params
即--version
直接返回。接著對用戶傳入的command
進行路徑判斷,如果是以nexus的工作目錄(applicationDirectories.getWorkDirectory().getAbsolutePath()
)開頭的,直接返回 null。繼續判斷,如果文件名不在allowedExecutables
則返回 null,也就是這條命令需要 以/createrepo
或者/mergerepo
結尾。都通過判斷之后,文件的絕對路徑拼接--version
返回。
說實話,看到這個補丁的第一眼,我就覺得大概率可以繞。
傳入的命令滿足兩個條件即可,不以nexus的工作目錄開頭,并且以/createrepo
或者/mergerepo
結尾即可。
看到補丁中的getCleanCommand()
方法,new File(command)
是關鍵,new File()
是通過將給定的路徑名字符串轉換為抽象路徑名來創建新的File實例。 值得注意的是,這里面路徑字符串是可以使用空格的,也就是
String f = "/etc/passwd /shadow"; File file = new File(f);
這種是合法的,并且調用file.getName()
取到的值是shadow
。結合這個特性,就可以繞過補丁里面的判斷。
String cmd = "/bin/bash -c whoami /createrepo"; File file = new File(cmd); System.out.println(file.getName()); System.out.println(file.getAbsolutePath());
運行結果
可以看到,file.getName()
的值正是createrepo
,滿足判斷。
2.14.14-01 版本
Linux
在Path of "createrepo"
里面傳入 payload。
在Status
欄查看執行的結果
可以看到,成功繞過了補丁。
在 Windows 環境下面就麻煩點了,沒有辦法使用cmd.exe /c whoami
這種形式執行命令了,因為cmd.exe /c whoami
經過new File()
之后變成了cmd.exe \c whoami
,后面是執行不了的。可以直接執行exe,注意后面是還會拼接--version
的,所以很多命令是執行不了的,但是還是有辦法利用能執行任意exe這點來做后續的攻擊的。
在我提交上述繞過方式后,官方修復了這種繞過方式,看下官方的補丁
在getCleanCommand()
方法中增加了一個file.exists()
判斷文件是否存在。之前的/bin/bash -c whoami /createrepo
這種形式的肯定就不行了,因為這個文件并不存在。所以現在又多了一個判斷,難度又加大了。難道就沒有辦法繞過了?不是的,還是可以繞過的。
現在傳入的命令要滿足三個條件了
不以nexus的工作目錄開頭
以/createrepo
或者/mergerepo
結尾
并且這createrepo
或者mergerepo
這個文件存在
看到file.exists()
我就想起了 php 中的file_exists()
,以前搞 php 的時候也遇到過這種判斷。有個系統特性,在 Windows 環境下,目錄跳轉是允許跳轉不存在的目錄的,而在Linux下面是不能跳轉不存在目錄的。
測試一下
Linux
可以看到,file.exists()
返回了 false
Windows
file.exists()
返回了 true
上面我們說了new File(pathname)
,pathname 是允許帶空格的。在利用上面WIndows環境下的特性,把cmd設置成C:\\Windows\\System32\\calc.exe \\..\\..\\win.ini
經過parse()
方法,最終到getRuntime.exec({"C:\\Windows\\System32\\calc.exe","\\..\\..\\win.ini"})
,這樣就能執行calc
了。
在上面這個測試win.ini
是確實存在的文件,回到補丁上面,需要判斷createrepo
或者mergerepo
存在。首先從功能上來說,createrepo 命令用于創建 yum 源(軟件倉庫),即為存放于本地特定位置的眾多rpm包建立索引,描述各包所需依賴信息,并形成元數據。也就是這個createrepo
在Windows下不太可能存在。如果這個不存在的話是沒有辦法經過判斷的。既然服務器內不存在createrepo
,那就想辦法創建一個,我首先試的是找個上傳點,嘗試上傳一個createrepo
,但是沒找到上傳之后名字還能保持不變的點。在Artifacts Upload
處上傳之后,都變成Artifact-Version.Packaging
這種形式的名字了,Artifact-Version.Packaging
這個是不滿足第二個判斷的,得以createrepo
結尾。
一開始看到file.exists()
就走進了思維定勢,以為是判斷文件存在的,但是看了官方的文檔,發現是判斷文件或者目錄存在的。。這點也就是這個漏洞形成的第二個關鍵點,我不能創建文件,但是可以創建文件夾啊。在Artifacts Upload
上傳Artifacts 的時候,可以通過GAV Parameters
來定義。
當Group
設置為test123
,Artifact
設置為test123
,Version
設置成1
,當上傳Artifacts
的時候,是會在服務器中創建對應的目錄的。對應的結構如下
如果我們將Group
設置為createrepo
,那么就會創建對應的createrepo
目錄。
結合兩個特性來測試一下
String cmd = "C:\\Windows\\System32\\calc.exe \\..\\..\\..\\nexus\\sonatype-work\\nexus\\storage\\thirdparty\\createrepo"; File file = new File(cmd); System.out.println(file.exists()); System.out.println(file.getName()); System.out.println(file.getAbsolutePath());
可以看到,file.exists()
返回了true,file.getName()
返回了createrepo
,都符合判斷了。
最后到getRuntime()
里面大概就是getRuntime.exec({"C:\Windows\System32\notepad.exe","\..\..\..\nexus\sonatype-work\nexus\storage\thirdparty\createrepo","--version"})
是可以成功執行notepad.exe
的。(calc.exe演示看不到進程哈,所以換成Notepad.exe)
2.14.15-01 版本
Windows
在Path of "createrepo"
里面傳入 payload。
查看進程,notepad.exe
啟動了
可以看到,成功繞過了補丁。
經過Badcode師傅第二次繞過分析,可以看到能成功在Windows系統執行命令了。但是有一個很大的限制:
nexus需要安裝在系統盤
一些帶參數的命令無法使用
在上面說到的Artifacts Upload
上傳處是可以上傳任意文件的,并且上傳后的文件名都是通過自定義的參數拼接得到,所以都能猜到。那么可以上傳自己編寫的任意exe文件了。
2.14.15-01 版本
Windows
導航到Views/Repositories->Repositories->3rd party->Configuration
,我們可以看到默認本地存儲位置
的絕對路徑(之后上傳的內容也在這個目錄下):
導航到Views/Repositories->Repositories->3rd party->Artifact Upload
,我們可以上傳惡意的exe文件:
該exe文件將被重命名為createrepo-1.exe
(自定義的參數拼接的):
同樣在Path of "createrepo"
里面傳入 payload(這時需要注意前面部分這時是以nexus安裝目錄開頭的,這在補丁中會判斷,所以這里可以在最頂層加..\
或者弄個虛假層aaa\..\
等):
可以看到createrepo-1.exe已經執行了:
第二次補丁繞過之后,官方又進行了修復,官方補丁主要如下:
刪除了之前的修復方式,增加了YumCapabilityUpdateValidator
類,在validate
中將獲取的值與properties中設置的值使用equals
進行絕對相等驗證。這個值要修改只能通過sonatype-work/nexus/conf/capabilities.xml
:
前端直接禁止修改了,通過抓包修改測試:
在YumCapabilityUpdateValidator.validate
斷到:
可以看到這種修復方式無法再繞過了,除非有文件覆蓋的地方覆蓋配置文件,例如解壓覆蓋那種方式,不過沒找到。
不過Artifacts Upload
那里可以上傳任意文件的地方依然還在,如果其他地方再出現上面的情況依然可以利用到。
看完上述內容,你們掌握Nexus Repository Manager 2.x命令注入漏洞CVE-2019-5475示例分析的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。