Merge branch 'main' into polyQhelp

This commit is contained in:
erik-krogh
2023-05-21 22:17:06 +02:00
191 changed files with 4780 additions and 3938 deletions

View File

@@ -27,7 +27,7 @@ abstract class MetadataExtractor extends string {
abstract predicate hasMetadata(
DataFlow::ParameterNode e, string package, string type, boolean subtypes, string name,
string signature, int input
string signature, int input, string parameterName
);
}
@@ -167,14 +167,15 @@ class FrameworkModeMetadataExtractor extends MetadataExtractor {
override predicate hasMetadata(
Endpoint e, string package, string type, boolean subtypes, string name, string signature,
int input
int input, string parameterName
) {
exists(Callable callable |
e.asParameter() = callable.getParameter(input) and
package = callable.getDeclaringType().getPackage().getName() and
type = callable.getDeclaringType().getErasure().(RefType).nestedName() and
subtypes = this.considerSubtypes(callable) and
name = e.toString() and
name = callable.getName() and
parameterName = e.asParameter().getName() and
signature = ExternalFlow::paramsString(callable)
)
}

View File

@@ -17,7 +17,7 @@ private import AutomodelSharedUtil
from
Endpoint endpoint, string message, MetadataExtractor meta, string package, string type,
boolean subtypes, string name, string signature, int input
boolean subtypes, string name, string signature, int input, string parameterName
where
not exists(CharacteristicsImpl::UninterestingToModelCharacteristic u |
u.appliesToEndpoint(endpoint)
@@ -28,7 +28,7 @@ where
// overlap between our detected sinks and the pre-existing modeling. We assume that, if a sink has already been
// modeled in a MaD model, then it doesn't belong to any additional sink types, and we don't need to reexamine it.
not CharacteristicsImpl::isSink(endpoint, _) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input, parameterName) and
// The message is the concatenation of all sink types for which this endpoint is known neither to be a sink nor to be
// a non-sink, and we surface only endpoints that have at least one such sink type.
message =
@@ -39,7 +39,7 @@ where
sinkType, ", "
)
select endpoint,
message + "\nrelated locations: $@, $@." + "\nmetadata: $@, $@, $@, $@, $@, $@.", //
message + "\nrelated locations: $@, $@." + "\nmetadata: $@, $@, $@, $@, $@, $@, $@.", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, MethodDoc()), "MethodDoc", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, ClassDoc()), "ClassDoc", //
package.(DollarAtString), "package", //
@@ -47,4 +47,5 @@ select endpoint,
subtypes.toString().(DollarAtString), "subtypes", //
name.(DollarAtString), "name", //
signature.(DollarAtString), "signature", //
input.toString().(DollarAtString), "input" //
input.toString().(DollarAtString), "input", //
parameterName.(DollarAtString), "parameterName" //

View File

@@ -15,7 +15,7 @@ private import AutomodelSharedUtil
from
Endpoint endpoint, EndpointCharacteristic characteristic, float confidence, string message,
MetadataExtractor meta, string package, string type, boolean subtypes, string name,
string signature, int input
string signature, int input, string parameterName
where
characteristic.appliesToEndpoint(endpoint) and
confidence >= SharedCharacteristics::highConfidence() and
@@ -23,7 +23,7 @@ where
// Exclude endpoints that have contradictory endpoint characteristics, because we only want examples we're highly
// certain about in the prompt.
not erroneousEndpoints(endpoint, _, _, _, _, false) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input, parameterName) and
// It's valid for a node to satisfy the logic for both `isSink` and `isSanitizer`, but in that case it will be
// treated by the actual query as a sanitizer, since the final logic is something like
// `isSink(n) and not isSanitizer(n)`. We don't want to include such nodes as negative examples in the prompt, because
@@ -36,7 +36,7 @@ where
) and
message = characteristic
select endpoint,
message + "\nrelated locations: $@, $@." + "\nmetadata: $@, $@, $@, $@, $@, $@.", //
message + "\nrelated locations: $@, $@." + "\nmetadata: $@, $@, $@, $@, $@, $@, $@.", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, MethodDoc()), "MethodDoc", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, ClassDoc()), "ClassDoc", //
package.(DollarAtString), "package", //
@@ -44,4 +44,5 @@ select endpoint,
subtypes.toString().(DollarAtString), "subtypes", //
name.(DollarAtString), "name", //
signature.(DollarAtString), "signature", //
input.toString().(DollarAtString), "input" //
input.toString().(DollarAtString), "input", //
parameterName.(DollarAtString), "parameterName" //

