mirror of
https://github.com/github/codeql.git
synced 2026-02-11 20:51:06 +01:00
Merge remote-tracking branch 'upstream/main' into JsonHijacking
This commit is contained in:
@@ -23,7 +23,7 @@ is called on the URL, potentially leading to a local file access.</p>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Java Platform, Standard Edition 11, API Specification:
|
||||
<li>Java API Specification:
|
||||
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URL.html">
|
||||
Class URL</a>.
|
||||
</li>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class URLConstructor extends ClassInstanceExpr {
|
||||
@@ -37,6 +38,8 @@ class RemoteURLToOpenStreamFlowConfig extends TaintTracking::Configuration {
|
||||
exists(MethodAccess m |
|
||||
sink.asExpr() = m.getQualifier() and m.getMethod() instanceof URLOpenStreamMethod
|
||||
)
|
||||
or
|
||||
sinkNode(sink, "url-open-stream")
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
|
||||
@@ -26,7 +26,7 @@ stylesheets must be processed, enable the secure processing mode.</p>
|
||||
|
||||
<references>
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/XSLT">XSLT</a>.</li>
|
||||
<li>Java Tutorial: <a href="https://docs.oracle.com/javase/tutorial/jaxp/xslt/transformingXML.html">Transforming XML Data with XSLT</a>.</li>
|
||||
<li>The Java Tutorials: <a href="https://docs.oracle.com/javase/tutorial/jaxp/xslt/transformingXML.html">Transforming XML Data with XSLT</a>.</li>
|
||||
<li><a href="https://blog.hunniccyber.com/ektron-cms-remote-code-execution-xslt-transform-injection-java/">XSLT Injection Basics</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Java EXpression Language (JEXL) is a simple expression language
|
||||
provided by the Apache Commons JEXL library.
|
||||
The syntax is close to a mix of ECMAScript and shell-script.
|
||||
The language allows invocation of methods available in the JVM.
|
||||
If a JEXL 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 JEXL expression.
|
||||
If it is not possible, JEXL 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 JEXL expression.
|
||||
</p>
|
||||
<sample src="UnsafeJexlExpressionEvaluation.java" />
|
||||
|
||||
<p>
|
||||
The next example shows how an untrusted JEXL expression can be run
|
||||
in a sandbox that allows accessing only methods in the <code>java.lang.Math</code> class.
|
||||
The sandbox is implemented using <code>JexlSandbox</code> class that is provided by
|
||||
Apache Commons JEXL 3.
|
||||
</p>
|
||||
<sample src="SaferJexlExpressionEvaluationWithSandbox.java" />
|
||||
|
||||
<p>
|
||||
The next example shows another way how a sandbox can be implemented.
|
||||
It uses a custom implementation of <code>JexlUberspect</code>
|
||||
that checks if callees are instances of allowed classes.
|
||||
</p>
|
||||
<sample src="SaferJexlExpressionEvaluationWithUberspectSandbox.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Apache Commons JEXL:
|
||||
<a href="https://commons.apache.org/proper/commons-jexl/">Project page</a>.
|
||||
</li>
|
||||
<li>
|
||||
Apache Commons JEXL documentation:
|
||||
<a href="https://commons.apache.org/proper/commons-jexl/javadocs/apidocs-2.1.1/">JEXL 2.1.1 API</a>.
|
||||
</li>
|
||||
<li>
|
||||
Apache Commons JEXL documentation:
|
||||
<a href="https://commons.apache.org/proper/commons-jexl/apidocs/index.html">JEXL 3.1 API</a>.
|
||||
</li>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection">Expression Language Injection</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @name Expression language injection (JEXL)
|
||||
* @description Evaluation of a user-controlled JEXL expression
|
||||
* may lead to arbitrary code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/jexl-expression-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import JexlInjectionLib
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, JexlInjectionConfig conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "JEXL injection from $@.", source.getNode(), "this user input"
|
||||
@@ -0,0 +1,288 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for unsafe user input
|
||||
* that is used to construct and evaluate a JEXL expression.
|
||||
* It supports both JEXL 2 and 3.
|
||||
*/
|
||||
class JexlInjectionConfig extends TaintTracking::Configuration {
|
||||
JexlInjectionConfig() { this = "JexlInjectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof JexlEvaluationSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
any(TaintPropagatingJexlMethodCall c).taintFlow(fromNode, toNode) or
|
||||
returnsDataFromBean(fromNode, toNode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for Expresssion Language injection vulnerabilities via Jexl,
|
||||
* i.e. method calls that run evaluation of a JEXL expression.
|
||||
*
|
||||
* Creating a `Callable` from a tainted JEXL expression or script is considered as a sink
|
||||
* although the tainted expression is not executed at this point.
|
||||
* Here we assume that it will get executed at some point,
|
||||
* maybe stored in an object field and then reached by a different flow.
|
||||
*/
|
||||
private class JexlEvaluationSink extends DataFlow::ExprNode {
|
||||
JexlEvaluationSink() {
|
||||
exists(MethodAccess ma, Method m, Expr taintFrom |
|
||||
ma.getMethod() = m and taintFrom = this.asExpr()
|
||||
|
|
||||
m instanceof DirectJexlEvaluationMethod and ma.getQualifier() = taintFrom
|
||||
or
|
||||
m instanceof CreateJexlCallableMethod and ma.getQualifier() = taintFrom
|
||||
or
|
||||
m instanceof JexlEngineGetSetPropertyMethod and
|
||||
taintFrom.getType() instanceof TypeString and
|
||||
ma.getAnArgument() = taintFrom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines method calls that propagate tainted data via one of the methods
|
||||
* from JEXL library.
|
||||
*/
|
||||
private class TaintPropagatingJexlMethodCall extends MethodAccess {
|
||||
Expr taintFromExpr;
|
||||
|
||||
TaintPropagatingJexlMethodCall() {
|
||||
exists(Method m, RefType taintType |
|
||||
this.getMethod() = m and
|
||||
taintType = taintFromExpr.getType()
|
||||
|
|
||||
isUnsafeEngine(this.getQualifier()) and
|
||||
(
|
||||
m instanceof CreateJexlScriptMethod and
|
||||
taintFromExpr = this.getArgument(0) and
|
||||
taintType instanceof TypeString
|
||||
or
|
||||
m instanceof CreateJexlExpressionMethod and
|
||||
taintFromExpr = this.getAnArgument() and
|
||||
taintType instanceof TypeString
|
||||
or
|
||||
m instanceof CreateJexlTemplateMethod and
|
||||
(taintType instanceof TypeString or taintType instanceof Reader) and
|
||||
taintFromExpr = this.getArgument([0, 1])
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step that propagates
|
||||
* tainted data.
|
||||
*/
|
||||
predicate taintFlow(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
fromNode.asExpr() = taintFromExpr and toNode.asExpr() = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `expr` is a JEXL engine that is not configured with a sandbox.
|
||||
*/
|
||||
private predicate isUnsafeEngine(Expr expr) {
|
||||
not exists(SandboxedJexlFlowConfig config | config.hasFlowTo(DataFlow::exprNode(expr)))
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration for a tracking sandboxed JEXL engines.
|
||||
*/
|
||||
private class SandboxedJexlFlowConfig extends DataFlow2::Configuration {
|
||||
SandboxedJexlFlowConfig() { this = "JexlInjection::SandboxedJexlFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof SandboxedJexlSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
(
|
||||
m instanceof CreateJexlScriptMethod or
|
||||
m instanceof CreateJexlExpressionMethod or
|
||||
m instanceof CreateJexlTemplateMethod
|
||||
) and
|
||||
ma.getQualifier() = node.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
createsJexlEngine(fromNode, toNode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a data flow source for JEXL engines configured with a sandbox.
|
||||
*/
|
||||
private class SandboxedJexlSource extends DataFlow::ExprNode {
|
||||
SandboxedJexlSource() {
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
m.getDeclaringType() instanceof JexlBuilder and
|
||||
m.hasName(["uberspect", "sandbox"]) and
|
||||
m.getReturnType() instanceof JexlBuilder and
|
||||
this.asExpr() = [ma, ma.getQualifier()]
|
||||
)
|
||||
or
|
||||
exists(ConstructorCall cc |
|
||||
cc.getConstructedType() instanceof JexlEngine and
|
||||
cc.getArgument(0).getType() instanceof JexlUberspect and
|
||||
cc = this.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step that creates one of the JEXL engines.
|
||||
*/
|
||||
private predicate createsJexlEngine(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(MethodAccess ma, Method m | m = ma.getMethod() |
|
||||
(m.getDeclaringType() instanceof JexlBuilder or m.getDeclaringType() instanceof JexlEngine) and
|
||||
m.hasName(["create", "createJxltEngine"]) and
|
||||
ma.getQualifier() = fromNode.asExpr() and
|
||||
ma = toNode.asExpr()
|
||||
)
|
||||
or
|
||||
exists(ConstructorCall cc |
|
||||
cc.getConstructedType() instanceof UnifiedJexl and
|
||||
cc.getArgument(0) = fromNode.asExpr() and
|
||||
cc = toNode.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step that returns data from
|
||||
* a bean by calling one of its getters.
|
||||
*/
|
||||
private predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
m instanceof GetterMethod and
|
||||
ma.getQualifier() = fromNode.asExpr() and
|
||||
ma = toNode.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A methods in the `JexlEngine` class that gets or sets a property with a JEXL expression.
|
||||
*/
|
||||
private class JexlEngineGetSetPropertyMethod extends Method {
|
||||
JexlEngineGetSetPropertyMethod() {
|
||||
getDeclaringType() instanceof JexlEngine and
|
||||
hasName(["getProperty", "setProperty"])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that triggers direct evaluation of JEXL expressions.
|
||||
*/
|
||||
private class DirectJexlEvaluationMethod extends Method {
|
||||
DirectJexlEvaluationMethod() {
|
||||
getDeclaringType() instanceof JexlExpression and hasName("evaluate")
|
||||
or
|
||||
getDeclaringType() instanceof JexlScript and hasName("execute")
|
||||
or
|
||||
getDeclaringType() instanceof JxltEngineExpression and hasName(["evaluate", "prepare"])
|
||||
or
|
||||
getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate")
|
||||
or
|
||||
getDeclaringType() instanceof UnifiedJexlExpression and hasName(["evaluate", "prepare"])
|
||||
or
|
||||
getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that creates a JEXL script.
|
||||
*/
|
||||
private class CreateJexlScriptMethod extends Method {
|
||||
CreateJexlScriptMethod() { getDeclaringType() instanceof JexlEngine and hasName("createScript") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that creates a `Callable` for a JEXL expression or script.
|
||||
*/
|
||||
private class CreateJexlCallableMethod extends Method {
|
||||
CreateJexlCallableMethod() {
|
||||
(getDeclaringType() instanceof JexlExpression or getDeclaringType() instanceof JexlScript) and
|
||||
hasName("callable")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that creates a JEXL template.
|
||||
*/
|
||||
private class CreateJexlTemplateMethod extends Method {
|
||||
CreateJexlTemplateMethod() {
|
||||
(getDeclaringType() instanceof JxltEngine or getDeclaringType() instanceof UnifiedJexl) and
|
||||
hasName("createTemplate")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that creates a JEXL expression.
|
||||
*/
|
||||
private class CreateJexlExpressionMethod extends Method {
|
||||
CreateJexlExpressionMethod() {
|
||||
(getDeclaringType() instanceof JexlEngine or getDeclaringType() instanceof JxltEngine) and
|
||||
hasName("createExpression")
|
||||
or
|
||||
getDeclaringType() instanceof UnifiedJexl and hasName("parse")
|
||||
}
|
||||
}
|
||||
|
||||
private class JexlRefType extends RefType {
|
||||
JexlRefType() { getPackage().hasName(["org.apache.commons.jexl2", "org.apache.commons.jexl3"]) }
|
||||
}
|
||||
|
||||
private class JexlExpression extends JexlRefType {
|
||||
JexlExpression() { hasName(["Expression", "JexlExpression"]) }
|
||||
}
|
||||
|
||||
private class JexlScript extends JexlRefType {
|
||||
JexlScript() { hasName(["Script", "JexlScript"]) }
|
||||
}
|
||||
|
||||
private class JexlBuilder extends JexlRefType {
|
||||
JexlBuilder() { hasName("JexlBuilder") }
|
||||
}
|
||||
|
||||
private class JexlEngine extends JexlRefType {
|
||||
JexlEngine() { hasName("JexlEngine") }
|
||||
}
|
||||
|
||||
private class JxltEngine extends JexlRefType {
|
||||
JxltEngine() { hasName("JxltEngine") }
|
||||
}
|
||||
|
||||
private class UnifiedJexl extends JexlRefType {
|
||||
UnifiedJexl() { hasName("UnifiedJEXL") }
|
||||
}
|
||||
|
||||
private class JexlUberspect extends Interface {
|
||||
JexlUberspect() {
|
||||
hasQualifiedName("org.apache.commons.jexl2.introspection", "Uberspect") or
|
||||
hasQualifiedName("org.apache.commons.jexl3.introspection", "JexlUberspect")
|
||||
}
|
||||
}
|
||||
|
||||
private class JxltEngineExpression extends NestedType {
|
||||
JxltEngineExpression() { getEnclosingType() instanceof JxltEngine and hasName("Expression") }
|
||||
}
|
||||
|
||||
private class JxltEngineTemplate extends NestedType {
|
||||
JxltEngineTemplate() { getEnclosingType() instanceof JxltEngine and hasName("Template") }
|
||||
}
|
||||
|
||||
private class UnifiedJexlExpression extends NestedType {
|
||||
UnifiedJexlExpression() { getEnclosingType() instanceof UnifiedJexl and hasName("Expression") }
|
||||
}
|
||||
|
||||
private class UnifiedJexlTemplate extends NestedType {
|
||||
UnifiedJexlTemplate() { getEnclosingType() instanceof UnifiedJexl and hasName("Template") }
|
||||
}
|
||||
|
||||
private class Reader extends RefType {
|
||||
Reader() { hasQualifiedName("java.io", "Reader") }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Bad: Execute externally controlled input in Nashorn Script Engine
|
||||
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
|
||||
NashornScriptEngine engine = (NashornScriptEngine) factory.getScriptEngine(new String[] { "-scripting"});
|
||||
Object result = engine.eval(input);
|
||||
@@ -0,0 +1,14 @@
|
||||
public void evaluate(Socket socket) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(socket.getInputStream()))) {
|
||||
|
||||
JexlSandbox onlyMath = new JexlSandbox(false);
|
||||
onlyMath.white("java.lang.Math");
|
||||
JexlEngine jexl = new JexlBuilder().sandbox(onlyMath).create();
|
||||
|
||||
String input = reader.readLine();
|
||||
JexlExpression expression = jexl.createExpression(input);
|
||||
JexlContext context = new MapContext();
|
||||
expression.evaluate(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
public void evaluate(Socket socket) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(socket.getInputStream()))) {
|
||||
|
||||
JexlUberspect sandbox = new JexlUberspectSandbox();
|
||||
JexlEngine jexl = new JexlBuilder().uberspect(sandbox).create();
|
||||
|
||||
String input = reader.readLine();
|
||||
JexlExpression expression = jexl.createExpression(input);
|
||||
JexlContext context = new MapContext();
|
||||
expression.evaluate(context);
|
||||
}
|
||||
|
||||
private static class JexlUberspectSandbox implements JexlUberspect {
|
||||
|
||||
private static final List<String> ALLOWED_CLASSES =
|
||||
Arrays.asList("java.lang.Math", "java.util.Random");
|
||||
|
||||
private final JexlUberspect uberspect = new JexlBuilder().create().getUberspect();
|
||||
|
||||
private void checkAccess(Object obj) {
|
||||
if (!ALLOWED_CLASSES.contains(obj.getClass().getCanonicalName())) {
|
||||
throw new AccessControlException("Not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlMethod getMethod(Object obj, String method, Object... args) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getMethod(obj, method, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PropertyResolver> getResolvers(JexlOperator op, Object obj) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getResolvers(op, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClassLoader(ClassLoader loader) {
|
||||
uberspect.setClassLoader(loader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return uberspect.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlMethod getConstructor(Object obj, Object... args) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getConstructor(obj, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlPropertyGet getPropertyGet(Object obj, Object identifier) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getPropertyGet(obj, identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlPropertyGet getPropertyGet(List<PropertyResolver> resolvers, Object obj, Object identifier) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getPropertyGet(resolvers, obj, identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getPropertySet(obj, identifier, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlPropertySet getPropertySet(List<PropertyResolver> resolvers, Object obj, Object identifier, Object arg) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getPropertySet(resolvers, obj, identifier, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<?> getIterator(Object obj) {
|
||||
checkAccess(obj);
|
||||
return uberspect.getIterator(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic) {
|
||||
return uberspect.getArithmetic(arithmetic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ It allows applications to interact with scripts written in languages such as Jav
|
||||
<example>
|
||||
<p>The following code could execute random JavaScript code</p>
|
||||
<sample src="ScriptEngine.java" />
|
||||
<sample src="NashornScriptEngine.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
@@ -22,4 +23,4 @@ It allows applications to interact with scripts written in languages such as Jav
|
||||
CERT coding standard: <a href="https://wiki.sei.cmu.edu/confluence/display/java/IDS52-J.+Prevent+code+injection">ScriptEngine code injection</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
</qhelp>
|
||||
|
||||
@@ -15,7 +15,7 @@ import DataFlow::PathGraph
|
||||
|
||||
class ScriptEngineMethod extends Method {
|
||||
ScriptEngineMethod() {
|
||||
this.getDeclaringType().hasQualifiedName("javax.script", "ScriptEngine") and
|
||||
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and
|
||||
this.hasName("eval")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
<include src="SpringViewManipulation.qhelp" />
|
||||
</qhelp>
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @name Spring Implicit View Manipulation
|
||||
* @description Untrusted input in a Spring View Controller can lead to RCE.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/spring-view-manipulation-implicit
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import SpringViewManipulationLib
|
||||
|
||||
private predicate canResultInImplicitViewConversion(Method m) {
|
||||
m.getReturnType() instanceof VoidType
|
||||
or
|
||||
m.getReturnType() instanceof MapType
|
||||
or
|
||||
m.getReturnType().(RefType).hasQualifiedName("org.springframework.ui", "Model")
|
||||
}
|
||||
|
||||
private predicate maybeATestMethod(Method m) {
|
||||
exists(string s |
|
||||
s = m.getName() or
|
||||
s = m.getFile().getRelativePath() or
|
||||
s = m.getDeclaringType().getName()
|
||||
|
|
||||
s.matches(["%test%", "%example%", "%exception%"])
|
||||
)
|
||||
}
|
||||
|
||||
private predicate mayBeExploitable(Method m) {
|
||||
// There should be a attacker controlled parameter in the URI for the attack to be exploitable.
|
||||
// This is possible only when there exists a parameter with the Spring `@PathVariable` annotation
|
||||
// applied to it.
|
||||
exists(Parameter p |
|
||||
p = m.getAParameter() and
|
||||
p.hasAnnotation("org.springframework.web.bind.annotation", "PathVariable") and
|
||||
// Having a parameter of say type `Long` is non exploitable as Java type
|
||||
// checking rules are applied prior to view name resolution, rendering the exploit useless.
|
||||
// hence, here we check for the param type to be a Java `String`.
|
||||
p.getType() instanceof TypeString and
|
||||
// Exclude cases where a regex check is applied on a parameter to prevent false positives.
|
||||
not m.(SpringRequestMappingMethod).getValue().matches("%{%:[%]%}%")
|
||||
) and
|
||||
not maybeATestMethod(m)
|
||||
}
|
||||
|
||||
from SpringRequestMappingMethod m
|
||||
where
|
||||
thymeleafIsUsed() and
|
||||
mayBeExploitable(m) and
|
||||
canResultInImplicitViewConversion(m) and
|
||||
// If there's a parameter of type`HttpServletResponse`, Spring Framework does not interpret
|
||||
// it as a view name, but just returns this string in HTTP Response preventing exploitation
|
||||
// This also applies to `@ResponseBody` annotation.
|
||||
not m.getParameterType(_) instanceof HttpServletResponse and
|
||||
// A spring request mapping method which does not have response body annotation applied to it
|
||||
m.getAnAnnotation().getType() instanceof SpringRequestMappingAnnotationType and
|
||||
not exists(SpringResponseBodyAnnotationType t | t = m.getAnAnnotation().getType()) and
|
||||
// `@RestController` inherits `@ResponseBody` internally so it should be ignored.
|
||||
not m.getDeclaringType() instanceof SpringRestController
|
||||
select m, "This method may be vulnerable to spring view manipulation vulnerabilities"
|
||||
@@ -0,0 +1,17 @@
|
||||
@Controller
|
||||
public class SptingViewManipulationController {
|
||||
|
||||
Logger log = LoggerFactory.getLogger(HelloController.class);
|
||||
|
||||
@GetMapping("/safe/fragment")
|
||||
public String Fragment(@RequestParam String section) {
|
||||
// bad as template path is attacker controlled
|
||||
return "welcome :: " + section;
|
||||
}
|
||||
|
||||
@GetMapping("/doc/{document}")
|
||||
public void getDocument(@PathVariable String document) {
|
||||
// returns void, so view name is taken from URI
|
||||
log.info("Retrieving " + document);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
@Controller
|
||||
public class SptingViewManipulationController {
|
||||
|
||||
Logger log = LoggerFactory.getLogger(HelloController.class);
|
||||
|
||||
@GetMapping("/safe/fragment")
|
||||
@ResponseBody
|
||||
public String Fragment(@RequestParam String section) {
|
||||
// good, as `@ResponseBody` annotation tells Spring
|
||||
// to process the return values as body, instead of view name
|
||||
return "welcome :: " + section;
|
||||
}
|
||||
|
||||
@GetMapping("/safe/doc/{document}")
|
||||
public void getDocument(@PathVariable String document, HttpServletResponse response) {
|
||||
// good as `HttpServletResponse param tells Spring that the response is already
|
||||
// processed.
|
||||
log.info("Retrieving " + document); // FP
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
The Spring Expression Language (SpEL) is a powerful expression language
|
||||
provided by Spring Framework. The language offers many features
|
||||
including invocation of methods available in the JVM.
|
||||
</p>
|
||||
<p>
|
||||
An unrestricted view name manipulation vulnerability in Spring Framework could lead to attacker-controlled arbitary SpEL expressions being evaluated using attacker-controlled data, which may in turn allow an attacker to run arbitrary code.
|
||||
</p>
|
||||
<p>
|
||||
Note: two related variants of this problem are detected by different queries, `java/spring-view-manipulation` and `java/spring-view-manipulation-implicit`. The first detects taint flow problems where the return types is always <code>String</code>. While the latter, `java/spring-view-manipulation-implicit` detects cases where the request mapping method has a non-string return type such as <code>void</code>.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
In general, using user input to determine Spring view name should be avoided.
|
||||
If user input must be included in the expression, the controller can be annotated by
|
||||
a <code>@ReponseBody</code> annotation. In this case, Spring Framework does not interpret
|
||||
it as a view name, but just returns this string in HTTP Response. The same applies to using
|
||||
a <code>@RestController</code> annotation on a class, as internally it inherits <code>@ResponseBody</code>.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the following example, the <code>Fragment</code> method uses an externally controlled variable <code>section</code> to generate the view name. Hence, it is vulnerable to Spring View Manipulation attacks.
|
||||
</p>
|
||||
<sample src="SpringViewBad.java" />
|
||||
<p>
|
||||
This can be easily prevented by using the <code>ResponseBody</code> annotation which marks the reponse is already processed preventing exploitation of Spring View Manipulation vulnerabilities. Alternatively, this can also be fixed by adding a <code>HttpServletResponse</code> parameter to the method definition as shown in the example below.
|
||||
</p>
|
||||
<sample src="SpringViewGood.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Veracode Research : <a href="https://github.com/veracode-research/spring-view-manipulation/">Spring View Manipulation </a>
|
||||
</li>
|
||||
<li>
|
||||
Spring Framework Reference Documentation: <a href="https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/expressions.html">Spring Expression Language (SpEL)</a>
|
||||
</li>
|
||||
<li>
|
||||
OWASP: <a href="https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection">Expression Language Injection</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @name Spring View Manipulation
|
||||
* @description Untrusted input in a Spring View can lead to RCE.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/spring-view-manipulation
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import SpringViewManipulationLib
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, SpringViewManipulationConfig conf
|
||||
where
|
||||
thymeleafIsUsed() and
|
||||
conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Potential Spring Expression Language injection from $@.",
|
||||
source.getNode(), "this user input"
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Provides classes for reasoning about Spring View Manipulation vulnerabilities
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.frameworks.spring.Spring
|
||||
import SpringFrameworkLib
|
||||
|
||||
/** Holds if `Thymeleaf` templating engine is used in the project. */
|
||||
predicate thymeleafIsUsed() {
|
||||
exists(Pom p |
|
||||
p.getADependency().getArtifact().getValue() in [
|
||||
"spring-boot-starter-thymeleaf", "thymeleaf-spring4", "springmvc-xml-thymeleaf",
|
||||
"thymeleaf-spring5"
|
||||
]
|
||||
)
|
||||
or
|
||||
exists(SpringBean b | b.getClassNameRaw().matches("org.thymeleaf.spring%"))
|
||||
}
|
||||
|
||||
/** Models methods from the `javax.portlet.RenderState` package which return data from externally controlled sources. */
|
||||
class PortletRenderRequestMethod extends Method {
|
||||
PortletRenderRequestMethod() {
|
||||
exists(RefType c, Interface t |
|
||||
c.extendsOrImplements*(t) and
|
||||
t.hasQualifiedName("javax.portlet", "RenderState") and
|
||||
this = c.getAMethod()
|
||||
|
|
||||
this.hasName([
|
||||
"getCookies", "getParameter", "getRenderParameters", "getParameterNames",
|
||||
"getParameterValues", "getParameterMap"
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for unsafe user input
|
||||
* that can lead to Spring View Manipulation vulnerabilities.
|
||||
*/
|
||||
class SpringViewManipulationConfig extends TaintTracking::Configuration {
|
||||
SpringViewManipulationConfig() { this = "Spring View Manipulation Config" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source instanceof RemoteFlowSource or
|
||||
source instanceof WebRequestSource or
|
||||
source.asExpr().(MethodAccess).getMethod() instanceof PortletRenderRequestMethod
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof SpringViewManipulationSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
// Block flows like
|
||||
// ```
|
||||
// a = "redirect:" + taint`
|
||||
// ```
|
||||
exists(AddExpr e, StringLiteral sl |
|
||||
node.asExpr() = e.getControlFlowNode().getASuccessor*() and
|
||||
sl = e.getLeftOperand*() and
|
||||
sl.getRepresentedString().matches(["redirect:%", "ajaxredirect:%", "forward:%"])
|
||||
)
|
||||
or
|
||||
// Block flows like
|
||||
// ```
|
||||
// x.append("redirect:");
|
||||
// x.append(tainted());
|
||||
// return x.toString();
|
||||
//
|
||||
// "redirect:".concat(taint)
|
||||
//
|
||||
// String.format("redirect:%s",taint);
|
||||
// ```
|
||||
exists(Call ca, StringLiteral sl |
|
||||
(
|
||||
sl = ca.getArgument(_)
|
||||
or
|
||||
sl = ca.getQualifier()
|
||||
) and
|
||||
ca = getAStringCombiningCall() and
|
||||
sl.getRepresentedString().matches(["redirect:%", "ajaxredirect:%", "forward:%"])
|
||||
|
|
||||
exists(Call cc | DataFlow::localExprFlow(ca.getQualifier(), cc.getQualifier()) |
|
||||
cc = node.asExpr()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private Call getAStringCombiningCall() {
|
||||
exists(StringCombiningMethod m | result = m.getAReference())
|
||||
}
|
||||
|
||||
abstract private class StringCombiningMethod extends Method { }
|
||||
|
||||
private class AppendableAppendMethod extends StringCombiningMethod {
|
||||
AppendableAppendMethod() {
|
||||
exists(RefType t |
|
||||
t.hasQualifiedName("java.lang", "Appendable") and
|
||||
this.getDeclaringType().extendsOrImplements*(t) and
|
||||
this.hasName("append")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class StringConcatMethod extends StringCombiningMethod {
|
||||
StringConcatMethod() {
|
||||
this.getDeclaringType() instanceof TypeString and
|
||||
this.hasName("concat")
|
||||
}
|
||||
}
|
||||
|
||||
private class StringFormatMethod extends StringCombiningMethod {
|
||||
StringFormatMethod() {
|
||||
this.getDeclaringType() instanceof TypeString and
|
||||
this.hasName("format")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for Spring View Manipulation vulnerabilities,
|
||||
*/
|
||||
class SpringViewManipulationSink extends DataFlow::ExprNode {
|
||||
SpringViewManipulationSink() {
|
||||
exists(ReturnStmt r, SpringRequestMappingMethod m |
|
||||
r.getResult() = this.asExpr() and
|
||||
m.getBody().getAStmt() = r and
|
||||
not m.isResponseBody() and
|
||||
r.getResult().getType() instanceof TypeString
|
||||
)
|
||||
or
|
||||
exists(ConstructorCall c | c.getConstructedType() instanceof ModelAndView |
|
||||
this.asExpr() = c.getArgument(0) and
|
||||
c.getConstructor().getParameterType(0) instanceof TypeString
|
||||
)
|
||||
or
|
||||
exists(SpringModelAndViewSetViewNameCall c | this.asExpr() = c.getArgument(0))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
public void evaluate(Socket socket) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(socket.getInputStream()))) {
|
||||
|
||||
String input = reader.readLine();
|
||||
JexlEngine jexl = new JexlBuilder().create();
|
||||
JexlExpression expression = jexl.createExpression(input);
|
||||
JexlContext context = new MapContext();
|
||||
expression.evaluate(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
public class InsecureLdapEndpoint {
|
||||
public Hashtable<String, String> createConnectionEnv() {
|
||||
Hashtable<String, String> env = new Hashtable<String, String>();
|
||||
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||
env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636");
|
||||
|
||||
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
||||
env.put(Context.SECURITY_PRINCIPAL, "username");
|
||||
env.put(Context.SECURITY_CREDENTIALS, "secpassword");
|
||||
|
||||
// BAD - Test configuration with disabled SSL endpoint check.
|
||||
{
|
||||
System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true");
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Java versions 8u181 or greater have enabled LDAPS endpoint identification by default. Nowadays
|
||||
infrastructure services like LDAP are commonly deployed behind load balancers therefore the LDAP
|
||||
server name can be different from the FQDN of the LDAPS endpoint. If a service certificate does not
|
||||
properly contain a matching DNS name as part of the certificate, Java will reject it by default.</p>
|
||||
<p>Instead of addressing the issue properly by having a compliant certificate deployed, frequently
|
||||
developers simply disable the LDAPS endpoint check.</p>
|
||||
<p>Failing to validate the certificate makes the SSL session susceptible to a man-in-the-middle attack.
|
||||
This query checks whether the LDAPS endpoint check is disabled in system properties.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Replace any non-conforming LDAP server certificates to include a DNS name in the subjectAltName field
|
||||
of the certificate that matches the FQDN of the service.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following two examples show two ways of configuring LDAPS endpoint. In the 'BAD' case,
|
||||
endpoint check is disabled. In the 'GOOD' case, endpoint check is left enabled through the
|
||||
default Java configuration.</p>
|
||||
<sample src="InsecureLdapEndpoint.java" />
|
||||
<sample src="InsecureLdapEndpoint2.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Oracle Java 8 Update 181 (8u181):
|
||||
<a href="https://www.java.com/en/download/help/release_changes.html">Endpoint identification enabled on LDAPS connections</a>
|
||||
</li>
|
||||
<li>
|
||||
IBM:
|
||||
<a href="https://www.ibm.com/support/pages/how-do-i-fix-ldap-ssl-error-%E2%80%9Cjavasecuritycertcertificateexception-no-subject-alternative-names-present%E2%80%9D-websphere-application-server">Fix this LDAP SSL error</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* @name Insecure LDAPS Endpoint Configuration
|
||||
* @description Java application configured to disable LDAPS endpoint identification does not validate
|
||||
* the SSL certificate to properly ensure that it is actually associated with that host.
|
||||
* @kind problem
|
||||
* @id java/insecure-ldaps-endpoint
|
||||
* @tags security
|
||||
* external/cwe-297
|
||||
*/
|
||||
|
||||
import java
|
||||
|
||||
/** The method to set a system property. */
|
||||
class SetSystemPropertyMethod extends Method {
|
||||
SetSystemPropertyMethod() {
|
||||
this.hasName("setProperty") and
|
||||
this.getDeclaringType().hasQualifiedName("java.lang", "System")
|
||||
}
|
||||
}
|
||||
|
||||
/** The class `java.util.Hashtable`. */
|
||||
class TypeHashtable extends Class {
|
||||
TypeHashtable() { this.getSourceDeclaration().hasQualifiedName("java.util", "Hashtable") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The method to set Java properties either through `setProperty` declared in the class `Properties`
|
||||
* or `put` declared in its parent class `HashTable`.
|
||||
*/
|
||||
class SetPropertyMethod extends Method {
|
||||
SetPropertyMethod() {
|
||||
this.getDeclaringType().getAnAncestor() instanceof TypeHashtable and
|
||||
this.hasName(["put", "setProperty"])
|
||||
}
|
||||
}
|
||||
|
||||
/** The `setProperties` method declared in `java.lang.System`. */
|
||||
class SetSystemPropertiesMethod extends Method {
|
||||
SetSystemPropertiesMethod() {
|
||||
this.hasName("setProperties") and
|
||||
this.getDeclaringType().hasQualifiedName("java.lang", "System")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `Expr` expr is evaluated to the string literal
|
||||
* `com.sun.jndi.ldap.object.disableEndpointIdentification`.
|
||||
*/
|
||||
predicate isPropertyDisableLdapEndpointId(Expr expr) {
|
||||
expr.(CompileTimeConstantExpr).getStringValue() =
|
||||
"com.sun.jndi.ldap.object.disableEndpointIdentification"
|
||||
or
|
||||
exists(Field f |
|
||||
expr = f.getAnAccess() and
|
||||
f.getAnAssignedValue().(StringLiteral).getValue() =
|
||||
"com.sun.jndi.ldap.object.disableEndpointIdentification"
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if an expression is evaluated to the boolean value true. */
|
||||
predicate isBooleanTrue(Expr expr) {
|
||||
expr.(CompileTimeConstantExpr).getStringValue() = "true" // "true"
|
||||
or
|
||||
expr.(BooleanLiteral).getBooleanValue() = true // true
|
||||
or
|
||||
exists(MethodAccess ma |
|
||||
expr = ma and
|
||||
ma.getMethod().hasName("toString") and
|
||||
ma.getQualifier().(FieldAccess).getField().hasName("TRUE") and
|
||||
ma.getQualifier()
|
||||
.(FieldAccess)
|
||||
.getField()
|
||||
.getDeclaringType()
|
||||
.hasQualifiedName("java.lang", "Boolean") // Boolean.TRUE.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `ma` is in a test class or method. */
|
||||
predicate isTestMethod(MethodAccess ma) {
|
||||
ma.getEnclosingCallable() instanceof TestMethod or
|
||||
ma.getEnclosingCallable().getDeclaringType() instanceof TestClass or
|
||||
ma.getEnclosingCallable().getDeclaringType().getPackage().getName().matches("%test%") or
|
||||
ma.getEnclosingCallable().getDeclaringType().getName().toLowerCase().matches("%test%")
|
||||
}
|
||||
|
||||
/** Holds if `MethodAccess` ma disables SSL endpoint check. */
|
||||
predicate isInsecureSSLEndpoint(MethodAccess ma) {
|
||||
(
|
||||
ma.getMethod() instanceof SetSystemPropertyMethod and
|
||||
isPropertyDisableLdapEndpointId(ma.getArgument(0)) and
|
||||
isBooleanTrue(ma.getArgument(1)) //com.sun.jndi.ldap.object.disableEndpointIdentification=true
|
||||
or
|
||||
ma.getMethod() instanceof SetSystemPropertiesMethod and
|
||||
exists(MethodAccess ma2 |
|
||||
ma2.getMethod() instanceof SetPropertyMethod and
|
||||
isPropertyDisableLdapEndpointId(ma2.getArgument(0)) and
|
||||
isBooleanTrue(ma2.getArgument(1)) and //com.sun.jndi.ldap.object.disableEndpointIdentification=true
|
||||
ma2.getQualifier().(VarAccess).getVariable().getAnAccess() = ma.getArgument(0) // systemProps.setProperties(properties)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from MethodAccess ma
|
||||
where
|
||||
isInsecureSSLEndpoint(ma) and
|
||||
not isTestMethod(ma)
|
||||
select ma, "LDAPS configuration allows insecure endpoint identification"
|
||||
@@ -0,0 +1,17 @@
|
||||
public class InsecureLdapEndpoint2 {
|
||||
public Hashtable<String, String> createConnectionEnv() {
|
||||
Hashtable<String, String> env = new Hashtable<String, String>();
|
||||
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||
env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636");
|
||||
|
||||
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
||||
env.put(Context.SECURITY_PRINCIPAL, "username");
|
||||
env.put(Context.SECURITY_CREDENTIALS, "secpassword");
|
||||
|
||||
// GOOD - No configuration to disable SSL endpoint check since it is enabled by default.
|
||||
{
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
}
|
||||
@@ -55,8 +55,8 @@ revocation checker that uses OCSP to obtain revocation status of certificates.</
|
||||
<a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html">Java PKI Programmer's Guide</a>
|
||||
</li>
|
||||
<li>
|
||||
Java SE API Specification:
|
||||
<a href="https://docs.oracle.com/javase/8/docs/api/java/security/cert/CertPathValidator.html">CertPathValidator</a>
|
||||
Java API Specification:
|
||||
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/cert/CertPathValidator.html">CertPathValidator</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
|
||||
@@ -42,18 +42,18 @@ Unfortunately, older versions were found to be vulnerable to a number of attacks
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Java SE API Specification:
|
||||
<a href="https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/SSLContext.html">SSLContext</a>
|
||||
Java API Specification:
|
||||
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLContext.html">SSLContext</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Java SE API Specification:
|
||||
<a href="https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/SSLParameters.html">SSLParameters</a>
|
||||
Java API Specification:
|
||||
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLParameters.html">SSLParameters</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Java SE API Specification:
|
||||
<a href="https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/SSLSocket.html">SSLSocket</a>
|
||||
Java API Specification:
|
||||
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLSocket.html">SSLSocket</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class CorsFilter implements Filter {
|
||||
public void init(FilterConfig filterConfig) throws ServletException {}
|
||||
|
||||
public void doFilter(ServletRequest req, ServletResponse res,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
String url = request.getHeader("Origin");
|
||||
|
||||
if (!StringUtils.isEmpty(url)) {
|
||||
String val = response.getHeader("Access-Control-Allow-Origin");
|
||||
|
||||
if (StringUtils.isEmpty(val)) {
|
||||
response.addHeader("Access-Control-Allow-Origin", url); // BAD -> User controlled CORS header being set here.
|
||||
response.addHeader("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(url)) {
|
||||
List<String> checkorigins = Arrays.asList("www.example.com", "www.sub.example.com");
|
||||
|
||||
if (checkorigins.contains(url)) { // GOOD -> Origin is validated here.
|
||||
response.addHeader("Access-Control-Allow-Origin", url);
|
||||
response.addHeader("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
|
||||
public void destroy() {}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
|
||||
A server can send the
|
||||
<code>Access-Control-Allow-Credentials</code> CORS header to control
|
||||
when a browser may send user credentials in Cross-Origin HTTP
|
||||
requests.
|
||||
|
||||
</p>
|
||||
<p>
|
||||
|
||||
When the <code>Access-Control-Allow-Credentials</code> header
|
||||
is <code>true</code>, the <code>Access-Control-Allow-Origin</code>
|
||||
header must have a value different from <code>*</code> in order
|
||||
for browsers to accept the header. Therefore, to allow multiple origins
|
||||
for cross-origin requests with credentials, the server must
|
||||
dynamically compute the value of the
|
||||
<code>Access-Control-Allow-Origin</code> header. Computing this
|
||||
header value from information in the request to the server can
|
||||
therefore potentially allow an attacker to control the origins that
|
||||
the browser sends credentials to.
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
|
||||
When the <code>Access-Control-Allow-Credentials</code> header
|
||||
value is <code>true</code>, a dynamic computation of the
|
||||
<code>Access-Control-Allow-Origin</code> header must involve
|
||||
sanitization if it relies on user-controlled input.
|
||||
|
||||
|
||||
</p>
|
||||
<p>
|
||||
|
||||
Since the <code>null</code> origin is easy to obtain for an
|
||||
attacker, it is never safe to use <code>null</code> as the value of
|
||||
the <code>Access-Control-Allow-Origin</code> header when the
|
||||
<code>Access-Control-Allow-Credentials</code> header value is
|
||||
<code>true</code>.A null origin can be set by an attacker using a sandboxed iframe.
|
||||
A more detailed explanation is available in the portswigger blogpost referenced below.
|
||||
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
|
||||
In the example below, the server allows the browser to send
|
||||
user credentials in a cross-origin request. The request header
|
||||
<code>origins</code> controls the allowed origins for such a
|
||||
Cross-Origin request.
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="UnvalidatedCors.java"/>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin">CORS, Access-Control-Allow-Origin</a>.</li>
|
||||
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials">CORS, Access-Control-Allow-Credentials</a>.</li>
|
||||
<li>PortSwigger: <a href="http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html">Exploiting CORS Misconfigurations for Bitcoins and Bounties</a></li>
|
||||
<li>W3C: <a href="https://w3c.github.io/webappsec-cors-for-developers/#resources">CORS for developers, Advice for Resource Owners</a></li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* @name CORS is derived from untrusted input
|
||||
* @description CORS header is derived from untrusted input, allowing a remote user to control which origins are trusted.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/unvalidated-cors-origin-set
|
||||
* @tags security
|
||||
* external/cwe/cwe-346
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.frameworks.Servlets
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.TaintTracking2
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* Holds if `header` sets `Access-Control-Allow-Credentials` to `true`. This ensures fair chances of exploitability.
|
||||
*/
|
||||
private predicate setsAllowCredentials(MethodAccess header) {
|
||||
(
|
||||
header.getMethod() instanceof ResponseSetHeaderMethod or
|
||||
header.getMethod() instanceof ResponseAddHeaderMethod
|
||||
) and
|
||||
header.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() =
|
||||
"access-control-allow-credentials" and
|
||||
header.getArgument(1).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "true"
|
||||
}
|
||||
|
||||
private class CorsProbableCheckAccess extends MethodAccess {
|
||||
CorsProbableCheckAccess() {
|
||||
getMethod().hasName("contains") and
|
||||
getMethod().getDeclaringType().getASourceSupertype*() instanceof CollectionType
|
||||
or
|
||||
getMethod().hasName("containsKey") and
|
||||
getMethod().getDeclaringType().getASourceSupertype*() instanceof MapType
|
||||
or
|
||||
getMethod().hasName("equals") and
|
||||
getQualifier().getType() instanceof TypeString
|
||||
}
|
||||
}
|
||||
|
||||
private Expr getAccessControlAllowOriginHeaderName() {
|
||||
result.(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-origin"
|
||||
}
|
||||
|
||||
/**
|
||||
* This taintflow2 configuration checks if there is a flow from source node towards CorsProbableCheckAccess methods.
|
||||
*/
|
||||
class CorsSourceReachesCheckConfig extends TaintTracking2::Configuration {
|
||||
CorsSourceReachesCheckConfig() { this = "CorsOriginConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { any(CorsOriginConfig c).hasFlow(source, _) }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink.asExpr() = any(CorsProbableCheckAccess check).getAnArgument()
|
||||
}
|
||||
}
|
||||
|
||||
private class CorsOriginConfig extends TaintTracking::Configuration {
|
||||
CorsOriginConfig() { this = "CorsOriginConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(MethodAccess corsHeader, MethodAccess allowCredentialsHeader |
|
||||
(
|
||||
corsHeader.getMethod() instanceof ResponseSetHeaderMethod or
|
||||
corsHeader.getMethod() instanceof ResponseAddHeaderMethod
|
||||
) and
|
||||
getAccessControlAllowOriginHeaderName() = corsHeader.getArgument(0) and
|
||||
setsAllowCredentials(allowCredentialsHeader) and
|
||||
corsHeader.getEnclosingCallable() = allowCredentialsHeader.getEnclosingCallable() and
|
||||
sink.asExpr() = corsHeader.getArgument(1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
DataFlow::PathNode source, DataFlow::PathNode sink, CorsOriginConfig conf,
|
||||
CorsSourceReachesCheckConfig sanconf
|
||||
where conf.hasFlowPath(source, sink) and not sanconf.hasFlow(source.getNode(), _)
|
||||
select sink.getNode(), source, sink, "CORS header is being set using user controlled value $@.",
|
||||
source.getNode(), "user-provided value"
|
||||
32
java/ql/src/experimental/Security/CWE/CWE-489/EJBMain.java
Normal file
32
java/ql/src/experimental/Security/CWE/CWE-489/EJBMain.java
Normal file
@@ -0,0 +1,32 @@
|
||||
public class EJBMain implements SessionBean {
|
||||
/**
|
||||
* Create the session bean (empty implementation)
|
||||
*/
|
||||
public void ejbCreate() throws javax.ejb.CreateException {
|
||||
System.out.println("EJBMain:ejbCreate()");
|
||||
}
|
||||
|
||||
public void ejbActivate() throws javax.ejb.EJBException, java.rmi.RemoteException {
|
||||
}
|
||||
|
||||
public void ejbPassivate() throws javax.ejb.EJBException, java.rmi.RemoteException {
|
||||
}
|
||||
|
||||
public void ejbRemove() throws javax.ejb.EJBException, java.rmi.RemoteException {
|
||||
}
|
||||
|
||||
public void setSessionContext(SessionContext parm1) throws javax.ejb.EJBException, java.rmi.RemoteException {
|
||||
}
|
||||
|
||||
public String doService() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// BAD - Implement a main method in session bean.
|
||||
public static void main(String[] args) throws Exception {
|
||||
EJBMain b = new EJBMain();
|
||||
b.doService();
|
||||
}
|
||||
|
||||
// GOOD - Not to have a main method in session bean.
|
||||
}
|
||||
27
java/ql/src/experimental/Security/CWE/CWE-489/EJBMain.qhelp
Normal file
27
java/ql/src/experimental/Security/CWE/CWE-489/EJBMain.qhelp
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Debug code can create unintended entry points in a deployed Java EE web application therefore should never make into production. There is no reason to have a main method in a Java EE web application. Having a main method in the Java EE application increases the attack surface that an attacker can exploit to attack the application logic.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Remove the main method from enterprise beans.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following example shows two ways of implementing enterprise beans. In the 'BAD' case, a main method is implemented. In the 'GOOD' case, no main method is implemented.</p>
|
||||
<sample src="EJBMain.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
SonarSource:
|
||||
<a href="https://rules.sonarsource.com/java/tag/owasp/RSPEC-2653">Web applications should not have a "main" method</a>
|
||||
</li>
|
||||
<li>
|
||||
Carnegie Mellon University:
|
||||
<a href="https://wiki.sei.cmu.edu/confluence/display/java/ENV06-J.+Production+code+must+not+contain+debugging+entry+points">ENV06-J. Production code must not contain debugging entry points</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
24
java/ql/src/experimental/Security/CWE/CWE-489/EJBMain.ql
Normal file
24
java/ql/src/experimental/Security/CWE/CWE-489/EJBMain.ql
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @name Main Method in Enterprise Java Bean
|
||||
* @description Java EE applications with a main method.
|
||||
* @kind problem
|
||||
* @id java/main-method-in-enterprise-bean
|
||||
* @tags security
|
||||
* external/cwe-489
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.J2EE
|
||||
import TestLib
|
||||
|
||||
/** The `main` method in an Enterprise Java Bean. */
|
||||
class EnterpriseBeanMainMethod extends Method {
|
||||
EnterpriseBeanMainMethod() {
|
||||
this.getDeclaringType() instanceof EnterpriseBean and
|
||||
this instanceof MainMethod and
|
||||
not isTestMethod(this)
|
||||
}
|
||||
}
|
||||
|
||||
from EnterpriseBeanMainMethod sm
|
||||
select sm, "Java EE application has a main method."
|
||||
11
java/ql/src/experimental/Security/CWE/CWE-489/StrutsBad.xml
Normal file
11
java/ql/src/experimental/Security/CWE/CWE-489/StrutsBad.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE struts PUBLIC
|
||||
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
|
||||
"http://struts.apache.org/dtds/struts-2.3.dtd">
|
||||
|
||||
<struts>
|
||||
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
|
||||
<constant name="struts.devMode" value="true" />
|
||||
<constant name="struts.i18n.encoding" value="utf-8" />
|
||||
<include file="login.xml" />
|
||||
</struts>
|
||||
11
java/ql/src/experimental/Security/CWE/CWE-489/StrutsGood.xml
Normal file
11
java/ql/src/experimental/Security/CWE/CWE-489/StrutsGood.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE struts PUBLIC
|
||||
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
|
||||
"http://struts.apache.org/dtds/struts-2.3.dtd">
|
||||
|
||||
<struts>
|
||||
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
|
||||
<constant name="struts.devMode" value="false" />
|
||||
<constant name="struts.i18n.encoding" value="utf-8"></constant>
|
||||
<include file="login.xml" />
|
||||
</struts>
|
||||
17
java/ql/src/experimental/Security/CWE/CWE-489/TestLib.qll
Normal file
17
java/ql/src/experimental/Security/CWE/CWE-489/TestLib.qll
Normal file
@@ -0,0 +1,17 @@
|
||||
/** Definitions related to test methods. */
|
||||
|
||||
import java
|
||||
|
||||
/**
|
||||
* Holds if `m` is a test method indicated by:
|
||||
* a) in a test directory such as `src/test/java`
|
||||
* b) in a test package whose name has the word `test`
|
||||
* c) in a test class whose name has the word `test`
|
||||
* d) in a test class implementing a test framework such as JUnit or TestNG
|
||||
*/
|
||||
predicate isTestMethod(Method m) {
|
||||
m.getDeclaringType().getName().toLowerCase().matches("%test%") or // Simple check to exclude test classes to reduce FPs
|
||||
m.getDeclaringType().getPackage().getName().toLowerCase().matches("%test%") or // Simple check to exclude classes in test packages to reduce FPs
|
||||
exists(m.getLocation().getFile().getAbsolutePath().indexOf("/src/test/java")) or // Match test directory structure of build tools like maven
|
||||
m instanceof TestMethod // Test method of a test case implementing a test framework such as JUnit or TestNG
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
public class WebComponentMain implements Servlet {
|
||||
// BAD - Implement a main method in servlet.
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Connect to my server
|
||||
URL url = new URL("https://www.example.com");
|
||||
url.openConnection();
|
||||
}
|
||||
|
||||
// GOOD - Not to have a main method in servlet.
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Debug code can create unintended entry points in a deployed Java EE web application therefore should never make into production. There is no reason to have a main method in a Java EE web application. Having a main method in the Java EE application increases the attack surface that an attacker can exploit to attack the application logic.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Remove the main method from web components including servlets, filters, and listeners.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following example shows two ways of implementing web components. In the 'BAD' case, a main method is implemented. In the 'GOOD' case, no main method is implemented.</p>
|
||||
<sample src="WebComponentMain.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Fortify:
|
||||
<a href="https://vulncat.fortify.com/en/detail?id=desc.structural.java.j2ee_badpractices_leftover_debug_code">J2EE Bad Practices: Leftover Debug Code</a>
|
||||
</li>
|
||||
<li>
|
||||
SonarSource:
|
||||
<a href="https://rules.sonarsource.com/java/tag/owasp/RSPEC-2653">Web applications should not have a "main" method</a>
|
||||
</li>
|
||||
<li>
|
||||
Carnegie Mellon University:
|
||||
<a href="https://wiki.sei.cmu.edu/confluence/display/java/ENV06-J.+Production+code+must+not+contain+debugging+entry+points">ENV06-J. Production code must not contain debugging entry points</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @name Main Method in Java EE Web Components
|
||||
* @description Java EE web applications with a main method.
|
||||
* @kind problem
|
||||
* @id java/main-method-in-web-components
|
||||
* @tags security
|
||||
* external/cwe-489
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.frameworks.Servlets
|
||||
import TestLib
|
||||
|
||||
/** The java type `javax.servlet.Filter`. */
|
||||
class ServletFilterClass extends Class {
|
||||
ServletFilterClass() { this.getASupertype*().hasQualifiedName("javax.servlet", "Filter") }
|
||||
}
|
||||
|
||||
/** Listener class in the package `javax.servlet` and `javax.servlet.http` */
|
||||
class ServletListenerClass extends Class {
|
||||
// Various listener classes of Java EE such as ServletContextListener. They all have a name ending with the word "Listener".
|
||||
ServletListenerClass() {
|
||||
this.getASupertype*()
|
||||
.getQualifiedName()
|
||||
.regexpMatch([
|
||||
"javax\\.servlet\\.[a-zA-Z]+Listener", "javax\\.servlet\\.http\\.[a-zA-Z]+Listener"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/** The `main` method in `Servlet` and `Action` of the Spring and Struts framework. */
|
||||
class WebComponentMainMethod extends Method {
|
||||
WebComponentMainMethod() {
|
||||
(
|
||||
this.getDeclaringType() instanceof ServletClass or
|
||||
this.getDeclaringType() instanceof ServletFilterClass or
|
||||
this.getDeclaringType() instanceof ServletListenerClass or
|
||||
this.getDeclaringType()
|
||||
.getASupertype*()
|
||||
.hasQualifiedName("org.apache.struts.action", "Action") or // Struts actions
|
||||
this.getDeclaringType()
|
||||
.getASupertype+()
|
||||
.hasQualifiedName("com.opensymphony.xwork2", "ActionSupport") or // Struts 2 actions
|
||||
this.getDeclaringType()
|
||||
.getASupertype+()
|
||||
.hasQualifiedName("org.springframework.web.struts", "ActionSupport") or // Spring/Struts 2 actions
|
||||
this.getDeclaringType()
|
||||
.getASupertype+()
|
||||
.hasQualifiedName("org.springframework.webflow.execution", "Action") // Spring actions
|
||||
) and
|
||||
this instanceof MainMethod and
|
||||
not isTestMethod(this)
|
||||
}
|
||||
}
|
||||
|
||||
from WebComponentMainMethod sm
|
||||
select sm, "Web application has a main method."
|
||||
32
java/ql/src/experimental/Security/CWE/CWE-489/devMode.qhelp
Normal file
32
java/ql/src/experimental/Security/CWE/CWE-489/devMode.qhelp
Normal file
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Turning Apache Struts' development mode configuration on while deploying applications to production environments can lead to remote code execution.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>An application should disable the development mode at the time of deployment.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>The following example shows a `struts.xml` file with `struts.devmode` enabled.</p>
|
||||
|
||||
<sample src="StrutsBad.xml" />
|
||||
|
||||
<p>This can be easily corrected by setting the value of the `struts.devmode` parameter to false.</p>
|
||||
|
||||
<sample src="StrutsGood.xml" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
Apache Struts:
|
||||
<a href="https://struts.apache.org/core-developers/development-mode.html">Struts development mode configuration</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
24
java/ql/src/experimental/Security/CWE/CWE-489/devMode.ql
Normal file
24
java/ql/src/experimental/Security/CWE/CWE-489/devMode.ql
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @name Apache Struts development mode enabled
|
||||
* @description Enabling struts development mode in production environment
|
||||
* can lead to remote code execution.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/struts-development-mode
|
||||
* @tags security
|
||||
* external/cwe/cwe-489
|
||||
*/
|
||||
|
||||
import java
|
||||
import experimental.semmle.code.xml.StrutsXML
|
||||
|
||||
bindingset[path]
|
||||
predicate isLikelyDemoProject(string path) { path.regexpMatch("(?i).*(demo|test|example).*") }
|
||||
|
||||
from ConstantParameter c
|
||||
where
|
||||
c.getNameValue() = "struts.devMode" and
|
||||
c.getValueValue() = "true" and
|
||||
not isLikelyDemoProject(c.getFile().getRelativePath())
|
||||
select c, "Enabling development mode in production environments is dangerous"
|
||||
@@ -0,0 +1,70 @@
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.xquery.XQConnection;
|
||||
import javax.xml.xquery.XQDataSource;
|
||||
import javax.xml.xquery.XQException;
|
||||
import javax.xml.xquery.XQItemType;
|
||||
import javax.xml.xquery.XQPreparedExpression;
|
||||
import javax.xml.xquery.XQResultSequence;
|
||||
import net.sf.saxon.xqj.SaxonXQDataSource;
|
||||
|
||||
public void bad(HttpServletRequest request) throws XQException {
|
||||
String name = request.getParameter("name");
|
||||
XQDataSource ds = new SaxonXQDataSource();
|
||||
XQConnection conn = ds.getConnection();
|
||||
String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password";
|
||||
XQPreparedExpression xqpe = conn.prepareExpression(query);
|
||||
XQResultSequence result = xqpe.executeQuery();
|
||||
while (result.next()){
|
||||
System.out.println(result.getItemAsString(null));
|
||||
}
|
||||
}
|
||||
|
||||
public void bad1(HttpServletRequest request) throws XQException {
|
||||
String name = request.getParameter("name");
|
||||
XQDataSource xqds = new SaxonXQDataSource();
|
||||
String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password";
|
||||
XQConnection conn = xqds.getConnection();
|
||||
XQExpression expr = conn.createExpression();
|
||||
XQResultSequence result = expr.executeQuery(query);
|
||||
while (result.next()){
|
||||
System.out.println(result.getItemAsString(null));
|
||||
}
|
||||
}
|
||||
|
||||
public void bad2(HttpServletRequest request) throws XQException {
|
||||
String name = request.getParameter("name");
|
||||
XQDataSource xqds = new SaxonXQDataSource();
|
||||
XQConnection conn = xqds.getConnection();
|
||||
XQExpression expr = conn.createExpression();
|
||||
//bad code
|
||||
expr.executeCommand(name);
|
||||
}
|
||||
|
||||
public void good(HttpServletRequest request) throws XQException {
|
||||
String name = request.getParameter("name");
|
||||
XQDataSource ds = new SaxonXQDataSource();
|
||||
XQConnection conn = ds.getConnection();
|
||||
String query = "declare variable $name as xs:string external;"
|
||||
+ " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password";
|
||||
XQPreparedExpression xqpe = conn.prepareExpression(query);
|
||||
xqpe.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING));
|
||||
XQResultSequence result = xqpe.executeQuery();
|
||||
while (result.next()){
|
||||
System.out.println(result.getItemAsString(null));
|
||||
}
|
||||
}
|
||||
|
||||
public void good1(HttpServletRequest request) throws XQException {
|
||||
String name = request.getParameter("name");
|
||||
String query = "declare variable $name as xs:string external;"
|
||||
+ " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password";
|
||||
XQDataSource xqds = new SaxonXQDataSource();
|
||||
XQConnection conn = xqds.getConnection();
|
||||
XQExpression expr = conn.createExpression();
|
||||
expr.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING));
|
||||
XQResultSequence result = expr.executeQuery(query);
|
||||
while (result.next()){
|
||||
System.out.println(result.getItemAsString(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>The software uses external input to dynamically construct an XQuery expression which is then used to retrieve data from an XML database.
|
||||
However, the input is not neutralized, or is incorrectly neutralized, which allows an attacker to control the structure of the query.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Use parameterized queries. This will help ensure the program retains control of the query structure.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>The following example compares building a query by string concatenation (bad) vs. using <code>bindString</code> to parameterize the query (good).</p>
|
||||
|
||||
<sample src="XQueryInjection.java" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Balisage:
|
||||
<a href="https://www.balisage.net/Proceedings/vol7/html/Vlist02/BalisageVol7-Vlist02.html">XQuery Injection</a>.</li>
|
||||
|
||||
|
||||
|
||||
<!-- LocalWords: CWE
|
||||
-->
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @name XQuery query built from user-controlled sources
|
||||
* @description Building an XQuery query from user-controlled sources is vulnerable to insertion of
|
||||
* malicious XQuery code by the user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/xquery-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-652
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import XQueryInjectionLib
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration tracing flow from remote sources, through an XQuery parser, to its eventual execution.
|
||||
*/
|
||||
class XQueryInjectionConfig extends TaintTracking::Configuration {
|
||||
XQueryInjectionConfig() { this = "XQueryInjectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink.asExpr() = any(XQueryPreparedExecuteCall xpec).getPreparedExpression() or
|
||||
sink.asExpr() = any(XQueryExecuteCall xec).getExecuteQueryArgument() or
|
||||
sink.asExpr() = any(XQueryExecuteCommandCall xecc).getExecuteCommandArgument()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint from the input `pred` to a `prepareExpression` call flows to the returned prepared expression `succ`.
|
||||
*/
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(XQueryParserCall parser | pred.asExpr() = parser.getInput() and succ.asExpr() = parser)
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, XQueryInjectionConfig conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "XQuery query might include code from $@.", source.getNode(),
|
||||
"this user input"
|
||||
@@ -0,0 +1,68 @@
|
||||
import java
|
||||
|
||||
/** A call to `XQConnection.prepareExpression`. */
|
||||
class XQueryParserCall extends MethodAccess {
|
||||
XQueryParserCall() {
|
||||
exists(Method m |
|
||||
this.getMethod() = m and
|
||||
m.getDeclaringType()
|
||||
.getASourceSupertype*()
|
||||
.hasQualifiedName("javax.xml.xquery", "XQConnection") and
|
||||
m.hasName("prepareExpression")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first parameter of the `prepareExpression` method, which provides
|
||||
* the string, stream or reader to be compiled into a prepared expression.
|
||||
*/
|
||||
Expr getInput() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** A call to `XQPreparedExpression.executeQuery`. */
|
||||
class XQueryPreparedExecuteCall extends MethodAccess {
|
||||
XQueryPreparedExecuteCall() {
|
||||
exists(Method m |
|
||||
this.getMethod() = m and
|
||||
m.hasName("executeQuery") and
|
||||
m.getDeclaringType()
|
||||
.getASourceSupertype*()
|
||||
.hasQualifiedName("javax.xml.xquery", "XQPreparedExpression")
|
||||
)
|
||||
}
|
||||
|
||||
/** Return this prepared expression. */
|
||||
Expr getPreparedExpression() { result = this.getQualifier() }
|
||||
}
|
||||
|
||||
/** A call to `XQExpression.executeQuery`. */
|
||||
class XQueryExecuteCall extends MethodAccess {
|
||||
XQueryExecuteCall() {
|
||||
exists(Method m |
|
||||
this.getMethod() = m and
|
||||
m.hasName("executeQuery") and
|
||||
m.getDeclaringType()
|
||||
.getASourceSupertype*()
|
||||
.hasQualifiedName("javax.xml.xquery", "XQExpression")
|
||||
)
|
||||
}
|
||||
|
||||
/** Return this execute query argument. */
|
||||
Expr getExecuteQueryArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** A call to `XQExpression.executeCommand`. */
|
||||
class XQueryExecuteCommandCall extends MethodAccess {
|
||||
XQueryExecuteCommandCall() {
|
||||
exists(Method m |
|
||||
this.getMethod() = m and
|
||||
m.hasName("executeCommand") and
|
||||
m.getDeclaringType()
|
||||
.getASourceSupertype*()
|
||||
.hasQualifiedName("javax.xml.xquery", "XQExpression")
|
||||
)
|
||||
}
|
||||
|
||||
/** Return this execute command argument. */
|
||||
Expr getExecuteCommandArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
40
java/ql/src/experimental/semmle/code/xml/StrutsXML.qll
Normal file
40
java/ql/src/experimental/semmle/code/xml/StrutsXML.qll
Normal file
@@ -0,0 +1,40 @@
|
||||
import java
|
||||
|
||||
/**
|
||||
* A deployment descriptor file, typically called `struts.xml`.
|
||||
*/
|
||||
class StrutsXMLFile extends XMLFile {
|
||||
StrutsXMLFile() {
|
||||
count(XMLElement e | e = this.getAChild()) = 1 and
|
||||
this.getAChild().getName() = "struts"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An XML element in a `StrutsXMLFile`.
|
||||
*/
|
||||
class StrutsXMLElement extends XMLElement {
|
||||
StrutsXMLElement() { this.getFile() instanceof StrutsXMLFile }
|
||||
|
||||
/**
|
||||
* Gets the value for this element, with leading and trailing whitespace trimmed.
|
||||
*/
|
||||
string getValue() { result = allCharactersString().trim() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `<constant>` element in a `StrutsXMLFile`.
|
||||
*/
|
||||
class ConstantParameter extends StrutsXMLElement {
|
||||
ConstantParameter() { this.getName() = "constant" }
|
||||
|
||||
/**
|
||||
* Gets the value of the `name` attribute of this `<constant>`.
|
||||
*/
|
||||
string getNameValue() { result = getAttributeValue("name") }
|
||||
|
||||
/**
|
||||
* Gets the value of the `value` attribute of this `<constant>`.
|
||||
*/
|
||||
string getValueValue() { result = getAttributeValue("value") }
|
||||
}
|
||||
Reference in New Issue
Block a user