Merge remote-tracking branch 'upstream/main' into JsonHijacking

This commit is contained in:
haby0
2021-03-24 15:52:01 +08:00
1762 changed files with 46263 additions and 15293 deletions

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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") }
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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")
}
}

View File

@@ -0,0 +1,4 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<include src="SpringViewManipulation.qhelp" />
</qhelp>

View File

@@ -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"

View File

@@ -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);
}
}

View File

@@ -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
}
}

View File

@@ -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>

View File

@@ -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"

View File

@@ -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))
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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"

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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() {}
}

View File

@@ -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>

View File

@@ -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"

View 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.
}

View 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>

View 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."

View 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>

View 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>

View 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
}

View File

@@ -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.
}

View File

@@ -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>

View File

@@ -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."

View 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>

View 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"

View File

@@ -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));
}
}

View File

@@ -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>

View File

@@ -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"

View File

@@ -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) }
}

View 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") }
}