View File

@@ -14,16 +14,16 @@ private import AutomodelSharedUtil
from
Endpoint endpoint, SinkType sinkType, MetadataExtractor meta, string package, string type,
boolean subtypes, string name, string signature, int input
boolean subtypes, string name, string signature, int input, string parameterName
where
// Exclude endpoints that have contradictory endpoint characteristics, because we only want examples we're highly
// certain about in the prompt.
not erroneousEndpoints(endpoint, _, _, _, _, false) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input, parameterName) and
// Extract positive examples of sinks belonging to the existing ATM query configurations.
CharacteristicsImpl::isKnownSink(endpoint, sinkType)
select endpoint,
sinkType + "\nrelated locations: $@, $@." + "\nmetadata: $@, $@, $@, $@, $@, $@.", //
sinkType + "\nrelated locations: $@, $@." + "\nmetadata: $@, $@, $@, $@, $@, $@, $@.", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, MethodDoc()), "MethodDoc", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, ClassDoc()), "ClassDoc", //
package.(DollarAtString), "package", //
@@ -31,4 +31,5 @@ select endpoint,
subtypes.toString().(DollarAtString), "subtypes", //
name.(DollarAtString), "name", //
signature.(DollarAtString), "signature", //
input.toString().(DollarAtString), "input" //
input.toString().(DollarAtString), "input", //
parameterName.(DollarAtString), "parameterName" //

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Experimental sinks for the query "Resolving XML external entity in user-controlled data" (`java/xxe`) have been promoted to the main query pack. These sinks were originally [submitted as part of an experimental query by @haby0](https://github.com/github/codeql/pull/6564).

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The queries `java/xxe` and `java/xxe-local` now recognize the second argument of calls to `XPath.evaluate` as a sink.

View File

@@ -1,85 +0,0 @@
import java.beans.XMLDecoder;
import java.io.BufferedReader;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.apache.commons.digester3.Digester;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class XxeController {
@PostMapping(value = "xxe1")
public void bad1(HttpServletRequest request, HttpServletResponse response) throws Exception {
ServletInputStream servletInputStream = request.getInputStream();
Digester digester = new Digester();
digester.parse(servletInputStream);
}
@PostMapping(value = "xxe2")
public void bad2(HttpServletRequest request) throws Exception {
BufferedReader br = request.getReader();
String str = "";
StringBuilder listString = new StringBuilder();
while ((str = br.readLine()) != null) {
listString.append(str).append("\n");
}
Document document = DocumentHelper.parseText(listString.toString());
}
@PostMapping(value = "xxe3")
public void bad3(HttpServletRequest request) throws Exception {
ServletInputStream servletInputStream = request.getInputStream();
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();
Validator validator = schema.newValidator();
StreamSource source = new StreamSource(servletInputStream);
validator.validate(source);
}
@PostMapping(value = "xxe4")
public void bad4(HttpServletRequest request) throws Exception {
ServletInputStream servletInputStream = request.getInputStream();
XMLDecoder xmlDecoder = new XMLDecoder(servletInputStream);
xmlDecoder.readObject();
}
@PostMapping(value = "good1")
public void good1(HttpServletRequest request, HttpServletResponse response) throws Exception {
BufferedReader br = request.getReader();
String str = "";
StringBuilder listString = new StringBuilder();
while ((str = br.readLine()) != null) {
listString.append(str);
}
Digester digester = new Digester();
digester.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
digester.setFeature("http://xml.org/sax/features/external-general-entities", false);
digester.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
digester.parse(listString.toString());
}
@PostMapping(value = "good2")
public void good2(HttpServletRequest request, HttpServletResponse response) throws Exception {
BufferedReader br = request.getReader();
String str = "";
StringBuilder listString = new StringBuilder();
while ((str = br.readLine()) != null) {
listString.append(str).append("\n");
}
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();
Validator validator = schema.newValidator();
validator.setProperty("http://javax.xml.XMLConstants/property/accessExternalDTD", "");
validator.setProperty("http://javax.xml.XMLConstants/property/accessExternalSchema", "");
StreamSource source = new StreamSource(listString.toString());
validator.validate(source);
}
}

View File

@@ -1,67 +0,0 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Parsing untrusted XML files with a weakly configured XML parser may lead to an XML External Entity (XXE) attack. This type of attack
uses external entity references to access arbitrary files on a system, carry out denial of service, or server side
request forgery. Even when the result of parsing is not returned to the user, out-of-band
data retrieval techniques may allow attackers to steal sensitive data. Denial of services can also be
carried out in this situation.
</p>
<p>
There are many XML parsers for Java, and most of them are vulnerable to XXE because their default settings enable parsing of
external entities. This query currently identifies vulnerable XML parsing from the following parsers: <code>javax.xml.validation.Validator</code>,
<code>org.dom4j.DocumentHelper</code>, <code>org.rundeck.api.parser.ParserHelper</code>, <code>org.apache.commons.digester3.Digester</code>,
<code>org.apache.commons.digester.Digester</code>, <code>org.apache.tomcat.util.digester.Digester</code>, <code>java.beans.XMLDecoder</code>.
</p>
</overview>
<recommendation>
<p>
The best way to prevent XXE attacks is to disable the parsing of any Document Type Declarations (DTDs) in untrusted data.
If this is not possible you should disable the parsing of external general entities and external parameter entities.
This improves security but the code will still be at risk of denial of service and server side request forgery attacks.
Protection against denial of service attacks may also be implemented by setting entity expansion limits, which is done
by default in recent JDK and JRE implementations.
</p>
</recommendation>
<example>
<p>
The following bad examples parses the xml data entered by the user under an unsafe configuration, which is inherently insecure and may cause xml entity injection.
In good examples, the security configuration is carried out, for example: Disable DTD to protect the program from XXE attacks.
</p>
<sample src="XXE.java" />
</example>
<references>
<li>
OWASP vulnerability description:
<a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">XML External Entity (XXE) Processing</a>.
</li>
<li>
OWASP guidance on parsing xml files:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java">XXE Prevention Cheat Sheet</a>.
</li>
<li>
Paper by Timothy Morgen:
<a href="https://research.nccgroup.com/2014/05/19/xml-schema-dtd-and-entity-attacks-a-compendium-of-known-techniques/">XML Schema, DTD, and Entity Attacks</a>
</li>
<li>
Out-of-band data retrieval: Timur Yunusov &amp; Alexey Osipov, Black hat EU 2013:
<a href="https://www.slideshare.net/qqlan/bh-ready-v4">XML Out-Of-Band Data Retrieval</a>.
</li>
<li>
Denial of service attack (Billion laughs):
<a href="https://en.wikipedia.org/wiki/Billion_laughs">Billion Laughs.</a>
</li>
<li>
The Java Tutorials:
<a href="https://docs.oracle.com/javase/tutorial/jaxp/limits/limits.html">Processing Limit Definitions.</a>
</li>
</references>
</qhelp>

View File

@@ -1,32 +0,0 @@
/**
* @name Resolving XML external entity in user-controlled data (experimental sinks)
* @description Parsing user-controlled XML documents and allowing expansion of external entity
* references may lead to disclosure of confidential data or denial of service.
* (note this version differs from query `java/xxe` by including support for additional possibly-vulnerable XML parsers)
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/xxe-with-experimental-sinks
* @tags security
* experimental
* external/cwe/cwe-611
*/
import java
import XXELib
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import XxeFlow::PathGraph
module XxeConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeXxeSink }
}
module XxeFlow = TaintTracking::Global<XxeConfig>;
from XxeFlow::PathNode source, XxeFlow::PathNode sink
where XxeFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Unsafe parsing of XML file from $@.", source.getNode(),
"user input"

