mirror of
https://github.com/apache/lucene.git
synced 2025-02-13 21:45:39 +00:00
SOLR-12891 MacroExpander will no longer will expand URL parameters by
default inside of the 'expr' parameter, add InjectionDefense class for safer handling of untrusted data in streaming expressions and add -DStreamingExpressionMacros system property to revert to legacy behavior (cherry picked from commit 9edc557f4526ffbbf35daea06972eb2c595e692b)
This commit is contained in:
parent
8d0652451e
commit
470813143d
@ -30,10 +30,15 @@ Jetty 9.4.14.v20181114
|
||||
|
||||
Upgrade Notes
|
||||
----------------------
|
||||
When requesting the status of an async request via REQUESTSTATUS collections API, the response will
|
||||
include the list of internal async requests (if any) in the "success" or "failed" keys (in addition
|
||||
to them being included outside those keys for backwards compatibility). See SOLR-12708 for more
|
||||
details
|
||||
* When requesting the status of an async request via REQUESTSTATUS collections API, the response will
|
||||
include the list of internal async requests (if any) in the "success" or "failed" keys (in addition
|
||||
to them being included outside those keys for backwards compatibility). See SOLR-12708 for more
|
||||
details
|
||||
|
||||
* SOLR-12891: MacroExpander will no longer will expand URL parameters inside of the 'expr' parameter (used by streaming
|
||||
expressions) Additionally, users are advised to use the 'InjectionDefense' class when constructing streaming
|
||||
expressions that include user supplied data to avoid risks similar to SQL injection. The legacy behavior of
|
||||
expanding the 'expr' parameter can be reinstated with -DStreamingExpressionMacros=true passed to the JVM at startup.
|
||||
|
||||
New Features
|
||||
----------------------
|
||||
|
@ -36,7 +36,6 @@ public class MacroExpander {
|
||||
private int level;
|
||||
private final boolean failOnMissingParams;
|
||||
|
||||
|
||||
public MacroExpander(Map<String,String[]> orig) {
|
||||
this(orig, false);
|
||||
}
|
||||
@ -58,8 +57,12 @@ public class MacroExpander {
|
||||
boolean changed = false;
|
||||
for (Map.Entry<String,String[]> entry : orig.entrySet()) {
|
||||
String k = entry.getKey();
|
||||
String newK = expand(k);
|
||||
String[] values = entry.getValue();
|
||||
if (!isExpandingExpr() && "expr".equals(k) ) { // SOLR-12891
|
||||
expanded.put(k,values);
|
||||
continue;
|
||||
}
|
||||
String newK = expand(k);
|
||||
List<String> newValues = null;
|
||||
for (String v : values) {
|
||||
String newV = expand(v);
|
||||
@ -92,6 +95,10 @@ public class MacroExpander {
|
||||
return changed;
|
||||
}
|
||||
|
||||
private Boolean isExpandingExpr() {
|
||||
return Boolean.valueOf(System.getProperty("StreamingExpressionMacros", "false"));
|
||||
}
|
||||
|
||||
public String expand(String val) {
|
||||
level++;
|
||||
try {
|
||||
|
@ -118,6 +118,7 @@ public class TestMacroExpander extends SolrTestCase {
|
||||
public void testMap() { // see SOLR-9740, the second fq param was being dropped.
|
||||
final Map<String,String[]> request = new HashMap<>();
|
||||
request.put("fq", new String[] {"zero", "${one_ref}", "two", "${three_ref}"});
|
||||
request.put("expr", new String[] {"${one_ref}"}); // expr is for streaming expressions, no replacement by default
|
||||
request.put("one_ref",new String[] {"one"});
|
||||
request.put("three_ref",new String[] {"three"});
|
||||
Map expanded = MacroExpander.expand(request);
|
||||
@ -125,6 +126,30 @@ public class TestMacroExpander extends SolrTestCase {
|
||||
assertEquals("one", ((String[])expanded.get("fq"))[1]);
|
||||
assertEquals("two", ((String[]) expanded.get("fq"))[2]);
|
||||
assertEquals("three", ((String[]) expanded.get("fq"))[3]);
|
||||
|
||||
assertEquals("${one_ref}", ((String[])expanded.get("expr"))[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapExprExpandOn() {
|
||||
final Map<String,String[]> request = new HashMap<>();
|
||||
request.put("fq", new String[] {"zero", "${one_ref}", "two", "${three_ref}"});
|
||||
request.put("expr", new String[] {"${one_ref}"}); // expr is for streaming expressions, no replacement by default
|
||||
request.put("one_ref",new String[] {"one"});
|
||||
request.put("three_ref",new String[] {"three"});
|
||||
// I believe that so long as this is sure to be reset before the end of the test we should
|
||||
// be fine with respect to other tests.
|
||||
String oldVal = System.getProperty("StreamingExpressionMacros","false");
|
||||
System.setProperty("StreamingExpressionMacros", "true");
|
||||
try {
|
||||
Map expanded = MacroExpander.expand(request);
|
||||
assertEquals("zero", ((String[])expanded.get("fq"))[0]);
|
||||
assertEquals("one", ((String[])expanded.get("fq"))[1]);
|
||||
assertEquals("two", ((String[]) expanded.get("fq"))[2]);
|
||||
assertEquals("three", ((String[]) expanded.get("fq"))[3]);
|
||||
assertEquals("one", ((String[])expanded.get("expr"))[0]);
|
||||
} finally {
|
||||
System.setProperty("StreamingExpressionMacros", oldVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,11 +99,17 @@ The {solr-javadocs}/solr-solrj/org/apache/solr/client/solrj/io/package-summary.h
|
||||
|
||||
[source,java]
|
||||
----
|
||||
StreamFactory streamFactory = new DefaultStreamFactory().withCollectionZkHost("collection1", zkServer.getZkAddress());
|
||||
|
||||
ParallelStream pstream = (ParallelStream)streamFactory.constructStream("parallel(collection1, group(search(collection1, q=\"*:*\", fl=\"id,a_s,a_i,a_f\", sort=\"a_s asc,a_f asc\", partitionKeys=\"a_s\"), by=\"a_s asc\"), workers=\"2\", zkHost=\""+zkHost+"\", sort=\"a_s asc\")");
|
||||
StreamFactory streamFactory = new DefaultStreamFactory().withCollectionZkHost("collection1", zkServer.getZkAddress());
|
||||
InjectionDefense defense = new InjectionDefense("parallel(collection1, group(search(collection1, q=\"*:*\", fl=\"id,a_s,a_i,a_f\", sort=\"a_s asc,a_f asc\", partitionKeys=\"a_s\"), by=\"a_s asc\"), workers=\"2\", zkHost=\"?$?\", sort=\"a_s asc\")");
|
||||
defense.addParameter(zkhost);
|
||||
ParallelStream pstream = (ParallelStream)streamFactory.constructStream(defense.safeExpressionString());
|
||||
----
|
||||
|
||||
Note that InjectionDefense need only be used if the string being inserted could contain user supplied data. See the
|
||||
javadoc for `InjectionDefense` for usage details and SOLR-12891 for an example of the potential risks.
|
||||
Also note that for security reasons normal parameter substitution no longer applies to the expr parameter
|
||||
unless the jvm has been started with `-DStreamingExpressionMacros=true` (usually via `solr.in.sh`)
|
||||
|
||||
=== Data Requirements
|
||||
|
||||
Because streaming expressions relies on the `/export` handler, many of the field and field type requirements to use `/export` are also requirements for `/stream`, particularly for `sort` and `fl` parameters. Please see the section <<exporting-result-sets.adoc#exporting-result-sets,Exporting Result Sets>> for details.
|
||||
|
@ -94,7 +94,7 @@ public class Lang {
|
||||
.withFunctionName("plist", ParallelListStream.class)
|
||||
.withFunctionName("zplot", ZplotStream.class)
|
||||
.withFunctionName("hashRollup", HashRollupStream.class)
|
||||
|
||||
.withFunctionName("noop", NoOpStream.class)
|
||||
|
||||
// metrics
|
||||
.withFunctionName("min", MinMetric.class)
|
||||
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.client.solrj.io.stream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.solr.client.solrj.io.Tuple;
|
||||
import org.apache.solr.client.solrj.io.comp.StreamComparator;
|
||||
import org.apache.solr.client.solrj.io.stream.expr.Explanation;
|
||||
import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
|
||||
import org.apache.solr.client.solrj.io.stream.expr.Expressible;
|
||||
import org.apache.solr.client.solrj.io.stream.expr.StreamExplanation;
|
||||
import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
|
||||
import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
|
||||
|
||||
/**
|
||||
* A simple no-operation stream. Immediately returns eof. Mostly intended for use as
|
||||
* a place holder in {@link org.apache.solr.client.solrj.io.stream.expr.InjectionDefense}.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public class NoOpStream extends TupleStream implements Expressible {
|
||||
|
||||
private static final long serialVersionUID = 1;
|
||||
private boolean finished;
|
||||
|
||||
|
||||
|
||||
public NoOpStream() throws IOException {
|
||||
}
|
||||
|
||||
public NoOpStream(StreamExpression expression, StreamFactory factory) throws IOException {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public StreamExpression toExpression(StreamFactory factory) throws IOException{
|
||||
return toExpression(factory, true);
|
||||
}
|
||||
|
||||
private StreamExpression toExpression(StreamFactory factory, boolean includeStreams) throws IOException {
|
||||
// function name
|
||||
StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass()));
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation toExplanation(StreamFactory factory) throws IOException {
|
||||
|
||||
return new StreamExplanation(getStreamNodeId().toString())
|
||||
.withFunctionName(factory.getFunctionName(this.getClass()))
|
||||
.withImplementingClass(this.getClass().getName())
|
||||
.withExpressionType(ExpressionType.STREAM_DECORATOR)
|
||||
.withExpression(toExpression(factory, false).toString());
|
||||
}
|
||||
|
||||
public void setStreamContext(StreamContext context) {
|
||||
}
|
||||
|
||||
public List<TupleStream> children() {
|
||||
List<TupleStream> l = new ArrayList<TupleStream>();
|
||||
return l;
|
||||
}
|
||||
|
||||
public void open() throws IOException {
|
||||
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
}
|
||||
|
||||
public Tuple read() throws IOException {
|
||||
HashMap m = new HashMap();
|
||||
m.put("EOF", true);
|
||||
Tuple tuple = new Tuple(m);
|
||||
return tuple;
|
||||
}
|
||||
|
||||
/** Return the stream sort - ie, the order in which records are returned */
|
||||
public StreamComparator getStreamSort(){
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getCost() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.client.solrj.io.stream.expr;
|
||||
|
||||
class InjectedExpressionException extends IllegalStateException {
|
||||
InjectedExpressionException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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.client.solrj.io.stream.expr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A class with which to safely build a streaming expression. Three types of parameters
|
||||
* (String, Numeric, Expression) are accepted and minimally type checked. All parameters
|
||||
* are positional (unnamed) so the order in which parameters are added must correspond to
|
||||
* the order of the parameters in the supplied expression string.<br><br>
|
||||
*
|
||||
* <p>Specifically, this class verifies that the parameter substitutions do not inject
|
||||
* additional expressions, and that the parameters are strings, valid numbers or valid
|
||||
* expressions producing the expected number of sub-expressions. The idea is not to provide
|
||||
* full type safety but rather to heuristically prevent the injection of malicious
|
||||
* expressions. The template expression and the parameters supplied must not contain
|
||||
* comments since injection of comments could be used to hide one or more of the expected
|
||||
* expressions. Use {@link #stripComments(String)} to remove comments.<br><br>
|
||||
*
|
||||
* <p>Valid patterns for parameters are:
|
||||
* <ul>
|
||||
* <li>?$? for strings</li>
|
||||
* <li>?#? for numeric parameters in integer or decimal format (no exponents)</li>
|
||||
* <li>?(n)? for expressions producing n sub-expressions (minimum n=1)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
|
||||
public class InjectionDefense {
|
||||
|
||||
private static final Pattern STRING_PARAM = Pattern.compile("\\?\\$\\?");
|
||||
private static final Pattern NUMBER_PARAM = Pattern.compile("\\?#\\?");
|
||||
private static final Pattern EXPRESSION_PARAM = Pattern.compile("\\?\\(\\d+\\)\\?");
|
||||
private static final Pattern EXPRESSION_COUNT = Pattern.compile("\\d+");
|
||||
private static final Pattern ANY_PARAM = Pattern.compile("\\?(?:[$#]|(?:\\(\\d+\\)))\\?");
|
||||
private static final Pattern INT_OR_FLOAT = Pattern.compile("-?\\d+(?:\\.\\d+)?");
|
||||
|
||||
private String exprString;
|
||||
private int expressionCount;
|
||||
private List<String> params = new ArrayList<>();
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public InjectionDefense(String exprString) {
|
||||
this.exprString = exprString;
|
||||
checkExpression(exprString);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static String stripComments(String exprString) {
|
||||
return StreamExpressionParser.stripComments(exprString);
|
||||
}
|
||||
|
||||
public void addParameter(String param) {
|
||||
params.add(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an expression that is guaranteed to have the expected number of sub-expressions
|
||||
*
|
||||
* @return An expression object that should be safe from injection of additional expressions
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public StreamExpression safeExpression() {
|
||||
String exprStr = buildExpression();
|
||||
System.out.println(exprStr);
|
||||
StreamExpression parsed = StreamExpressionParser.parse(exprStr);
|
||||
int actual = countExpressions(parsed);
|
||||
if (actual != expressionCount) {
|
||||
throw new InjectedExpressionException("Expected Expression count ("+expressionCount+") does not match actual final " +
|
||||
"expression count ("+actual+")! (possible injection attack?)");
|
||||
} else {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a string that is guaranteed to parse to a legal expression and to have the expected
|
||||
* number of sub-expressions.
|
||||
*
|
||||
* @return A string that should be safe from injection of additional expressions.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public String safeExpressionString() {
|
||||
String exprStr = buildExpression();
|
||||
StreamExpression parsed = StreamExpressionParser.parse(exprStr);
|
||||
if (countExpressions(parsed) != expressionCount) {
|
||||
throw new InjectedExpressionException("Expected Expression count does not match Actual final " +
|
||||
"expression count! (possible injection attack?)");
|
||||
} else {
|
||||
return exprStr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
String buildExpression() {
|
||||
Matcher anyParam = ANY_PARAM.matcher(exprString);
|
||||
StringBuffer buff = new StringBuffer();
|
||||
int pIdx = 0;
|
||||
while (anyParam.find()) {
|
||||
String found = anyParam.group();
|
||||
String p = params.get(pIdx++);
|
||||
if (found.contains("#")) {
|
||||
if (!INT_OR_FLOAT.matcher(p).matches()) {
|
||||
throw new NumberFormatException("Argument " + pIdx + " (" + p + ") is not numeric!");
|
||||
}
|
||||
}
|
||||
anyParam.appendReplacement(buff, p);
|
||||
}
|
||||
anyParam.appendTail(buff);
|
||||
|
||||
// strip comments may add '\n' at the end so trim()
|
||||
String result = buff.toString().trim();
|
||||
String noComments = stripComments(result).trim();
|
||||
if (!result.equals(noComments)) {
|
||||
throw new IllegalStateException("Comments are not allowed in prepared expressions for security reasons " +
|
||||
"please pre-process stripComments() first. If there were no comments, then they have been injected by " +
|
||||
"a parameter value.");
|
||||
}
|
||||
return buff.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform some initial checks and establish the expected number of expressions
|
||||
*
|
||||
* @param exprString the expression to check.
|
||||
*/
|
||||
private void checkExpression(String exprString) {
|
||||
exprString = STRING_PARAM.matcher(exprString).replaceAll("foo");
|
||||
exprString = NUMBER_PARAM.matcher(exprString).replaceAll("0");
|
||||
Matcher eMatcher = EXPRESSION_PARAM.matcher(exprString);
|
||||
StringBuffer temp = new StringBuffer();
|
||||
while (eMatcher.find()) {
|
||||
Matcher counter = EXPRESSION_COUNT.matcher(eMatcher.group());
|
||||
eMatcher.appendReplacement(temp, "noop()");
|
||||
if (counter.find()) {
|
||||
Integer subExprCount = Integer.valueOf(counter.group());
|
||||
if (subExprCount < 1) {
|
||||
throw new IllegalStateException("Expression Param must contribute at least 1 expression!" +
|
||||
" ?(1)? is the minimum allowed ");
|
||||
}
|
||||
expressionCount += (subExprCount - 1); // the noop() we insert will get counted later.
|
||||
}
|
||||
}
|
||||
eMatcher.appendTail(temp);
|
||||
exprString = temp.toString();
|
||||
|
||||
StreamExpression parsed = StreamExpressionParser.parse(exprString);
|
||||
if (parsed != null) {
|
||||
expressionCount += countExpressions(parsed);
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid expression (parse returned null):" + exprString);
|
||||
}
|
||||
}
|
||||
|
||||
private int countExpressions(StreamExpression expression) {
|
||||
int result = 0;
|
||||
List<StreamExpressionParameter> exprToCheck = new ArrayList<>();
|
||||
exprToCheck.add(expression);
|
||||
while (exprToCheck.size() > 0) {
|
||||
StreamExpressionParameter remove = exprToCheck.remove(0);
|
||||
if (remove instanceof StreamExpressionNamedParameter) {
|
||||
remove = ((StreamExpressionNamedParameter) remove).getParameter();
|
||||
}
|
||||
if (remove instanceof StreamExpression) {
|
||||
result++;
|
||||
for (StreamExpressionParameter parameter : ((StreamExpression) remove).getParameters()) {
|
||||
if (parameter instanceof StreamExpressionNamedParameter) {
|
||||
parameter = ((StreamExpressionNamedParameter) parameter).getParameter();
|
||||
}
|
||||
if (parameter instanceof StreamExpression) {
|
||||
exprToCheck.add(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -46,26 +46,18 @@ public class StreamExpressionParser {
|
||||
}
|
||||
|
||||
|
||||
private static String stripComments(String clause) throws RuntimeException {
|
||||
static String stripComments(String clause) throws RuntimeException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
BufferedReader reader = null;
|
||||
|
||||
try {
|
||||
reader = new BufferedReader(new StringReader(clause));
|
||||
String line = null;
|
||||
try (BufferedReader reader = new BufferedReader(new StringReader(clause))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if(line.trim().startsWith("#")) {
|
||||
continue;
|
||||
} else {
|
||||
builder.append(line+'\n');
|
||||
if (!line.trim().startsWith("#")) {
|
||||
builder.append(line).append('\n');
|
||||
}
|
||||
}
|
||||
}catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally{
|
||||
try {
|
||||
reader.close();
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
|
@ -75,7 +75,7 @@ public class TestLang extends SolrTestCase {
|
||||
"convexHull", "getVertices", "getBaryCenter", "getArea", "getBoundarySize","oscillate",
|
||||
"getAmplitude", "getPhase", "getAngularFrequency", "enclosingDisk", "getCenter", "getRadius",
|
||||
"getSupportPoints", "pairSort", "log10", "plist", "recip", "pivot", "ltrim", "rtrim", "export",
|
||||
"zplot", "natural", "repeat", "movingMAD", "hashRollup"};
|
||||
"zplot", "natural", "repeat", "movingMAD", "hashRollup", "noop"};
|
||||
|
||||
@Test
|
||||
public void testLang() {
|
||||
|
@ -412,7 +412,9 @@ public class StreamExpressionTest extends SolrCloudTestCase {
|
||||
|
||||
@Test
|
||||
public void testParameterSubstitution() throws Exception {
|
||||
|
||||
String oldVal = System.getProperty("StreamingExpressionMacros", "false");
|
||||
System.setProperty("StreamingExpressionMacros", "true");
|
||||
try {
|
||||
new UpdateRequest()
|
||||
.add(id, "0", "a_s", "hello0", "a_i", "0", "a_f", "0")
|
||||
.add(id, "2", "a_s", "hello2", "a_i", "2", "a_f", "0")
|
||||
@ -439,7 +441,7 @@ public class StreamExpressionTest extends SolrCloudTestCase {
|
||||
tuples = getTuples(stream);
|
||||
|
||||
assertEquals(4, tuples.size());
|
||||
assertOrder(tuples, 0,1,3,4);
|
||||
assertOrder(tuples, 0, 1, 3, 4);
|
||||
|
||||
// Basic test desc
|
||||
sParams.set("mySort", "a_f desc");
|
||||
@ -457,6 +459,9 @@ public class StreamExpressionTest extends SolrCloudTestCase {
|
||||
|
||||
assertEquals(5, tuples.size());
|
||||
assertOrder(tuples, 0, 2, 1, 3, 4);
|
||||
} finally {
|
||||
System.setProperty("StreamingExpressionMacros", oldVal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.client.solrj.io.stream.expr;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class InjectionDefenseTest {
|
||||
|
||||
private static final String EXPLOITABLE = "let(a=search(foo,q=\"time_dt:[?$? TO ?$?]\",fl=\"id,time_dt\",sort=\"time_dt asc\"))";
|
||||
private static final String NUMBER = "let(a=search(foo,q=\"gallons_f:[?#? TO ?#?]\",fl=\"id,gallons_f,time_dt\",sort=\"time_dt asc\"))";
|
||||
private static final String NUMBER_OK = "let(a=search(foo,q=\"gallons_f:[2 TO 3.5]\",fl=\"id,gallons_f,time_dt\",sort=\"time_dt asc\"))";
|
||||
private static final String ALLOWED = "let(a=search(foo,q=\"time_dt:[?$? TO ?$?]\",fl=\"id,time_dt\",sort=\"time_dt asc\"), x=?(2)?)";
|
||||
private static final String INJECTED = "let(a=search(foo,q=\"time_dt:[2000-01-01T00:00:00Z TO 2020-01-01T00:00:00Z]\",fl=\"id,time_dt\",sort=\"time_dt asc\"), x=jdbc( connection=\"jdbc:postgresql://localhost:5432/ouchdb\",sql=\"select * from users\",sort=\"id asc\"),z=jdbc( connection=\"jdbc:postgresql://localhost:5432/ouchdb\",sql=\"select * from race_cars\",sort=\"id asc\"))";
|
||||
|
||||
@Test(expected = InjectedExpressionException.class)
|
||||
public void testSafeExpression() {
|
||||
|
||||
InjectionDefense defender = new InjectionDefense(EXPLOITABLE);
|
||||
|
||||
defender.addParameter("2000-01-01T00:00:00Z");
|
||||
defender.addParameter("2020-01-01T00:00:00Z]\",fl=\"id\",sort=\"id asc\"), b=jdbc( connection=\"jdbc:postgresql://localhost:5432/ouchdb\",sql=\"select * from users\",sort=\"id asc\"),c=search(foo,q=\"time_dt:[* TO 2020-01-01T00:00:00Z");
|
||||
|
||||
defender.safeExpression();
|
||||
}
|
||||
|
||||
@Test(expected = InjectedExpressionException.class)
|
||||
public void testSafeString() {
|
||||
|
||||
InjectionDefense defender = new InjectionDefense(EXPLOITABLE);
|
||||
|
||||
defender.addParameter("2000-01-01T00:00:00Z");
|
||||
defender.addParameter("2020-01-01T00:00:00Z]\",fl=\"id\",sort=\"id asc\"), b=jdbc( connection=\"jdbc:postgresql://localhost:5432/ouchdb\",sql=\"select * from users\",sort=\"id asc\"),c=search(foo,q=\"time_dt:[* TO 2020-01-01T00:00:00Z");
|
||||
|
||||
defender.safeExpressionString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpectedInjectionOfExpressions() {
|
||||
InjectionDefense defender = new InjectionDefense(ALLOWED);
|
||||
|
||||
defender.addParameter("2000-01-01T00:00:00Z");
|
||||
defender.addParameter("2020-01-01T00:00:00Z");
|
||||
defender.addParameter("jdbc( connection=\"jdbc:postgresql://localhost:5432/ouchdb\",sql=\"select * from users\",sort=\"id asc\"),z=jdbc( connection=\"jdbc:postgresql://localhost:5432/ouchdb\",sql=\"select * from race_cars\",sort=\"id asc\")");
|
||||
|
||||
// no exceptions
|
||||
assertNotNull(defender.safeExpression());
|
||||
assertEquals(INJECTED, defender.safeExpressionString());
|
||||
|
||||
}
|
||||
|
||||
@Test(expected = InjectedExpressionException.class)
|
||||
public void testWrongNumberInjected() {
|
||||
InjectionDefense defender = new InjectionDefense(ALLOWED);
|
||||
|
||||
defender.addParameter("2000-01-01T00:00:00Z");
|
||||
defender.addParameter("2020-01-01T00:00:00Z");
|
||||
defender.addParameter("jdbc( connection=\"jdbc:postgresql://localhost:5432/ouchdb\",sql=\"select * from users\",sort=\"id asc\")");
|
||||
|
||||
// no exceptions
|
||||
defender.safeExpression();
|
||||
assertEquals(INJECTED, defender.safeExpressionString());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildExpression() {
|
||||
InjectionDefense defender = new InjectionDefense(ALLOWED);
|
||||
|
||||
defender.addParameter("2000-01-01T00:00:00Z");
|
||||
defender.addParameter("2020-01-01T00:00:00Z");
|
||||
defender.addParameter("jdbc( connection=\"jdbc:postgresql://localhost:5432/ouchdb\",sql=\"select * from users\",sort=\"id asc\"),z=jdbc( connection=\"jdbc:postgresql://localhost:5432/ouchdb\",sql=\"select * from race_cars\",sort=\"id asc\")");
|
||||
|
||||
assertEquals(INJECTED, defender.buildExpression());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInjectNumber() {
|
||||
InjectionDefense defender = new InjectionDefense(NUMBER);
|
||||
|
||||
defender.addParameter("2");
|
||||
defender.addParameter("3.5");
|
||||
|
||||
assertEquals(NUMBER_OK, defender.buildExpression());
|
||||
}
|
||||
|
||||
@Test(expected = NumberFormatException.class)
|
||||
public void testInjectAlphaFail() {
|
||||
InjectionDefense defender = new InjectionDefense(NUMBER);
|
||||
|
||||
defender.addParameter("a");
|
||||
defender.addParameter("3.5");
|
||||
|
||||
defender.buildExpression();
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user