Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 9 additions & 29 deletions java/ql/src/Security/CWE/CWE-022/ZipSlip.ql
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import semmle.code.java.dataflow.SSA
import semmle.code.java.dataflow.TaintTracking
import DataFlow
import PathGraph
private import semmle.code.java.dataflow.ExternalFlow

/**
* A method that returns the name of an archive entry.
Expand All @@ -33,34 +34,6 @@ class ArchiveEntryNameMethod extends Method {
}
}

/**
* An expression that will be treated as the destination of a write.
*/
class WrittenFileName extends Expr {
WrittenFileName() {
// Constructors that write to their first argument.
exists(ConstructorCall ctr | this = ctr.getArgument(0) |
exists(Class c | ctr.getConstructor() = c.getAConstructor() |
c.hasQualifiedName("java.io", "FileOutputStream") or
c.hasQualifiedName("java.io", "RandomAccessFile") or
c.hasQualifiedName("java.io", "FileWriter")
)
)
or
// Methods that write to their n'th argument
exists(MethodAccess call, int n | this = call.getArgument(n) |
call.getMethod().getDeclaringType().hasQualifiedName("java.nio.file", "Files") and
(
call.getMethod().getName().regexpMatch("new.*Reader|newOutputStream|create.*") and n = 0
or
call.getMethod().hasName("copy") and n = 1
or
call.getMethod().hasName("move") and n = 1
)
)
}
}