View File

@@ -1,246 +0,0 @@
import java
import semmle.code.java.dataflow.DataFlow3
import semmle.code.java.dataflow.DataFlow4
import semmle.code.java.dataflow.DataFlow5
import semmle.code.java.security.XmlParsers
private import semmle.code.java.dataflow.SSA
/** A data flow sink for untrusted user input used to insecure xml parse. */
class UnsafeXxeSink extends DataFlow::ExprNode {
UnsafeXxeSink() {
exists(XmlParserCall parse |
parse.getSink() = this.getExpr() and
not parse.isSafe()
)
}
}
/** The class `org.rundeck.api.parser.ParserHelper`. */
class ParserHelper extends RefType {
ParserHelper() { this.hasQualifiedName("org.rundeck.api.parser", "ParserHelper") }
}
/** A call to `ParserHelper.loadDocument`. */
class ParserHelperLoadDocument extends XmlParserCall {
ParserHelperLoadDocument() {
exists(Method m |
this.getMethod() = m and
m.getDeclaringType() instanceof ParserHelper and
m.hasName("loadDocument")
)
}
override Expr getSink() { result = this.getArgument(0) }
override predicate isSafe() { none() }
}
/** The class `javax.xml.validation.Validator`. */
class Validator extends RefType {
Validator() { this.hasQualifiedName("javax.xml.validation", "Validator") }
}
/** A call to `Validator.validate`. */
class ValidatorValidate extends XmlParserCall {
ValidatorValidate() {
exists(Method m |
this.getMethod() = m and
m.getDeclaringType() instanceof Validator and
m.hasName("validate")
)
}
override Expr getSink() { result = this.getArgument(0) }
override predicate isSafe() { SafeValidatorFlow::flowToExpr(this.getQualifier()) }
}
/** A `ParserConfig` specific to `Validator`. */
class ValidatorConfig extends TransformerConfig {
ValidatorConfig() {
exists(Method m |
this.getMethod() = m and
m.getDeclaringType() instanceof Validator and
m.hasName("setProperty")
)
}
}
/** A safely configured `Validator`. */
class SafeValidator extends VarAccess {
SafeValidator() {
exists(Variable v | v = this.getVariable() |
exists(ValidatorConfig config | config.getQualifier() = v.getAnAccess() |
config.disables(configAccessExternalDtd())
) and
exists(ValidatorConfig config | config.getQualifier() = v.getAnAccess() |
config.disables(configAccessExternalSchema())
)
)
}
}
private module SafeValidatorFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeValidator }
predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getQualifier() and
ma.getMethod().getDeclaringType() instanceof Validator
)
}
int fieldFlowBranchLimit() { result = 0 }
}
private module SafeValidatorFlow = DataFlow::Global<SafeValidatorFlowConfig>;
/**
* The classes `org.apache.commons.digester3.Digester`, `org.apache.commons.digester.Digester` or `org.apache.tomcat.util.digester.Digester`.
*/
class Digester extends RefType {
Digester() {
this.hasQualifiedName([
"org.apache.commons.digester3", "org.apache.commons.digester",
"org.apache.tomcat.util.digester"
], "Digester")
}
}
/** A call to `Digester.parse`. */
class DigesterParse extends XmlParserCall {
DigesterParse() {
exists(Method m |
this.getMethod() = m and
m.getDeclaringType() instanceof Digester and
m.hasName("parse")
)
}
override Expr getSink() { result = this.getArgument(0) }
override predicate isSafe() { SafeDigesterFlow::flowToExpr(this.getQualifier()) }
}
/** A `ParserConfig` that is specific to `Digester`. */
class DigesterConfig extends ParserConfig {
DigesterConfig() {
exists(Method m |
m = this.getMethod() and
m.getDeclaringType() instanceof Digester and
m.hasName("setFeature")
)
}
}
/**
* A safely configured `Digester`.
*/
class SafeDigester extends VarAccess {
SafeDigester() {
exists(Variable v | v = this.getVariable() |
exists(DigesterConfig config | config.getQualifier() = v.getAnAccess() |
config.enables(singleSafeConfig())
)
or
exists(DigesterConfig config | config.getQualifier() = v.getAnAccess() |
config
.disables(any(ConstantStringExpr s |
s.getStringValue() = "http://xml.org/sax/features/external-general-entities"
))
) and
exists(DigesterConfig config | config.getQualifier() = v.getAnAccess() |
config
.disables(any(ConstantStringExpr s |
s.getStringValue() = "http://xml.org/sax/features/external-parameter-entities"
))
) and
exists(DigesterConfig config | config.getQualifier() = v.getAnAccess() |
config
.disables(any(ConstantStringExpr s |
s.getStringValue() =
"http://apache.org/xml/features/nonvalidating/load-external-dtd"
))
)
)
}
}
private module SafeDigesterFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeDigester }
predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getQualifier() and ma.getMethod().getDeclaringType() instanceof Digester
)
}
int fieldFlowBranchLimit() { result = 0 }
}
private module SafeDigesterFlow = DataFlow::Global<SafeDigesterFlowConfig>;
/** The class `java.beans.XMLDecoder`. */
class XmlDecoder extends RefType {
XmlDecoder() { this.hasQualifiedName("java.beans", "XMLDecoder") }
}
/** DEPRECATED: Alias for XmlDecoder */
deprecated class XMLDecoder = XmlDecoder;
/** A call to `XMLDecoder.readObject`. */
class XmlDecoderReadObject extends XmlParserCall {
XmlDecoderReadObject() {
exists(Method m |
this.getMethod() = m and
m.getDeclaringType() instanceof XmlDecoder and
m.hasName("readObject")
)
}
override Expr getSink() { result = this.getQualifier() }
override predicate isSafe() { none() }
}
/** DEPRECATED: Alias for XmlDecoderReadObject */
deprecated class XMLDecoderReadObject = XmlDecoderReadObject;
private predicate constantStringExpr(Expr e, string val) {
e.(CompileTimeConstantExpr).getStringValue() = val
or
exists(SsaExplicitUpdate v, Expr src |
e = v.getAUse() and
src = v.getDefiningExpr().(VariableAssign).getSource() and
constantStringExpr(src, val)
)
}
/** A call to `SAXTransformerFactory.newTransformerHandler`. */
class SaxTransformerFactoryNewTransformerHandler extends XmlParserCall {
SaxTransformerFactoryNewTransformerHandler() {
exists(Method m |
this.getMethod() = m and
m.getDeclaringType().hasQualifiedName("javax.xml.transform.sax", "SAXTransformerFactory") and
m.hasName("newTransformerHandler")
)
}
override Expr getSink() { result = this.getArgument(0) }
override predicate isSafe() { SafeTransformerFactoryFlow::flowToExpr(this.getQualifier()) }
}
/** DEPRECATED: Alias for SaxTransformerFactoryNewTransformerHandler */
deprecated class SAXTransformerFactoryNewTransformerHandler =
SaxTransformerFactoryNewTransformerHandler;
/** An expression that always has the same string value. */
private class ConstantStringExpr extends Expr {
string value;
ConstantStringExpr() { constantStringExpr(this, value) }
/** Get the string value of this expression. */
string getStringValue() { result = value }
}

View File

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

View File

@@ -1,34 +0,0 @@
/**
* @name Resolving XML external entity from a local source (experimental sinks)
* @description Parsing user-controlled XML documents and allowing expansion of external entity
* references may lead to disclosure of confidential data or denial of service.
* (note this version differs from query `java/xxe` by including support for additional possibly-vulnerable XML parsers,
* and by considering local information sources dangerous (e.g. environment variables) in addition to the remote sources
* considered by the normal `java/xxe` query)
* @kind path-problem
* @problem.severity recommendation
* @precision medium
* @id java/xxe-local-experimental-sinks
* @tags security
* experimental
* external/cwe/cwe-611
*/
import java
import XXELib
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import XxeLocalFlow::PathGraph
module XxeLocalConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeXxeSink }
}
module XxeLocalFlow = TaintTracking::Global<XxeLocalConfig>;
from XxeLocalFlow::PathNode source, XxeLocalFlow::PathNode sink
where XxeLocalFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Unsafe parsing of XML file from $@.", source.getNode(),
"user input"