Merge pull request #5957 from haby0/java/BeanShellInjection

Java: BeanShell Injection
This commit is contained in:
Chris Smowton
2021-06-18 12:38:51 +01:00
committed by GitHub
18 changed files with 437 additions and 1 deletions

View File

@@ -0,0 +1,33 @@
import bsh.Interpreter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.scripting.bsh.BshScriptEvaluator;
import org.springframework.scripting.support.StaticScriptSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class BeanShellInjection {
@GetMapping(value = "bad1")
public void bad1(HttpServletRequest request) {
String code = request.getParameter("code");
BshScriptEvaluator evaluator = new BshScriptEvaluator();
evaluator.evaluate(new StaticScriptSource(code)); //bad
}
@GetMapping(value = "bad2")
public void bad2(HttpServletRequest request) throws Exception {
String code = request.getParameter("code");
Interpreter interpreter = new Interpreter();
interpreter.eval(code); //bad
}
@GetMapping(value = "bad3")
public void bad3(HttpServletRequest request) {
String code = request.getParameter("code");
StaticScriptSource staticScriptSource = new StaticScriptSource("test");
staticScriptSource.setScript(code);
BshScriptEvaluator evaluator = new BshScriptEvaluator();
evaluator.evaluate(staticScriptSource); //bad
}
}

View File

