You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ja...@apache.org on 2021/10/11 08:51:50 UTC
[lucene-solr] branch branch_8x updated: SOLR-15678 Allow only known
content types in ShowFileRequestHandler (#2589)
This is an automated email from the ASF dual-hosted git repository.
janhoy pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git
The following commit(s) were added to refs/heads/branch_8x by this push:
new 5bff5d6 SOLR-15678 Allow only known content types in ShowFileRequestHandler (#2589)
5bff5d6 is described below
commit 5bff5d6a8957d609f80651920677aced83f346d6
Author: Jan Høydahl <ja...@users.noreply.github.com>
AuthorDate: Mon Oct 11 10:51:30 2021 +0200
SOLR-15678 Allow only known content types in ShowFileRequestHandler (#2589)
---
solr/CHANGES.txt | 2 +-
.../solr/handler/admin/ShowFileRequestHandler.java | 48 ++++++++++----
.../test-files/solr/collection1/conf/example.html | 6 ++
.../handler/admin/ShowFileRequestHandlerTest.java | 74 ++++++++++++++++++++--
.../src/rule-based-authorization-plugin.adoc | 2 +-
5 files changed, 112 insertions(+), 20 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index dce18a1..52b6ef4 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -32,7 +32,7 @@ Build
Other Changes
---------------------
-(No changes)
+* SOLR-15678: Allow only known content types in ShowFileRequestHandler (janhoy, Gus Heck, Mal Aware)
======================= 8.10.1 =======================
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ShowFileRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ShowFileRequestHandler.java
index a000290..c3f40e8 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ShowFileRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ShowFileRequestHandler.java
@@ -16,6 +16,7 @@
*/
package org.apache.solr.handler.admin;
+import com.google.common.base.Strings;
import org.apache.solr.cloud.ZkSolrResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
@@ -33,7 +34,10 @@ import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.RawResponseWriter;
import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.security.PermissionNameProvider;
import org.apache.zookeeper.KeeperException;
+import org.eclipse.jetty.http.MimeTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,6 +46,7 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.invoke.MethodHandles;
import java.net.URISyntaxException;
+import java.nio.file.Path;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
@@ -87,7 +92,7 @@ import java.util.Set;
*
* @since solr 1.3
*/
-public class ShowFileRequestHandler extends RequestHandlerBase
+public class ShowFileRequestHandler extends RequestHandlerBase implements PermissionNameProvider
{
public static final String HIDDEN = "hidden";
public static final String USE_CONTENT_TYPE = "contentType";
@@ -178,7 +183,7 @@ public class ShowFileRequestHandler extends RequestHandlerBase
params.set(CommonParams.WT, "raw");
req.setParams(params);
ContentStreamBase content = new ContentStreamBase.ByteArrayStream(zkClient.getData(adminFile, null, null, true), adminFile);
- content.setContentType(req.getParams().get(USE_CONTENT_TYPE));
+ content.setContentType(getSafeContentType(req.getParams().get(USE_CONTENT_TYPE)));
rsp.add(RawResponseWriter.CONTENT, content);
}
@@ -243,13 +248,33 @@ public class ShowFileRequestHandler extends RequestHandlerBase
req.setParams(params);
ContentStreamBase content = new ContentStreamBase.FileStream( adminFile );
- content.setContentType(req.getParams().get(USE_CONTENT_TYPE));
+ content.setContentType(getSafeContentType(req.getParams().get(USE_CONTENT_TYPE)));
rsp.add(RawResponseWriter.CONTENT, content);
}
rsp.setHttpCaching(false);
}
+ /**
+ * Checks content type string and returns it if it is one of allowed types.
+ * The allowed types are all standard mime types.
+ * If an HTML type is requested, it is instead returned as text/plain
+ */
+ public static String getSafeContentType(String contentType) {
+ if (Strings.isNullOrEmpty(contentType)) {
+ log.debug("No contentType specified");
+ return null;
+ }
+ if (!MimeTypes.getKnownMimeTypes().contains(contentType)) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Requested content type '" + contentType + "' is not supported.");
+ }
+ if (contentType.toLowerCase(Locale.ROOT).contains("html")) {
+ log.info("Using text/plain instead of {}", contentType);
+ return "text/plain";
+ }
+ return contentType;
+ }
+
//////////////////////// Static methods //////////////////////////////
public static boolean isHiddenFile(SolrQueryRequest req, SolrQueryResponse rsp, String fnameIn, boolean reportError,
@@ -336,20 +361,16 @@ public class ShowFileRequestHandler extends RequestHandlerBase
String fname = req.getParams().get("file", null);
if( fname == null ) {
adminFile = configdir;
- }
- else {
+ } else {
fname = fname.replace( '\\', '/' ); // normalize slashes
if( hiddenFiles.contains( fname.toUpperCase(Locale.ROOT) ) ) {
log.error("Can not access: {}", fname);
rsp.setException(new SolrException( SolrException.ErrorCode.FORBIDDEN, "Can not access: "+fname ));
return null;
}
- if( fname.indexOf( ".." ) >= 0 ) {
- log.error("Invalid path: {}", fname);
- rsp.setException(new SolrException( SolrException.ErrorCode.FORBIDDEN, "Invalid path: "+fname ));
- return null;
- }
- adminFile = new File( configdir, fname );
+ Path filePath = configdir.toPath().resolve(fname);
+ req.getCore().getCoreContainer().assertPathAllowed(filePath);
+ adminFile = filePath.toFile();
}
return adminFile;
}
@@ -368,4 +389,9 @@ public class ShowFileRequestHandler extends RequestHandlerBase
public Category getCategory() {
return Category.ADMIN;
}
+
+ @Override
+ public Name getPermissionName(AuthorizationContext request) {
+ return Name.CONFIG_READ_PERM;
+ }
}
diff --git a/solr/core/src/test-files/solr/collection1/conf/example.html b/solr/core/src/test-files/solr/collection1/conf/example.html
new file mode 100644
index 0000000..bb8f277
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/example.html
@@ -0,0 +1,6 @@
+<html>
+<head>
+ <title>Welcome to Solr</title>
+</head>
+<body>Hello</body>
+</html>
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/ShowFileRequestHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/ShowFileRequestHandlerTest.java
index ee8695b..0e78329 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/ShowFileRequestHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/ShowFileRequestHandlerTest.java
@@ -16,23 +16,28 @@
*/
package org.apache.solr.handler.admin;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.util.concurrent.atomic.AtomicBoolean;
-
import org.apache.solr.SolrJettyTestBase;
import org.apache.solr.client.solrj.ResponseParser;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.NoOpResponseParser;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrCore;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.BeforeClass;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.concurrent.atomic.AtomicBoolean;
+
/**
* Extend SolrJettyTestBase because the SOLR-2535 bug only manifested itself when
* the {@link org.apache.solr.servlet.SolrDispatchFilter} is used, which isn't for embedded Solr use.
@@ -41,6 +46,7 @@ public class ShowFileRequestHandlerTest extends SolrJettyTestBase {
@BeforeClass
public static void beforeTest() throws Exception {
+ initCore("solrconfig.xml", "schema.xml");
createAndStartJetty(legacyExampleCollection1SolrHome());
}
@@ -54,10 +60,8 @@ public class ShowFileRequestHandlerTest extends SolrJettyTestBase {
}
public void test404Locally() throws Exception {
-
// we need to test that executing the handler directly does not
// throw an exception, just sets the exception on the response.
- initCore("solrconfig.xml", "schema.xml");
// bypass TestHarness since it will throw any exception found in the
// response.
@@ -114,4 +118,60 @@ public class ShowFileRequestHandlerTest extends SolrJettyTestBase {
//request.process(client); but we don't have a NamedList response
assertTrue(readFile.get());
}
+
+ public void testContentTypeHtmlBecomesTextPlain() throws Exception {
+ SolrRequestHandler handler = h.getCore().getRequestHandler("/admin/file");
+ SolrQueryRequest req = new LocalSolrQueryRequest(h.getCore(), params("file", "schema.xml", "contentType", "text/html"));
+ SolrQueryResponse rsp = new SolrQueryResponse();
+ handler.handleRequest(req, rsp);
+ ContentStreamBase.FileStream content = (ContentStreamBase.FileStream) rsp.getValues().get("content");
+ assertEquals("text/plain", content.getContentType());
+ }
+
+ public void testContentTypeHtmlDefault() throws Exception {
+ SolrRequestHandler handler = h.getCore().getRequestHandler("/admin/file");
+ SolrQueryRequest req = new LocalSolrQueryRequest(h.getCore(), params("file", "example.html"));
+ SolrQueryResponse rsp = new SolrQueryResponse();
+ handler.handleRequest(req, rsp);
+ ContentStreamBase.FileStream content = (ContentStreamBase.FileStream) rsp.getValues().get("content");
+ // System attempts to guess content type, but will only return XML, JSON, CSV, never HTML
+ assertEquals("application/xml", content.getContentType());
+ }
+
+ public void testIllegalContentType() {
+ SolrClient client = getSolrClient();
+ QueryRequest request = new QueryRequest(params("file", "managed-schema", "contentType", "not/known"));
+ request.setPath("/admin/file");
+ request.setResponseParser(new NoOpResponseParser());
+ expectThrows(SolrException.class, () -> client.request(request));
+ }
+
+ public void testAbsoluteFilename() {
+ SolrClient client = getSolrClient();
+ final QueryRequest request = new QueryRequest(params("file", "/etc/passwd"));
+ request.setPath("/admin/file"); // absolute path not allowed
+ request.setResponseParser(new NoOpResponseParser());
+ expectThrows(SolrException.class, () -> client.request(request));
+ }
+
+ public void testPathTraversalFilename() {
+ SolrClient client = getSolrClient();
+ final QueryRequest request = new QueryRequest(params("file", "../../../../../../etc/passwd"));
+ request.setPath("/admin/file");
+ request.setResponseParser(new NoOpResponseParser());
+ expectThrows(SolrException.class, () -> client.request(request));
+ }
+
+ public void testGetSafeContentType() {
+ // Valid content types are returned as is
+ assertEquals("application/json", ShowFileRequestHandler.getSafeContentType("application/json"));
+ assertEquals("text/csv", ShowFileRequestHandler.getSafeContentType("text/csv"));
+
+ // HTML gets rewritten as "safe" plaintext
+ assertEquals("text/plain", ShowFileRequestHandler.getSafeContentType("text/html"));
+ assertEquals("text/plain", ShowFileRequestHandler.getSafeContentType("application/xhtml+xml"));
+
+ // Non-known content types are rejected with 400 error
+ expectThrows(SolrException.class, () -> ShowFileRequestHandler.getSafeContentType("foo/bar"));
+ }
}
diff --git a/solr/solr-ref-guide/src/rule-based-authorization-plugin.adoc b/solr/solr-ref-guide/src/rule-based-authorization-plugin.adoc
index 4dc9401..fcb108d 100644
--- a/solr/solr-ref-guide/src/rule-based-authorization-plugin.adoc
+++ b/solr/solr-ref-guide/src/rule-based-authorization-plugin.adoc
@@ -207,7 +207,7 @@ The predefined permission names (and their effects) are:
* *schema-edit*: this permission is allowed to edit a collection's schema using the <<schema-api.adoc#,Schema API>>. Note that this allows schema edit permissions for _all_ collections. If edit permissions should only be applied to specific collections, a custom permission would need to be created.
* *schema-read*: this permission is allowed to read a collection's schema using the <<schema-api.adoc#,Schema API>>. Note that this allows schema read permissions for _all_ collections. If read permissions should only be applied to specific collections, a custom permission would need to be created.
* *config-edit*: this permission is allowed to edit a collection's configuration using the <<config-api.adoc#,Config API>>, the <<request-parameters-api.adoc#,Request Parameters API>>, and other APIs which modify `configoverlay.json`. Note that this allows configuration edit permissions for _all_ collections. If edit permissions should only be applied to specific collections, a custom permission would need to be created.
-* *config-read*: this permission is allowed to read a collection's configuration using the <<config-api.adoc#,Config API>>, the <<request-parameters-api.adoc#,Request Parameters API>>, and other APIs which modify `configoverlay.json`. Note that this allows configuration read permissions for _all_ collections. If read permissions should only be applied to specific collections, a custom permission would need to be created.
+* *config-read*: this permission is allowed to read a collection's configuration using the <<config-api.adoc#,Config API>>, the <<request-parameters-api.adoc#,Request Parameters API>>, <<configsets-api.adoc#configsets-list,Configsets API>>, the Admin UI's <<files-screen.adoc#,Files Screen>>, and other APIs accessing configuration. Note that this allows configuration read permissions for _all_ collections. If read permissions should only be applied to specific collections, a custom permis [...]
* *metrics-read*: this permission allows access to Solr's <<metrics-reporting.adoc#metrics-api,Metrics API>>
* *metrics-history-read*: this permission allows access to Solr's <<metrics-history.adoc#metrics-history-api,Metrics History API>>, which provides long-term history for a select set of key Solr metrics.
* *autoscaling-read*: this permission allows users to read Solr's <<solrcloud-autoscaling-api.adoc#read-api,autoscaling>> configuration. This covers all read-only autoscaling APIs, including: