Skip to content

Commit

Permalink
SOLR-12450: Don't allow referal to external resources in various conf…
Browse files Browse the repository at this point in the history
…ig files
  • Loading branch information
uschindler authored and Ishan Chattopadhyaya committed Jun 29, 2018
1 parent e27cf65 commit 1880d48
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 69 deletions.
19 changes: 19 additions & 0 deletions solr/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,25 @@ Other Changes
* SOLR-11122: Creating a core should write a core.properties file first and clean up on failure
(Erick Erickson)

================== 6.6.5 ==================

Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.

Versions of Major Components
---------------------
Apache Tika 1.13
Carrot2 3.15.0
Velocity 1.7 and Velocity Tools 2.0
Apache UIMA 2.3.1
Apache ZooKeeper 3.4.10
Jetty 9.3.14.v20161028

Bug Fixes
----------------------

* SOLR-12450: Don't allow referal to external resources in various config files.
(Yuyang Xiao, Uwe Schindler)

================== 6.6.4 ==================

Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,31 @@
*/
package org.apache.solr.handler.extraction;

import javax.xml.parsers.DocumentBuilderFactory;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.util.SafeXMLParsing;
import org.apache.tika.parser.ParseContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ParseContextConfig {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

private final Map<Class<?>, Object> entries = new HashMap<>();

/** Creates an empty Config without any settings (used as placeholder). */
Expand All @@ -54,9 +58,7 @@ public ParseContextConfig(SolrResourceLoader resourceLoader, String parseContext
}

private static Document loadConfigFile(SolrResourceLoader resourceLoader, String parseContextConfigLoc) throws Exception {
try (InputStream in = resourceLoader.openResource(parseContextConfigLoc)) {
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in, parseContextConfigLoc);
}
return SafeXMLParsing.parseConfigXML(log, resourceLoader, parseContextConfigLoc);
}

private void extract(Element element, SolrResourceLoader loader) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,21 @@

package org.apache.solr.schema;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.lucene.analysis.util.ResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.util.SafeXMLParsing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
Expand All @@ -46,6 +45,7 @@
*/
class FileExchangeRateProvider implements ExchangeRateProvider {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

protected static final String PARAM_CURRENCY_CONFIG = "currencyConfig";

// Exchange rate map, maps Currency Code -> Currency Code -> Rate
Expand Down Expand Up @@ -159,71 +159,49 @@ public Set<String> listAvailableCurrencies() {

@Override
public boolean reload() throws SolrException {
InputStream is = null;
Map<String, Map<String, Double>> tmpRates = new HashMap<>();
log.debug("Reloading exchange rates from file {}", currencyConfigFile);

try {
log.debug("Reloading exchange rates from file "+this.currencyConfigFile);

is = loader.openResource(currencyConfigFile);
javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
dbf.setXIncludeAware(true);
dbf.setNamespaceAware(true);
} catch (UnsupportedOperationException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser doesn't support XInclude option", e);
}
Document doc = SafeXMLParsing.parseConfigXML(log, loader, currencyConfigFile);
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();

// Parse exchange rates.
NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);

try {
Document doc = dbf.newDocumentBuilder().parse(is);
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
for (int i = 0; i < nodes.getLength(); i++) {
Node rateNode = nodes.item(i);
NamedNodeMap attributes = rateNode.getAttributes();
Node from = attributes.getNamedItem("from");
Node to = attributes.getNamedItem("to");
Node rate = attributes.getNamedItem("rate");

if (from == null || to == null || rate == null) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Exchange rate missing attributes (required: from, to, rate) " + rateNode);
}

// Parse exchange rates.
NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);
String fromCurrency = from.getNodeValue();
String toCurrency = to.getNodeValue();
Double exchangeRate;

for (int i = 0; i < nodes.getLength(); i++) {
Node rateNode = nodes.item(i);
NamedNodeMap attributes = rateNode.getAttributes();
Node from = attributes.getNamedItem("from");
Node to = attributes.getNamedItem("to");
Node rate = attributes.getNamedItem("rate");

if (from == null || to == null || rate == null) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Exchange rate missing attributes (required: from, to, rate) " + rateNode);
}

String fromCurrency = from.getNodeValue();
String toCurrency = to.getNodeValue();
Double exchangeRate;

if (null == CurrencyFieldType.getCurrency(fromCurrency)) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'from' currency not supported in this JVM: " + fromCurrency);
}
if (null == CurrencyFieldType.getCurrency(toCurrency)) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'to' currency not supported in this JVM: " + toCurrency);
}

try {
exchangeRate = Double.parseDouble(rate.getNodeValue());
} catch (NumberFormatException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not parse exchange rate: " + rateNode, e);
}

addRate(tmpRates, fromCurrency, toCurrency, exchangeRate);
if (null == CurrencyFieldType.getCurrency(fromCurrency)) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'from' currency not supported in this JVM: " + fromCurrency);
}
} catch (SAXException | XPathExpressionException | ParserConfigurationException | IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error parsing currency config.", e);
}
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error while opening Currency configuration file "+currencyConfigFile, e);
} finally {
try {
if (is != null) {
is.close();
if (null == CurrencyFieldType.getCurrency(toCurrency)) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'to' currency not supported in this JVM: " + toCurrency);
}

try {
exchangeRate = Double.parseDouble(rate.getNodeValue());
} catch (NumberFormatException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not parse exchange rate: " + rateNode, e);
}
} catch (IOException e) {
e.printStackTrace();
addRate(tmpRates, fromCurrency, toCurrency, exchangeRate);
}
} catch (SAXException | IOException | XPathExpressionException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error while parsing currency configuration file "+currencyConfigFile, e);
}
// Atomically swap in the new rates map, if it loaded successfully
this.rates = tmpRates;
Expand Down
120 changes: 120 additions & 0 deletions solr/core/src/java/org/apache/solr/util/SafeXMLParsing.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.util;

import java.io.FilterReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.input.CloseShieldInputStream;
import org.apache.lucene.analysis.util.ResourceLoader;
import org.apache.solr.common.EmptyEntityResolver;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.SuppressForbidden;
import org.apache.solr.common.util.XMLErrorLogger;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
* Some utility methods for parsing XML in a safe way. This class can be used to parse XML
* coming from network (completely untrusted) or it can load a config file from a
* {@link ResourceLoader}. In this case it allows external entities and xincludes, but only
* referring to files reachable by the loader.
*/
@SuppressForbidden(reason = "This class uses XML APIs directly that should not be used anywhere else in Solr code")
public final class SafeXMLParsing {

public static final String SYSTEMID_UNTRUSTED = "untrusted://stream";

private SafeXMLParsing() {}

/** Parses a config file from ResourceLoader. Xinclude and external entities are enabled, but cannot escape the resource loader. */
public static Document parseConfigXML(Logger log, ResourceLoader loader, String file) throws SAXException, IOException {
try (InputStream in = loader.openResource(file)) {
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
dbf.setNamespaceAware(true);
trySetDOMFeature(dbf, XMLConstants.FEATURE_SECURE_PROCESSING, true);
try {
dbf.setXIncludeAware(true);
} catch (UnsupportedOperationException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser doesn't support XInclude option", e);
}

final DocumentBuilder db = dbf.newDocumentBuilder();
db.setEntityResolver(new SystemIdResolver(loader));
db.setErrorHandler(new XMLErrorLogger(log));
return db.parse(in, SystemIdResolver.createSystemIdFromResourceName(file));
} catch (ParserConfigurationException pce) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser cannot be configured", pce);
}
}

/** Parses the given InputStream as XML, disabling any external entities with secure processing enabled.
* The given InputStream is not closed. */
public static Document parseUntrustedXML(Logger log, InputStream in) throws SAXException, IOException {
return getUntrustedDocumentBuilder(log).parse(new CloseShieldInputStream(in), SYSTEMID_UNTRUSTED);
}

/** Parses the given InputStream as XML, disabling any external entities with secure processing enabled.
* The given Reader is not closed. */
public static Document parseUntrustedXML(Logger log, Reader reader) throws SAXException, IOException {
final InputSource is = new InputSource(new FilterReader(reader) {
@Override public void close() {}
});
is.setSystemId(SYSTEMID_UNTRUSTED);
return getUntrustedDocumentBuilder(log).parse(is);
}

public static Document parseUntrustedXML(Logger log, String xml) throws SAXException, IOException {
return parseUntrustedXML(log, new StringReader(xml));
}

private static DocumentBuilder getUntrustedDocumentBuilder(Logger log) {
try {
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
dbf.setNamespaceAware(true);
trySetDOMFeature(dbf, XMLConstants.FEATURE_SECURE_PROCESSING, true);

final DocumentBuilder db = dbf.newDocumentBuilder();
db.setEntityResolver(EmptyEntityResolver.SAX_INSTANCE);
db.setErrorHandler(new XMLErrorLogger(log));
return db;
} catch (ParserConfigurationException pce) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser cannot be configured", pce);
}
}

private static void trySetDOMFeature(DocumentBuilderFactory factory, String feature, boolean enabled) {
try {
factory.setFeature(feature, enabled);
} catch (Exception ex) {
// ignore
}
}

}
Loading

0 comments on commit 1880d48

Please sign in to comment.