/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String`,
* `File`, and `Path`.
Expand Down Expand Up @@ -151,7 +124,7 @@ class ZipSlipConfiguration extends TaintTracking::Configuration {
source.asExpr().(MethodAccess).getMethod() instanceof ArchiveEntryNameMethod
}

override predicate isSink(Node sink) { sink.asExpr() instanceof WrittenFileName }
override predicate isSink(Node sink) { sink instanceof FileCreationSink }

override predicate isAdditionalTaintStep(Node n1, Node n2) {
filePathStep(n1, n2) or fileTaintStep(n1, n2)
Expand All @@ -173,6 +146,13 @@ class ZipSlipConfiguration extends TaintTracking::Configuration {
}
}

/**
* A sink that represents a file creation, such as a file write, copy or move operation.
*/
private class FileCreationSink extends DataFlow::Node {
FileCreationSink() { sinkNode(this, "create-file") }
}

from PathNode source, PathNode sink
where any(ZipSlipConfiguration c).hasFlowPath(source, sink)
select source.getNode(), source, sink,
Expand Down
29 changes: 10 additions & 19 deletions java/ql/src/Security/CWE/CWE-094/InsecureBeanValidation.ql
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
private import semmle.code.java.dataflow.ExternalFlow

/**
* A message interpolator Type that perform Expression Language (EL) evaluations
Expand Down Expand Up @@ -50,19 +51,6 @@ class SetMessageInterpolatorCall extends MethodAccess {
predicate isSafe() { not this.getAnArgument().getType() instanceof ELMessageInterpolatorType }
}

/**
* A method named `buildConstraintViolationWithTemplate` declared on a subtype
* of `javax.validation.ConstraintValidatorContext`.
*/
class BuildConstraintViolationWithTemplateMethod extends Method {
BuildConstraintViolationWithTemplateMethod() {
this.getDeclaringType()
.getASupertype*()
.hasQualifiedName("javax.validation", "ConstraintValidatorContext") and
this.hasName("buildConstraintViolationWithTemplate")
}
}

/**
* Taint tracking BeanValidationConfiguration describing the flow of data from user input
* to the argument of a method that builds constraint error messages.
Expand All @@ -72,12 +60,15 @@ class BeanValidationConfig extends TaintTracking::Configuration {

override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }

override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
ma.getMethod() instanceof BuildConstraintViolationWithTemplateMethod and
sink.asExpr() = ma.getArgument(0)
)
}
override predicate isSink(DataFlow::Node sink) { sink instanceof BeanValidationSink }
}

/**
* A bean validation sink, such as method `buildConstraintViolationWithTemplate`
* declared on a subtype of `javax.validation.ConstraintValidatorContext`.
*/
private class BeanValidationSink extends DataFlow::Node {
BeanValidationSink() { sinkNode(this, "bean-validation") }
}

from BeanValidationConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink
Expand Down
17 changes: 9 additions & 8 deletions java/ql/src/Security/CWE/CWE-297/UnsafeHostnameVerification.ql
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.Encryption
import DataFlow::PathGraph
private import semmle.code.java.dataflow.ExternalFlow

/**
* Holds if `m` always returns `true` ignoring any exceptional flow.
Expand Down Expand Up @@ -49,14 +50,7 @@ class TrustAllHostnameVerifierConfiguration extends DataFlow::Configuration {
source.asExpr().(ClassInstanceExpr).getConstructedType() instanceof TrustAllHostnameVerifier
}

override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, Method m |
(m instanceof SetDefaultHostnameVerifierMethod or m instanceof SetHostnameVerifierMethod) and
ma.getMethod() = m
|
ma.getArgument(0) = sink.asExpr()
)
}
override predicate isSink(DataFlow::Node sink) { sink instanceof HostnameVerifierSink }

override predicate isBarrier(DataFlow::Node barrier) {
// ignore nodes that are in functions that intentionally disable hostname verification
Expand Down Expand Up @@ -84,6 +78,13 @@ class TrustAllHostnameVerifierConfiguration extends DataFlow::Configuration {
}
}

/**
* A sink that sets the `HostnameVerifier` on `HttpsURLConnection`.
*/
private class HostnameVerifierSink extends DataFlow::Node {
HostnameVerifierSink() { sinkNode(this, "set-hostname-verifier") }
}

bindingset[result]
private string getAFlagName() {
result
Expand Down
38 changes: 16 additions & 22 deletions java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.frameworks.Networking
import DataFlow::PathGraph
private import semmle.code.java.dataflow.ExternalFlow

class HTTPString extends StringLiteral {
HTTPString() {
class HttpString extends StringLiteral {
HttpString() {
// Avoid matching "https" here.
exists(string s | this.getRepresentedString() = s |
(
Expand All @@ -30,26 +31,12 @@ class HTTPString extends StringLiteral {
}
}

class URLOpenMethod extends Method {
URLOpenMethod() {
this.getDeclaringType().getQualifiedName() = "java.net.URL" and
(
this.getName() = "openConnection" or
this.getName() = "openStream"
)
}
}
class HttpStringToUrlOpenMethodFlowConfig extends TaintTracking::Configuration {
HttpStringToUrlOpenMethodFlowConfig() { this = "HttpsUrls::HttpStringToUrlOpenMethodFlowConfig" }

class HTTPStringToURLOpenMethodFlowConfig extends TaintTracking::Configuration {
HTTPStringToURLOpenMethodFlowConfig() { this = "HttpsUrls::HTTPStringToURLOpenMethodFlowConfig" }
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof HttpString }

override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof HTTPString }

override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess m |
sink.asExpr() = m.getQualifier() and m.getMethod() instanceof URLOpenMethod
)
}
override predicate isSink(DataFlow::Node sink) { sink instanceof UrlOpenSink }

override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(UrlConstructorCall u |
Expand All @@ -63,10 +50,17 @@ class HTTPStringToURLOpenMethodFlowConfig extends TaintTracking::Configuration {
}
}

from DataFlow::PathNode source, DataFlow::PathNode sink, MethodAccess m, HTTPString s
/**
* A sink that represents a URL opening method call, such as a call to `java.net.URL.openConnection()`.
*/
private class UrlOpenSink extends DataFlow::Node {
UrlOpenSink() { sinkNode(this, "open-url") }
}

from DataFlow::PathNode source, DataFlow::PathNode sink, MethodAccess m, HttpString s
where
source.getNode().asExpr() = s and
sink.getNode().asExpr() = m.getQualifier() and
any(HTTPStringToURLOpenMethodFlowConfig c).hasFlowPath(source, sink)
any(HttpStringToUrlOpenMethodFlowConfig c).hasFlowPath(source, sink)
select m, source, sink, "URL may have been constructed with HTTP protocol, using $@.", s,
"this source"
30 changes: 29 additions & 1 deletion java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ private module Frameworks {
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.guava.Guava
private import semmle.code.java.security.ResponseSplitting
private import semmle.code.java.security.XSS
private import semmle.code.java.security.LdapInjection
}

Expand Down Expand Up @@ -186,7 +188,33 @@ private predicate sourceModelCsv(string row) {
]
}

private predicate sinkModelCsv(string row) { none() }
private predicate sinkModelCsv(string row) {
row =
[
// Open URL
"java.net;URL;false;openConnection;;;Argument[-1];open-url",
"java.net;URL;false;openStream;;;Argument[-1];open-url",
// Create file
"java.io;FileOutputStream;false;FileOutputStream;;;Argument[0];create-file",
"java.io;RandomAccessFile;false;RandomAccessFile;;;Argument[0];create-file",
"java.io;FileWriter;false;FileWriter;;;Argument[0];create-file",
"java.nio.file;Files;false;move;;;Argument[1];create-file",
"java.nio.file;Files;false;copy;;;Argument[1];create-file",
"java.nio.file;Files;false;newOutputStream;;;Argument[0];create-file",
"java.nio.file;Files;false;newBufferedReader;;;Argument[0];create-file",
"java.nio.file;Files;false;createDirectory;;;Argument[0];create-file",
"java.nio.file;Files;false;createFile;;;Argument[0];create-file",
"java.nio.file;Files;false;createLink;;;Argument[0];create-file",
"java.nio.file;Files;false;createSymbolicLink;;;Argument[0];create-file",
"java.nio.file;Files;false;createTempDirectory;;;Argument[0];create-file",
"java.nio.file;Files;false;createTempFile;;;Argument[0];create-file",
// Bean validation
"javax.validation;ConstraintValidatorContext;true;buildConstraintViolationWithTemplate;;;Argument[0];bean-validation",
// Set hostname
"javax.net.ssl;HttpsURLConnection;true;setDefaultHostnameVerifier;;;Argument[0];set-hostname-verifier",
"javax.net.ssl;HttpsURLConnection;true;setHostnameVerifier;;;Argument[0];set-hostname-verifier"
]
}

private predicate summaryModelCsv(string row) {
row =
Expand Down
43 changes: 17 additions & 26 deletions java/ql/src/semmle/code/java/security/ResponseSplitting.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,32 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.Servlets
import semmle.code.java.frameworks.JaxWS
private import semmle.code.java.dataflow.ExternalFlow

/** A sink that is vulnerable to an HTTP header splitting attack. */
abstract class HeaderSplittingSink extends DataFlow::Node { }

/** A source that introduces data considered safe to use by a header splitting source. */
abstract class SafeHeaderSplittingSource extends DataFlow::Node {
SafeHeaderSplittingSource() { this instanceof RemoteFlowSource }
private class DefaultHeaderSplittingSink extends HeaderSplittingSink {
DefaultHeaderSplittingSink() { sinkNode(this, "header-splitting") }
}

/** A sink that identifies a Java Servlet or JaxWs method that is vulnerable to an HTTP header splitting attack. */
private class ServletHeaderSplittingSink extends HeaderSplittingSink {
ServletHeaderSplittingSink() {
exists(ResponseAddCookieMethod m, MethodAccess ma |
ma.getMethod() = m and
this.asExpr() = ma.getArgument(0)
)
or
exists(ResponseAddHeaderMethod m, MethodAccess ma |
ma.getMethod() = m and
this.asExpr() = ma.getAnArgument()
)
or
exists(ResponseSetHeaderMethod m, MethodAccess ma |
ma.getMethod() = m and
this.asExpr() = ma.getAnArgument()
)
or
exists(JaxRsResponseBuilder builder, Method m |
m = builder.getAMethod() and m.getName() = "header"
|
this.asExpr() = m.getAReference().getArgument(1)
)
private class HeaderSplittingSinkModel extends SinkModelCsv {
override predicate row(string row) {
row =
[
"javax.servlet.http;HttpServletResponse;false;addCookie;;;Argument[0];header-splitting",
"javax.servlet.http;HttpServletResponse;false;addHeader;;;Argument[0..1];header-splitting",
"javax.servlet.http;HttpServletResponse;false;setHeader;;;Argument[0..1];header-splitting",
"javax.ws.rs.core;ResponseBuilder;false;header;;;Argument[1];header-splitting"
]
}
}

/** A source that introduces data considered safe to use by a header splitting source. */
abstract class SafeHeaderSplittingSource extends DataFlow::Node {
SafeHeaderSplittingSource() { this instanceof RemoteFlowSource }
}

/** A default source that introduces data considered safe to use by a header splitting source. */
private class DefaultSafeHeaderSplittingSource extends SafeHeaderSplittingSource {
DefaultSafeHeaderSplittingSource() {
Expand Down
29 changes: 13 additions & 16 deletions java/ql/src/semmle/code/java/security/XSS.qll
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,30 @@ class XssAdditionalTaintStep extends Unit {
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
}

/** CSV sink models representing methods susceptible to XSS attacks. */
private class DefaultXssSinkModel extends SinkModelCsv {
override predicate row(string row) {
row =
[
"javax.servlet.http;HttpServletResponse;false;sendError;(int,String);;Argument[1];xss",
"android.webkit;WebView;false;loadData;;;Argument[0];xss",
"android.webkit;WebView;false;loadUrl;;;Argument[0];xss",
"android.webkit;WebView;false;loadDataWithBaseURL;;;Argument[1];xss"
]
}
}

/** A default sink representing methods susceptible to XSS attacks. */
private class DefaultXssSink extends XssSink {
DefaultXssSink() {
sinkNode(this, "xss")
or
exists(HttpServletResponseSendErrorMethod m, MethodAccess ma |
ma.getMethod() = m and
this.asExpr() = ma.getArgument(1)
)
or
exists(ServletWriterSourceToWritingMethodFlowConfig writer, MethodAccess ma |
ma.getMethod() instanceof WritingMethod and
writer.hasFlowToExpr(ma.getQualifier()) and
this.asExpr() = ma.getArgument(_)
)
or
exists(Method m |
m.getDeclaringType() instanceof TypeWebView and
(
m.getAReference().getArgument(0) = this.asExpr() and m.getName() = "loadData"
or
m.getAReference().getArgument(0) = this.asExpr() and m.getName() = "loadUrl"
or
m.getAReference().getArgument(1) = this.asExpr() and m.getName() = "loadDataWithBaseURL"
)
)
or
exists(SpringRequestMappingMethod requestMappingMethod, ReturnStmt rs |
requestMappingMethod = rs.getEnclosingCallable() and
this.asExpr() = rs.getResult() and
Expand Down