@@ -0,0 +1,34 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
BeanShell is a small, free, embeddable Java source interpreter with object scripting language
features, written in Java. BeanShell dynamically executes standard Java syntax and extends it
with common scripting conveniences such as loose types, commands, and method closures like
those in Perl and JavaScript. If a BeanShell expression is built using attacker-controlled data,
and then evaluated, then it may allow the attacker to run arbitrary code.
</p>
</overview>
<recommendation>
<p>
It is generally recommended to avoid using untrusted input in a BeanShell expression.
If it is not possible, BeanShell expressions should be run in a sandbox that allows accessing only
explicitly allowed classes.
</p>
</recommendation>
<example>
<p>
The following example uses untrusted data to build and run a BeanShell expression.
</p>
<sample src="BeanShellInjection.java" />
</example>
<references>
<li>
CVE-2016-2510:<a href="https://nvd.nist.gov/vuln/detail/CVE-2016-2510">BeanShell Injection</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,47 @@
/**
* @name BeanShell injection
* @description Evaluation of a user-controlled BeanShell expression
* may lead to arbitrary code execution.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/beanshell-injection
* @tags security
* external/cwe/cwe-094
*/
import java
import BeanShellInjection
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
class BeanShellInjectionConfig extends TaintTracking::Configuration {
BeanShellInjectionConfig() { this = "BeanShellInjectionConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof BeanShellInjectionSink }
override predicate isAdditionalTaintStep(DataFlow::Node prod, DataFlow::Node succ) {
exists(ClassInstanceExpr cie |
cie.getConstructedType()
.hasQualifiedName("org.springframework.scripting.support", "StaticScriptSource") and
cie.getArgument(0) = prod.asExpr() and
cie = succ.asExpr()
)
or
exists(MethodAccess ma |
ma.getMethod().hasName("setScript") and
ma.getMethod()
.getDeclaringType()
.hasQualifiedName("org.springframework.scripting.support", "StaticScriptSource") and
ma.getArgument(0) = prod.asExpr() and
ma.getQualifier() = succ.asExpr()
)
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, BeanShellInjectionConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "BeanShell injection from $@.", source.getNode(),
"this user input"

View File

@@ -0,0 +1,28 @@
import java
import semmle.code.java.dataflow.FlowSources
/** A call to `Interpreter.eval`. */
class InterpreterEvalCall extends MethodAccess {
InterpreterEvalCall() {
this.getMethod().hasName("eval") and
this.getMethod().getDeclaringType().hasQualifiedName("bsh", "Interpreter")
}
}
/** A call to `BshScriptEvaluator.evaluate`. */
class BshScriptEvaluatorEvaluateCall extends MethodAccess {
BshScriptEvaluatorEvaluateCall() {
this.getMethod().hasName("evaluate") and
this.getMethod()
.getDeclaringType()
.hasQualifiedName("org.springframework.scripting.bsh", "BshScriptEvaluator")
}
}
/** A sink for BeanShell expression injection vulnerabilities. */
class BeanShellInjectionSink extends DataFlow::Node {
BeanShellInjectionSink() {
this.asExpr() = any(InterpreterEvalCall iec).getArgument(0) or
this.asExpr() = any(BshScriptEvaluatorEvaluateCall bseec).getArgument(0)
}
}

View File

@@ -0,0 +1,15 @@
edges
| BeanShellInjection.java:13:17:13:44 | getParameter(...) : String | BeanShellInjection.java:15:22:15:49 | new StaticScriptSource(...) |
| BeanShellInjection.java:20:17:20:44 | getParameter(...) : String | BeanShellInjection.java:22:20:22:23 | code |
| BeanShellInjection.java:27:17:27:44 | getParameter(...) : String | BeanShellInjection.java:31:22:31:39 | staticScriptSource |
nodes
| BeanShellInjection.java:13:17:13:44 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| BeanShellInjection.java:15:22:15:49 | new StaticScriptSource(...) | semmle.label | new StaticScriptSource(...) |
| BeanShellInjection.java:20:17:20:44 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| BeanShellInjection.java:22:20:22:23 | code | semmle.label | code |
| BeanShellInjection.java:27:17:27:44 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| BeanShellInjection.java:31:22:31:39 | staticScriptSource | semmle.label | staticScriptSource |
#select
| BeanShellInjection.java:15:22:15:49 | new StaticScriptSource(...) | BeanShellInjection.java:13:17:13:44 | getParameter(...) : String | BeanShellInjection.java:15:22:15:49 | new StaticScriptSource(...) | BeanShell injection from $@. | BeanShellInjection.java:13:17:13:44 | getParameter(...) | this user input |
| BeanShellInjection.java:22:20:22:23 | code | BeanShellInjection.java:20:17:20:44 | getParameter(...) : String | BeanShellInjection.java:22:20:22:23 | code | BeanShell injection from $@. | BeanShellInjection.java:20:17:20:44 | getParameter(...) | this user input |
| BeanShellInjection.java:31:22:31:39 | staticScriptSource | BeanShellInjection.java:27:17:27:44 | getParameter(...) : String | BeanShellInjection.java:31:22:31:39 | staticScriptSource | BeanShell injection from $@. | BeanShellInjection.java:27:17:27:44 | getParameter(...) | this user input |

View File

@@ -0,0 +1,33 @@
import bsh.Interpreter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.scripting.bsh.BshScriptEvaluator;
import org.springframework.scripting.support.StaticScriptSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class BeanShellInjection {
@GetMapping(value = "bad1")
public void bad1(HttpServletRequest request) {
String code = request.getParameter("code");
BshScriptEvaluator evaluator = new BshScriptEvaluator();
evaluator.evaluate(new StaticScriptSource(code)); //bad
}
@GetMapping(value = "bad2")
public void bad2(HttpServletRequest request) throws Exception {
String code = request.getParameter("code");
Interpreter interpreter = new Interpreter();
interpreter.eval(code); //bad
}
@GetMapping(value = "bad3")
public void bad3(HttpServletRequest request) {
String code = request.getParameter("code");
StaticScriptSource staticScriptSource = new StaticScriptSource("test");
staticScriptSource.setScript(code);
BshScriptEvaluator evaluator = new BshScriptEvaluator();
evaluator.evaluate(staticScriptSource); //bad
}
}

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-094/BeanShellInjection.ql

View File

@@ -1 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/scriptengine:${testdir}/../../../../stubs/java-ee-el:${testdir}/../../../../stubs/juel-2.2:${testdir}/../../../stubs/groovy-all-3.0.7:${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/jython-2.7.2:${testdir}/../../../../experimental/stubs/rhino-1.7.13
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/scriptengine:${testdir}/../../../../stubs/java-ee-el:${testdir}/../../../../stubs/juel-2.2:${testdir}/../../../stubs/groovy-all-3.0.7:${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/jython-2.7.2:${testdir}/../../../../experimental/stubs/rhino-1.7.13:${testdir}/../../../../stubs/bsh-2.0b5

View File

@@ -0,0 +1,18 @@
package bsh;
import java.io.PrintStream;
import java.io.Reader;
public interface ConsoleInterface {
Reader getIn();
PrintStream getOut();
PrintStream getErr();
void println(Object var1);
void print(Object var1);
void error(Object var1);
}

View File

@@ -0,0 +1,5 @@
package bsh;
public class EvalError extends Exception {
}

View File

@@ -0,0 +1,125 @@
package bsh;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Interpreter implements Runnable, ConsoleInterface, Serializable {
public Interpreter(Reader in, PrintStream out, PrintStream err, boolean interactive, NameSpace namespace, Interpreter parent, String sourceFileInfo) { }
public Interpreter(Reader in, PrintStream out, PrintStream err, boolean interactive, NameSpace namespace) { }
public Interpreter(Reader in, PrintStream out, PrintStream err, boolean interactive) { }
public Interpreter(ConsoleInterface console, NameSpace globalNameSpace) { }
public Interpreter(ConsoleInterface console) { }
public Interpreter() { }
public void setConsole(ConsoleInterface console) { }
private void initRootSystemObject() { }
public void setNameSpace(NameSpace globalNameSpace) { }
public NameSpace getNameSpace() {
return null;
}
public static void main(String[] args) { }
public static void invokeMain(Class clas, String[] args) throws Exception { }
public void run() { }
public Object source(String filename, NameSpace nameSpace) throws FileNotFoundException, IOException, EvalError {
return null;
}
public Object source(String filename) throws FileNotFoundException, IOException, EvalError {
return null;
}
public Object eval(Reader in, NameSpace nameSpace, String sourceFileInfo) throws EvalError {
return null;
}
public Object eval(Reader in) throws EvalError {
return null;
}
public Object eval(String statements) throws EvalError {
return null;
}
public Object eval(String statements, NameSpace nameSpace) throws EvalError {
return null;
}
private String showEvalString(String s) {
return null;
}
public final void error(Object o) { }
public Reader getIn() {
return null;
}
public PrintStream getOut() {
return null;
}
public PrintStream getErr() {
return null;
}
public final void println(Object o) { }
public final void print(Object o) { }
public static final void debug(String s) { }
public Object get(String name) throws EvalError {
return null;
}
Object getu(String name) {
return null;
}
public void set(String name, Object value) throws EvalError { }
void setu(String name, Object value) { }
public void set(String name, long value) throws EvalError { }
public void set(String name, int value) throws EvalError { }
public void set(String name, double value) throws EvalError { }
public void set(String name, float value) throws EvalError { }
public void set(String name, boolean value) throws EvalError { }
public void unset(String name) throws EvalError { }
public Object getInterface(Class interf) throws EvalError {
return null;
}
}

View File

@@ -0,0 +1,7 @@
package bsh;
import java.io.Serializable;
public class NameSpace implements Serializable {
}

View File

@@ -0,0 +1,4 @@
package org.springframework.beans.factory;
public interface Aware {
}

View File

@@ -0,0 +1,5 @@
package org.springframework.beans.factory;
public interface BeanClassLoaderAware extends Aware {
void setBeanClassLoader(ClassLoader var1);
}

View File

@@ -0,0 +1,12 @@
package org.springframework.scripting;
import java.util.Map;
import org.springframework.lang.Nullable;
public interface ScriptEvaluator {
@Nullable
Object evaluate(ScriptSource var1) ;
@Nullable
Object evaluate(ScriptSource var1, @Nullable Map<String, Object> var2) ;
}

View File

@@ -0,0 +1,13 @@
package org.springframework.scripting;
import java.io.IOException;
import org.springframework.lang.Nullable;
public interface ScriptSource {
String getScriptAsString() throws IOException;
boolean isModified();
@Nullable
String suggestedClassName();
}

View File

@@ -0,0 +1,26 @@
package org.springframework.scripting.bsh;
import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.lang.Nullable;
import org.springframework.scripting.ScriptEvaluator;
import org.springframework.scripting.ScriptSource;
public class BshScriptEvaluator implements ScriptEvaluator, BeanClassLoaderAware {
public BshScriptEvaluator() { }
public BshScriptEvaluator(ClassLoader classLoader) { }
public void setBeanClassLoader(ClassLoader classLoader) { }
@Nullable
public Object evaluate(ScriptSource script) {
return null;
}
@Nullable
public Object evaluate(ScriptSource script, @Nullable Map<String, Object> arguments) {
return null;
}
}

View File

@@ -0,0 +1,30 @@
package org.springframework.scripting.support;
import org.springframework.lang.Nullable;
import org.springframework.scripting.ScriptSource;
public class StaticScriptSource implements ScriptSource {
public StaticScriptSource(String script) { }
public StaticScriptSource(String script, @Nullable String className) { }
public synchronized void setScript(String script) { }
public synchronized String getScriptAsString() {
return null;
}
public synchronized boolean isModified() {
return true;
}
@Nullable
public String suggestedClassName() {
return null;
}
public String toString() {
return null;
}
}