mirror of
https://github.com/github/codeql.git
synced 2026-04-27 17:55:19 +02:00
Merge branch 'main' into feature/java-sinks-csv
This commit is contained in:
70
java/ql/src/Diagnostics/DiagnosticsReporting.qll
Normal file
70
java/ql/src/Diagnostics/DiagnosticsReporting.qll
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Provides classes and predicates for reporting extractor diagnostics to end users.
|
||||
*/
|
||||
|
||||
import java
|
||||
|
||||
/** Gets the SARIF severity level that indicates an error. */
|
||||
private int getErrorSeverity() { result = 2 }
|
||||
|
||||
/** Gets the SARIF severity level that indicates a warning. */
|
||||
private int getWarnSeverity() { result = 1 }
|
||||
|
||||
private predicate knownWarnings(@diagnostic d, string msg, int sev) {
|
||||
exists(string filename |
|
||||
diagnostics(d, 2, _, "Skipping Lombok-ed source file: " + filename, _, _) and
|
||||
msg = "Use of Lombok detected. Skipping file: " + filename and
|
||||
sev = getWarnSeverity()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate knownErrors(@diagnostic d, string msg, int sev) {
|
||||
exists(string numErr, Location l |
|
||||
diagnostics(d, 6, _, numErr, _, l) and
|
||||
msg = "Frontend errors in file: " + l.getFile().getAbsolutePath() + " (" + numErr + ")" and
|
||||
sev = getErrorSeverity()
|
||||
)
|
||||
or
|
||||
exists(string filename, Location l |
|
||||
diagnostics(d, 7, _, "Exception compiling file " + filename, _, l) and
|
||||
msg = "Extraction incomplete in file: " + filename and
|
||||
sev = getErrorSeverity()
|
||||
)
|
||||
or
|
||||
exists(string errMsg, Location l |
|
||||
diagnostics(d, 8, _, errMsg, _, l) and
|
||||
msg = "Severe error: " + errMsg and
|
||||
sev = getErrorSeverity()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate unknownErrors(@diagnostic d, string msg, int sev) {
|
||||
not knownErrors(d, _, _) and
|
||||
exists(Location l, File f, int diagSev |
|
||||
diagnostics(d, diagSev, _, _, _, l) and l.getFile() = f and diagSev > 3
|
||||
|
|
||||
exists(f.getRelativePath()) and
|
||||
msg = "Unknown errors in file: " + f.getAbsolutePath() + " (" + diagSev + ")" and
|
||||
sev = getErrorSeverity()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an extraction error or warning occurred that should be reported to end users,
|
||||
* with the error message `msg` and SARIF severity `sev`.
|
||||
*/
|
||||
predicate reportableDiagnostics(@diagnostic d, string msg, int sev) {
|
||||
knownWarnings(d, msg, sev) or knownErrors(d, msg, sev) or unknownErrors(d, msg, sev)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if compilation unit `f` is a source file that has
|
||||
* no relevant extraction diagnostics associated with it.
|
||||
*/
|
||||
predicate successfullyExtracted(CompilationUnit f) {
|
||||
not exists(@diagnostic d, Location l |
|
||||
reportableDiagnostics(d, _, _) and diagnostics(d, _, _, _, _, l) and l.getFile() = f
|
||||
) and
|
||||
exists(f.getRelativePath()) and
|
||||
f.fromSource()
|
||||
}
|
||||
13
java/ql/src/Diagnostics/ExtractionErrors.ql
Normal file
13
java/ql/src/Diagnostics/ExtractionErrors.ql
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @name Extraction errors
|
||||
* @description A list of extraction errors for files in the source code directory.
|
||||
* @kind diagnostic
|
||||
* @id java/diagnostics/extraction-errors
|
||||
*/
|
||||
|
||||
import java
|
||||
import DiagnosticsReporting
|
||||
|
||||
from string msg, int sev
|
||||
where reportableDiagnostics(_, msg, sev)
|
||||
select msg, sev
|
||||
14
java/ql/src/Diagnostics/SuccessfullyExtractedFiles.ql
Normal file
14
java/ql/src/Diagnostics/SuccessfullyExtractedFiles.ql
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @name Successfully extracted files
|
||||
* @description A list of all files in the source code directory that
|
||||
* were extracted without encountering an error in the file.
|
||||
* @kind diagnostic
|
||||
* @id java/diagnostics/successfully-extracted-files
|
||||
*/
|
||||
|
||||
import java
|
||||
import DiagnosticsReporting
|
||||
|
||||
from CompilationUnit f
|
||||
where successfullyExtracted(f)
|
||||
select f, ""
|
||||
@@ -100,8 +100,7 @@ predicate potentiallyStatic(InnerClass c) {
|
||||
m = a.getEnclosingCallable() and
|
||||
m.getDeclaringType() = c
|
||||
) and
|
||||
not c instanceof AnonymousClass and
|
||||
not c instanceof LocalClass and
|
||||
c instanceof MemberType and
|
||||
forall(
|
||||
InnerClass other // If nested and non-static, ...
|
||||
|
|
||||
|
||||
@@ -79,8 +79,7 @@ predicate stackTraceExpr(Expr exception, MethodAccess stackTraceString) {
|
||||
printStackCall.getAnArgument() = printWriter and
|
||||
printStackCall.getQualifier() = exception and
|
||||
stackTraceString.getQualifier() = stringWriterVar.getAnAccess() and
|
||||
stackTraceString.getMethod().getName() = "toString" and
|
||||
stackTraceString.getMethod().getNumberOfParameters() = 0
|
||||
stackTraceString.getMethod() instanceof ToStringMethod
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,8 @@ class InsecureAlgoLiteral extends ShortStringLiteral {
|
||||
}
|
||||
|
||||
predicate objectToString(MethodAccess ma) {
|
||||
exists(Method m |
|
||||
exists(ToStringMethod m |
|
||||
m = ma.getMethod() and
|
||||
m.hasName("toString") and
|
||||
m.getDeclaringType() instanceof TypeObject and
|
||||
variableTrack(ma.getQualifier()).getType().getErasure() instanceof TypeObject
|
||||
)
|
||||
|
||||
@@ -19,14 +19,14 @@ class HardcodedCredentialApiCallConfiguration extends DataFlow::Configuration {
|
||||
|
||||
override predicate isSource(DataFlow::Node n) {
|
||||
n.asExpr() instanceof HardcodedExpr and
|
||||
not n.asExpr().getEnclosingCallable().getName() = "toString"
|
||||
not n.asExpr().getEnclosingCallable() instanceof ToStringMethod
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node n) { n.asExpr() instanceof CredentialsApiSink }
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
node1.asExpr().getType() instanceof TypeString and
|
||||
exists(MethodAccess ma | ma.getMethod().getName().regexpMatch("getBytes|toCharArray") |
|
||||
exists(MethodAccess ma | ma.getMethod().hasName(["getBytes", "toCharArray"]) |
|
||||
node2.asExpr() = ma and
|
||||
ma.getQualifier() = node1.asExpr()
|
||||
)
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
|
||||
import java
|
||||
|
||||
from MethodAccess ma, Method tostring
|
||||
from MethodAccess ma, ToStringMethod tostring
|
||||
where
|
||||
tostring.hasName("toString") and
|
||||
tostring.getDeclaringType() instanceof TypeString and
|
||||
ma.getMethod() = tostring
|
||||
select ma, "Redundant call to 'toString' on a String object."
|
||||
|
||||
@@ -14,20 +14,13 @@ import java
|
||||
import semmle.code.java.StringFormat
|
||||
|
||||
predicate explicitToStringCall(Expr e) {
|
||||
exists(MethodAccess ma, Method toString | toString = ma.getMethod() |
|
||||
e = ma.getQualifier() and
|
||||
toString.getName() = "toString" and
|
||||
toString.getNumberOfParameters() = 0 and
|
||||
not toString.isStatic()
|
||||
exists(MethodAccess ma |
|
||||
ma.getMethod() instanceof ToStringMethod and
|
||||
e = ma.getQualifier()
|
||||
)
|
||||
}
|
||||
|
||||
predicate directlyDeclaresToString(Class c) {
|
||||
exists(Method m | m.getDeclaringType() = c |
|
||||
m.getName() = "toString" and
|
||||
m.getNumberOfParameters() = 0
|
||||
)
|
||||
}
|
||||
predicate directlyDeclaresToString(Class c) { any(ToStringMethod m).getDeclaringType() = c }
|
||||
|
||||
predicate inheritsObjectToString(Class t) {
|
||||
not directlyDeclaresToString(t.getSourceDeclaration()) and
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Spring Boot is a popular framework that facilitates the development of stand-alone applications
|
||||
and micro services. Spring Boot Actuator helps to expose production-ready support features against
|
||||
Spring Boot applications.</p>
|
||||
|
||||
<p>Endpoints of Spring Boot Actuator allow to monitor and interact with a Spring Boot application.
|
||||
Exposing unprotected actuator endpoints through configuration files can lead to information disclosure
|
||||
or even remote code execution vulnerability.</p>
|
||||
|
||||
<p>Rather than programmatically permitting endpoint requests or enforcing access control, frequently
|
||||
developers simply leave management endpoints publicly accessible in the application configuration file
|
||||
<code>application.properties</code> without enforcing access control through Spring Security.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Declare the Spring Boot Starter Security module in XML configuration or programmatically enforce
|
||||
security checks on management endpoints using Spring Security. Otherwise accessing management endpoints
|
||||
on a different HTTP port other than the port that the web application is listening on also helps to
|
||||
improve the security.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following examples show both 'BAD' and 'GOOD' configurations. In the 'BAD' configuration,
|
||||
no security module is declared and sensitive management endpoints are exposed. In the 'GOOD' configuration,
|
||||
security is enforced and only endpoints requiring exposure are exposed.</p>
|
||||
<sample src="pom_good.xml" />
|
||||
<sample src="pom_bad.xml" />
|
||||
<sample src="application.properties" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Spring Boot documentation:
|
||||
<a href="https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html">Spring Boot Actuator: Production-ready Features</a>
|
||||
</li>
|
||||
<li>
|
||||
VERACODE Blog:
|
||||
<a href="https://www.veracode.com/blog/research/exploiting-spring-boot-actuators">Exploiting Spring Boot Actuators</a>
|
||||
</li>
|
||||
<li>
|
||||
HackerOne Report:
|
||||
<a href="https://hackerone.com/reports/862589">Spring Actuator endpoints publicly available, leading to account takeover</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @name Insecure Spring Boot Actuator Configuration
|
||||
* @description Exposed Spring Boot Actuator through configuration files without declarative or procedural
|
||||
* security enforcement leads to information leak or even remote code execution.
|
||||
* @kind problem
|
||||
* @id java/insecure-spring-actuator-config
|
||||
* @tags security
|
||||
* external/cwe-016
|
||||
*/
|
||||
|
||||
/*
|
||||
* Note this query requires properties files to be indexed before it can produce results.
|
||||
* If creating your own database with the CodeQL CLI, you should run
|
||||
* `codeql database index-files --language=properties ...`
|
||||
* If using lgtm.com, you should add `properties_files: true` to the index block of your
|
||||
* lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction)
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.configfiles.ConfigFiles
|
||||
import semmle.code.xml.MavenPom
|
||||
|
||||
/** The parent node of the `org.springframework.boot` group. */
|
||||
class SpringBootParent extends Parent {
|
||||
SpringBootParent() { this.getGroup().getValue() = "org.springframework.boot" }
|
||||
}
|
||||
|
||||
/** Class of Spring Boot dependencies. */
|
||||
class SpringBootPom extends Pom {
|
||||
SpringBootPom() { this.getParentElement() instanceof SpringBootParent }
|
||||
|
||||
/** Holds if the Spring Boot Actuator module `spring-boot-starter-actuator` is used in the project. */
|
||||
predicate isSpringBootActuatorUsed() {
|
||||
this.getADependency().getArtifact().getValue() = "spring-boot-starter-actuator"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the Spring Boot Security module is used in the project, which brings in other security
|
||||
* related libraries.
|
||||
*/
|
||||
predicate isSpringBootSecurityUsed() {
|
||||
this.getADependency().getArtifact().getValue() = "spring-boot-starter-security"
|
||||
}
|
||||
}
|
||||
|
||||
/** The properties file `application.properties`. */
|
||||
class ApplicationProperties extends ConfigPair {
|
||||
ApplicationProperties() { this.getFile().getBaseName() = "application.properties" }
|
||||
}
|
||||
|
||||
/** The configuration property `management.security.enabled`. */
|
||||
class ManagementSecurityConfig extends ApplicationProperties {
|
||||
ManagementSecurityConfig() { this.getNameElement().getName() = "management.security.enabled" }
|
||||
|
||||
/** Gets the whitespace-trimmed value of this property. */
|
||||
string getValue() { result = this.getValueElement().getValue().trim() }
|
||||
|
||||
/** Holds if `management.security.enabled` is set to `false`. */
|
||||
predicate hasSecurityDisabled() { getValue() = "false" }
|
||||
|
||||
/** Holds if `management.security.enabled` is set to `true`. */
|
||||
predicate hasSecurityEnabled() { getValue() = "true" }
|
||||
}
|
||||
|
||||
/** The configuration property `management.endpoints.web.exposure.include`. */
|
||||
class ManagementEndPointInclude extends ApplicationProperties {
|
||||
ManagementEndPointInclude() {
|
||||
this.getNameElement().getName() = "management.endpoints.web.exposure.include"
|
||||
}
|
||||
|
||||
/** Gets the whitespace-trimmed value of this property. */
|
||||
string getValue() { result = this.getValueElement().getValue().trim() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ApplicationProperties` ap of a repository managed by `SpringBootPom` pom
|
||||
* has a vulnerable configuration of Spring Boot Actuator management endpoints.
|
||||
*/
|
||||
predicate hasConfidentialEndPointExposed(SpringBootPom pom, ApplicationProperties ap) {
|
||||
pom.isSpringBootActuatorUsed() and
|
||||
not pom.isSpringBootSecurityUsed() and
|
||||
ap.getFile()
|
||||
.getParentContainer()
|
||||
.getAbsolutePath()
|
||||
.matches(pom.getFile().getParentContainer().getAbsolutePath() + "%") and // in the same sub-directory
|
||||
exists(string springBootVersion | springBootVersion = pom.getParentElement().getVersionString() |
|
||||
springBootVersion.regexpMatch("1\\.[0-4].*") and // version 1.0, 1.1, ..., 1.4
|
||||
not exists(ManagementSecurityConfig me |
|
||||
me.hasSecurityEnabled() and me.getFile() = ap.getFile()
|
||||
)
|
||||
or
|
||||
springBootVersion.matches("1.5%") and // version 1.5
|
||||
exists(ManagementSecurityConfig me | me.hasSecurityDisabled() and me.getFile() = ap.getFile())
|
||||
or
|
||||
springBootVersion.matches("2.%") and //version 2.x
|
||||
exists(ManagementEndPointInclude mi |
|
||||
mi.getFile() = ap.getFile() and
|
||||
(
|
||||
mi.getValue() = "*" // all endpoints are enabled
|
||||
or
|
||||
mi.getValue()
|
||||
.matches([
|
||||
"%dump%", "%trace%", "%logfile%", "%shutdown%", "%startup%", "%mappings%", "%env%",
|
||||
"%beans%", "%sessions%"
|
||||
]) // confidential endpoints to check although all endpoints apart from '/health' and '/info' are considered sensitive by Spring
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from SpringBootPom pom, ApplicationProperties ap, Dependency d
|
||||
where
|
||||
hasConfidentialEndPointExposed(pom, ap) and
|
||||
d = pom.getADependency() and
|
||||
d.getArtifact().getValue() = "spring-boot-starter-actuator"
|
||||
select d, "Insecure configuration of Spring Boot Actuator exposes sensitive endpoints."
|
||||
@@ -0,0 +1,22 @@
|
||||
#management.endpoints.web.base-path=/admin
|
||||
|
||||
|
||||
#### BAD: All management endpoints are accessible ####
|
||||
# vulnerable configuration (spring boot 1.0 - 1.4): exposes actuators by default
|
||||
|
||||
# vulnerable configuration (spring boot 1.5+): requires value false to expose sensitive actuators
|
||||
management.security.enabled=false
|
||||
|
||||
# vulnerable configuration (spring boot 2+): exposes health and info only by default, here overridden to expose everything
|
||||
management.endpoints.web.exposure.include=*
|
||||
|
||||
|
||||
#### GOOD: All management endpoints have access control ####
|
||||
# safe configuration (spring boot 1.0 - 1.4): exposes actuators by default
|
||||
management.security.enabled=true
|
||||
|
||||
# safe configuration (spring boot 1.5+): requires value false to expose sensitive actuators
|
||||
management.security.enabled=true
|
||||
|
||||
# safe configuration (spring boot 2+): exposes health and info only by default, here overridden to expose one additional endpoint which we assume is intentional and safe.
|
||||
management.endpoints.web.exposure.include=beans,info,health
|
||||
50
java/ql/src/experimental/Security/CWE/CWE-016/pom_bad.xml
Normal file
50
java/ql/src/experimental/Security/CWE/CWE-016/pom_bad.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>spring-boot-actuator-app</groupId>
|
||||
<artifactId>spring-boot-actuator-app</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.8.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- BAD: No Spring Security enabled -->
|
||||
<!-- dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
50
java/ql/src/experimental/Security/CWE/CWE-016/pom_good.xml
Normal file
50
java/ql/src/experimental/Security/CWE/CWE-016/pom_good.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>spring-boot-actuator-app</groupId>
|
||||
<artifactId>spring-boot-actuator-app</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.8.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- GOOD: Enable Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
14
java/ql/src/experimental/Security/CWE/CWE-094/FlowUtils.qll
Normal file
14
java/ql/src/experimental/Security/CWE/CWE-094/FlowUtils.qll
Normal file
@@ -0,0 +1,14 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step that returns data from
|
||||
* a bean by calling one of its getters.
|
||||
*/
|
||||
predicate hasGetterFlow(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()
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Apache Groovy is a powerful, optionally typed and dynamic language,
|
||||
with static-typing and static compilation capabilities.
|
||||
|
||||
It integrates smoothly with any Java program,
|
||||
and immediately delivers to your application powerful features,
|
||||
including scripting capabilities, Domain-Specific Language authoring,
|
||||
runtime and compile-time meta-programming and functional programming.
|
||||
|
||||
If a Groovy script is built using attacker-controlled data,
|
||||
and then evaluated, then it may allow the attacker to achieve RCE.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
It is generally recommended to avoid using untrusted input in a Groovy evaluation.
|
||||
If this is not possible, use a sandbox solution. Developers must also take care that Groovy
|
||||
compile-time metaprogramming can also lead to RCE: it is possible to achieve RCE by compiling
|
||||
a Groovy script (see the article "Abusing Meta Programming for Unauthenticated RCE!" linked below).
|
||||
|
||||
Groovy's <code>SecureASTCustomizer</code> allows securing source code by controlling what code constructs are permitted.
|
||||
This is typically done when using Groovy for its scripting or domain specific language (DSL) features.
|
||||
The fundamental problem is that Groovy is a dynamic language, yet <code>SecureASTCustomizer</code> works by looking at Groovy AST statically.
|
||||
|
||||
This makes it very easy for an attacker to bypass many of the intended checks
|
||||
(see https://kohsuke.org/2012/04/27/groovy-secureastcustomizer-is-harmful/).
|
||||
Therefore, besides <code>SecureASTCustomizer</code>, runtime checks are also necessary before calling Groovy methods
|
||||
(see https://melix.github.io/blog/2015/03/sandboxing.html).
|
||||
|
||||
It is also possible to use a block-list method, excluding unwanted classes from being loaded by the JVM.
|
||||
This method is not always recommended, because block-lists can be bypassed by unexpected values.
|
||||
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example uses untrusted data to evaluate a Groovy script.
|
||||
</p>
|
||||
<sample src="GroovyInjectionBad.java" />
|
||||
|
||||
<p>
|
||||
The following example uses classloader block-list approach to exclude loading dangerous classes.
|
||||
</p>
|
||||
<sample src="GroovyInjectionBlocklist.java" />
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Orange Tsai:
|
||||
<a href="https://blog.orange.tw/2019/02/abusing-meta-programming-for-unauthenticated-rce.html">Abusing Meta Programming for Unauthenticated RCE!</a>.
|
||||
</li>
|
||||
<li>
|
||||
Cédric Champeau:
|
||||
<a href="https://melix.github.io/blog/2015/03/sandboxing.html">Improved sandboxing of Groovy scripts</a>.
|
||||
</li>
|
||||
<li>
|
||||
Kohsuke Kawaguchi:
|
||||
<a href="https://kohsuke.org/2012/04/27/groovy-secureastcustomizer-is-harmful/">Groovy SecureASTCustomizer is harmful</a>.
|
||||
</li>
|
||||
<li>
|
||||
Welk1n:
|
||||
<a href="https://github.com/welk1n/exploiting-groovy-in-Java/">Groovy Injection payloads</a>.
|
||||
</li>
|
||||
<li>
|
||||
Charles Chan:
|
||||
<a href="https://levelup.gitconnected.com/secure-groovy-script-execution-in-a-sandbox-ea39f80ee87/">Secure Groovy Script Execution in a Sandbox</a>.
|
||||
</li>
|
||||
<li>
|
||||
Eugene:
|
||||
<a href="https://stringconcat.com/en/scripting-and-sandboxing/">Scripting and sandboxing in a JVM environment</a>.
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Groovy Language injection
|
||||
* @description Evaluation of a user-controlled Groovy script
|
||||
* may lead to arbitrary code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/groovy-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import DataFlow::PathGraph
|
||||
import GroovyInjectionLib
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, GroovyInjectionConfig conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Groovy Injection from $@.", source.getNode(),
|
||||
"this user input"
|
||||
@@ -0,0 +1,27 @@
|
||||
public class GroovyInjection {
|
||||
void injectionViaClassLoader(HttpServletRequest request) {
|
||||
String script = request.getParameter("script");
|
||||
final GroovyClassLoader classLoader = new GroovyClassLoader();
|
||||
Class groovy = classLoader.parseClass(script);
|
||||
GroovyObject groovyObj = (GroovyObject) groovy.newInstance();
|
||||
}
|
||||
|
||||
void injectionViaEval(HttpServletRequest request) {
|
||||
String script = request.getParameter("script");
|
||||
Eval.me(script);
|
||||
}
|
||||
|
||||
void injectionViaGroovyShell(HttpServletRequest request) {
|
||||
GroovyShell shell = new GroovyShell();
|
||||
String script = request.getParameter("script");
|
||||
shell.evaluate(script);
|
||||
}
|
||||
|
||||
void injectionViaGroovyShellGroovyCodeSource(HttpServletRequest request) {
|
||||
GroovyShell shell = new GroovyShell();
|
||||
String script = request.getParameter("script");
|
||||
GroovyCodeSource gcs = new GroovyCodeSource(script, "test", "Test");
|
||||
shell.evaluate(gcs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
public class SandboxGroovyClassLoader extends ClassLoader {
|
||||
public SandboxGroovyClassLoader(ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
/* override `loadClass` here to prevent loading sensitive classes, such as `java.lang.Runtime`, `java.lang.ProcessBuilder`, `java.lang.System`, etc. */
|
||||
/* Note we must also block `groovy.transform.ASTTest`, `groovy.lang.GrabConfig` and `org.buildobjects.process.ProcBuilder` to prevent compile-time RCE. */
|
||||
|
||||
static void runWithSandboxGroovyClassLoader() throws Exception {
|
||||
// GOOD: route all class-loading via sand-boxing classloader.
|
||||
SandboxGroovyClassLoader classLoader = new GroovyClassLoader(new SandboxGroovyClassLoader());
|
||||
|
||||
Class<?> scriptClass = classLoader.parseClass(untrusted.getQueryString());
|
||||
Object scriptInstance = scriptClass.newInstance();
|
||||
Object result = scriptClass.getDeclaredMethod("bar", new Class[]{}).invoke(scriptInstance, new Object[]{});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Provides classes and predicates for Groovy Code Injection
|
||||
* taint-tracking configuration.
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
|
||||
/** A data flow sink for Groovy expression injection vulnerabilities. */
|
||||
abstract private class GroovyInjectionSink extends DataFlow::ExprNode { }
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for unsafe user input
|
||||
* that is used to evaluate a Groovy expression.
|
||||
*/
|
||||
class GroovyInjectionConfig extends TaintTracking::Configuration {
|
||||
GroovyInjectionConfig() { this = "GroovyInjectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof GroovyInjectionSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
groovyCodeSourceTaintStep(fromNode, toNode)
|
||||
}
|
||||
}
|
||||
|
||||
/** The class `groovy.lang.GroovyShell`. */
|
||||
private class TypeGroovyShell extends RefType {
|
||||
TypeGroovyShell() { this.hasQualifiedName("groovy.lang", "GroovyShell") }
|
||||
}
|
||||
|
||||
/** The class `groovy.lang.GroovyCodeSource`. */
|
||||
private class TypeGroovyCodeSource extends RefType {
|
||||
TypeGroovyCodeSource() { this.hasQualifiedName("groovy.lang", "GroovyCodeSource") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in the `GroovyShell` class that evaluate a Groovy expression.
|
||||
*/
|
||||
private class GroovyShellMethod extends Method {
|
||||
GroovyShellMethod() {
|
||||
this.getDeclaringType() instanceof TypeGroovyShell and
|
||||
this.getName() in ["evaluate", "parse", "run"]
|
||||
}
|
||||
}
|
||||
|
||||
private class GroovyShellMethodAccess extends MethodAccess {
|
||||
GroovyShellMethodAccess() { this.getMethod() instanceof GroovyShellMethod }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step from a tainted string to
|
||||
* a `GroovyCodeSource` instance, i.e. `new GroovyCodeSource(tainted, ...)`.
|
||||
*/
|
||||
private predicate groovyCodeSourceTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(ConstructorCall gcscc |
|
||||
gcscc.getConstructedType() instanceof TypeGroovyCodeSource and
|
||||
gcscc = toNode.asExpr() and
|
||||
gcscc.getArgument(0) = fromNode.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for Groovy Injection via the `GroovyShell` class.
|
||||
*
|
||||
* ```
|
||||
* GroovyShell gs = new GroovyShell();
|
||||
* gs.evaluate(sink, ....)
|
||||
* gs.run(sink, ....)
|
||||
* gs.parse(sink,...)
|
||||
* ```
|
||||
*/
|
||||
private class GroovyShellSink extends GroovyInjectionSink {
|
||||
GroovyShellSink() {
|
||||
exists(GroovyShellMethodAccess ma, Argument firstArg |
|
||||
ma.getArgument(0) = firstArg and
|
||||
firstArg = this.asExpr() and
|
||||
(
|
||||
firstArg.getType() instanceof TypeString or
|
||||
firstArg.getType() instanceof TypeGroovyCodeSource
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** The class `groovy.util.Eval`. */
|
||||
private class TypeEval extends RefType {
|
||||
TypeEval() { this.hasQualifiedName("groovy.util", "Eval") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in the `Eval` class that evaluate a Groovy expression.
|
||||
*/
|
||||
private class EvalMethod extends Method {
|
||||
EvalMethod() {
|
||||
this.getDeclaringType() instanceof TypeEval and
|
||||
this.getName() in ["me", "x", "xy", "xyz"]
|
||||
}
|
||||
}
|
||||
|
||||
private class EvalMethodAccess extends MethodAccess {
|
||||
EvalMethodAccess() { this.getMethod() instanceof EvalMethod }
|
||||
|
||||
Expr getArgumentExpr() { result = this.getArgument(this.getNumArgument() - 1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for Groovy Injection via the `Eval` class.
|
||||
*
|
||||
* ```
|
||||
* Eval.me(sink)
|
||||
* Eval.me("p1", "p2", sink)
|
||||
* Eval.x("p1", sink)
|
||||
* Eval.xy("p1", "p2" sink)
|
||||
* Eval.xyz("p1", "p2", "p3", sink)
|
||||
* ```
|
||||
*/
|
||||
private class EvalSink extends GroovyInjectionSink {
|
||||
EvalSink() { exists(EvalMethodAccess ma | ma.getArgumentExpr() = this.asExpr()) }
|
||||
}
|
||||
|
||||
/** The class `groovy.lang.GroovyClassLoader`. */
|
||||
private class TypeGroovyClassLoader extends RefType {
|
||||
TypeGroovyClassLoader() { this.hasQualifiedName("groovy.lang", "GroovyClassLoader") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method in the `GroovyClassLoader` class that evaluates a Groovy expression.
|
||||
*/
|
||||
private class GroovyClassLoaderParseClassMethod extends Method {
|
||||
GroovyClassLoaderParseClassMethod() {
|
||||
this.getDeclaringType() instanceof TypeGroovyClassLoader and
|
||||
this.hasName("parseClass")
|
||||
}
|
||||
}
|
||||
|
||||
private class GroovyClassLoaderParseClassMethodAccess extends MethodAccess {
|
||||
GroovyClassLoaderParseClassMethodAccess() {
|
||||
this.getMethod() instanceof GroovyClassLoaderParseClassMethod
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for Groovy Injection via the `GroovyClassLoader` class.
|
||||
*
|
||||
* ```
|
||||
* GroovyClassLoader classLoader = new GroovyClassLoader();
|
||||
* Class groovy = classLoader.parseClass(script);
|
||||
* ```
|
||||
*
|
||||
* Groovy supports compile-time metaprogramming, so just calling the `parseClass`
|
||||
* method is enough to achieve RCE.
|
||||
*/
|
||||
private class GroovyClassLoadParseClassSink extends GroovyInjectionSink {
|
||||
GroovyClassLoadParseClassSink() {
|
||||
exists(GroovyClassLoaderParseClassMethodAccess ma | ma.getArgument(0) = this.asExpr())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
It is dangerous to load Dex libraries from shared world-writable storage spaces. A malicious actor can replace a dex file with a maliciously crafted file
|
||||
which when loaded by the app can lead to code execution.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Loading a file from private storage instead of a world-writable one can prevent this issue,
|
||||
because the attacker cannot access files stored there.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example loads a Dex file from a shared world-writable location. in this case,
|
||||
since the `/sdcard` directory is on external storage, anyone can read/write to the location.
|
||||
bypassing all Android security policies. Hence, this is insecure.
|
||||
</p>
|
||||
<sample src="InsecureDexLoadingBad.java" />
|
||||
|
||||
<p>
|
||||
The next example loads a Dex file stored inside the app's private storage.
|
||||
This is not exploitable as nobody else except the app can access the data stored there.
|
||||
</p>
|
||||
<sample src="InsecureDexLoadingGood.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Android Documentation:
|
||||
<a href="https://developer.android.com/training/data-storage/">Data and file storage overview</a>.
|
||||
</li>
|
||||
<li>
|
||||
Android Documentation:
|
||||
<a href="https://developer.android.com/reference/dalvik/system/DexClassLoader">DexClassLoader</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Insecure loading of an Android Dex File
|
||||
* @description Loading a DEX library located in a world-writable location such as
|
||||
* an SD card can lead to arbitrary code execution vulnerabilities.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/android-insecure-dex-loading
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import InsecureDexLoading
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, InsecureDexConfiguration conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Potential arbitrary code execution due to $@.",
|
||||
source.getNode(), "a value loaded from a world-writable source."
|
||||
@@ -0,0 +1,100 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration detecting unsafe use of a
|
||||
* `DexClassLoader` by an Android app.
|
||||
*/
|
||||
class InsecureDexConfiguration extends TaintTracking::Configuration {
|
||||
InsecureDexConfiguration() { this = "Insecure Dex File Load" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof InsecureDexSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof InsecureDexSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
flowStep(pred, succ)
|
||||
}
|
||||
}
|
||||
|
||||
/** A data flow source for insecure Dex class loading vulnerabilities. */
|
||||
abstract class InsecureDexSource extends DataFlow::Node { }
|
||||
|
||||
/** A data flow sink for insecure Dex class loading vulnerabilities. */
|
||||
abstract class InsecureDexSink extends DataFlow::Node { }
|
||||
|
||||
private predicate flowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// propagate from a `java.io.File` via the `File.getAbsolutePath` call.
|
||||
exists(MethodAccess m |
|
||||
m.getMethod().getDeclaringType() instanceof TypeFile and
|
||||
m.getMethod().hasName("getAbsolutePath") and
|
||||
m.getQualifier() = pred.asExpr() and
|
||||
m = succ.asExpr()
|
||||
)
|
||||
or
|
||||
// propagate from a `java.io.File` via the `File.toString` call.
|
||||
exists(MethodAccess m |
|
||||
m.getMethod().getDeclaringType() instanceof TypeFile and
|
||||
m.getMethod().hasName("toString") and
|
||||
m.getQualifier() = pred.asExpr() and
|
||||
m = succ.asExpr()
|
||||
)
|
||||
or
|
||||
// propagate to newly created `File` if the parent directory of the new `File` is tainted
|
||||
exists(ConstructorCall cc |
|
||||
cc.getConstructedType() instanceof TypeFile and
|
||||
cc.getArgument(0) = pred.asExpr() and
|
||||
cc = succ.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to a `DexClassLoader` call taken as a sink for
|
||||
* insecure Dex class loading vulnerabilities.
|
||||
*/
|
||||
private class DexClassLoader extends InsecureDexSink {
|
||||
DexClassLoader() {
|
||||
exists(ConstructorCall cc |
|
||||
cc.getConstructedType().hasQualifiedName("dalvik.system", "DexClassLoader")
|
||||
|
|
||||
this.asExpr() = cc.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `File` instance which reads from an SD card
|
||||
* taken as a source for insecure Dex class loading vulnerabilities.
|
||||
*/
|
||||
private class ExternalFile extends InsecureDexSource {
|
||||
ExternalFile() {
|
||||
exists(ConstructorCall cc, Argument a |
|
||||
cc.getConstructedType() instanceof TypeFile and
|
||||
a = cc.getArgument(0) and
|
||||
a.(CompileTimeConstantExpr).getStringValue().matches("%sdcard%")
|
||||
|
|
||||
this.asExpr() = a
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A directory or file which may be stored in an world writable directory
|
||||
* taken as a source for insecure Dex class loading vulnerabilities.
|
||||
*/
|
||||
private class ExternalStorageDirSource extends InsecureDexSource {
|
||||
ExternalStorageDirSource() {
|
||||
exists(Method m |
|
||||
m.getDeclaringType().hasQualifiedName("android.os", "Environment") and
|
||||
m.hasName("getExternalStorageDirectory")
|
||||
or
|
||||
m.getDeclaringType().hasQualifiedName("android.content", "Context") and
|
||||
m.hasName([
|
||||
"getExternalFilesDir", "getExternalFilesDirs", "getExternalMediaDirs",
|
||||
"getExternalCacheDir", "getExternalCacheDirs"
|
||||
])
|
||||
|
|
||||
this.asExpr() = m.getAReference()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.os.Bundle;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
import dalvik.system.DexFile;
|
||||
|
||||
public class InsecureDexLoading extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
updateChecker();
|
||||
}
|
||||
|
||||
private void updateChecker() {
|
||||
try {
|
||||
File file = new File("/sdcard/updater.apk");
|
||||
if (file.exists() && file.isFile() && file.length() <= 1000) {
|
||||
DexClassLoader cl = new DexClassLoader(file.getAbsolutePath(), getCacheDir().getAbsolutePath(), null,
|
||||
getClassLoader());
|
||||
int version = (int) cl.loadClass("my.package.class").getDeclaredMethod("myMethod").invoke(null);
|
||||
if (Build.VERSION.SDK_INT < version) {
|
||||
Toast.makeText(this, "Loaded Dex!", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
public class SecureDexLoading extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
updateChecker();
|
||||
}
|
||||
|
||||
private void updateChecker() {
|
||||
try {
|
||||
File file = new File(getCacheDir() + "/updater.apk");
|
||||
if (file.exists() && file.isFile() && file.length() <= 1000) {
|
||||
DexClassLoader cl = new DexClassLoader(file.getAbsolutePath(), getCacheDir().getAbsolutePath(), null,
|
||||
getClassLoader());
|
||||
int version = (int) cl.loadClass("my.package.class").getDeclaredMethod("myMethod").invoke(null);
|
||||
if (Build.VERSION.SDK_INT < version) {
|
||||
Toast.makeText(this, "Securely loaded Dex!", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Jakarta Expression Language (EL) is an expression language for Java applications.
|
||||
There is a single language specification and multiple implementations
|
||||
such as Glassfish, Juel, Apache Commons EL, etc.
|
||||
The language allows invocation of methods available in the JVM.
|
||||
If an expression is built using attacker-controlled data,
|
||||
and then evaluated, it may allow the attacker to run arbitrary code.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
It is generally recommended to avoid using untrusted data in an EL expression.
|
||||
Before using untrusted data to build an EL expression, the data should be validated
|
||||
to ensure it is not evaluated as expression language. If the EL implementation offers
|
||||
configuring a sandbox for EL expressions, they should be run in a restrictive sandbox
|
||||
that allows accessing only explicitly allowed classes. If the EL implementation
|
||||
does not support sandboxing, consider using other expression language implementations
|
||||
with sandboxing capabilities such as Apache Commons JEXL or the Spring Expression Language.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example shows how untrusted data is used to build and run an expression
|
||||
using the JUEL interpreter:
|
||||
</p>
|
||||
<sample src="UnsafeExpressionEvaluationWithJuel.java" />
|
||||
|
||||
<p>
|
||||
JUEL does not support running expressions in a sandbox. To prevent running arbitrary code,
|
||||
incoming data has to be checked before including it in an expression. The next example
|
||||
uses a Regex pattern to check whether a user tries to run an allowed expression or not:
|
||||
</p>
|
||||
<sample src="SaferExpressionEvaluationWithJuel.java" />
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Eclipse Foundation:
|
||||
<a href="https://projects.eclipse.org/projects/ee4j.el">Jakarta Expression Language</a>.
|
||||
</li>
|
||||
<li>
|
||||
Jakarta EE documentation:
|
||||
<a href="https://javadoc.io/doc/jakarta.el/jakarta.el-api/latest/index.html">Jakarta Expression Language API</a>
|
||||
</li>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection">Expression Language Injection</a>.
|
||||
</li>
|
||||
<li>
|
||||
JUEL:
|
||||
<a href="http://juel.sourceforge.net">Home page</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Jakarta Expression Language injection
|
||||
* @description Evaluation of a user-controlled expression
|
||||
* may lead to arbitrary code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/javaee-expression-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import JakartaExpressionInjectionLib
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, JakartaExpressionInjectionConfig conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Jakarta Expression Language injection from $@.",
|
||||
source.getNode(), "this user input"
|
||||
@@ -0,0 +1,108 @@
|
||||
import java
|
||||
import FlowUtils
|
||||
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 an expression.
|
||||
*/
|
||||
class JakartaExpressionInjectionConfig extends TaintTracking::Configuration {
|
||||
JakartaExpressionInjectionConfig() { this = "JakartaExpressionInjectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof ExpressionEvaluationSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
any(TaintPropagatingCall c).taintFlow(fromNode, toNode) or
|
||||
hasGetterFlow(fromNode, toNode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for Expresssion Language injection vulnerabilities,
|
||||
* i.e. method calls that run evaluation of an expression.
|
||||
*/
|
||||
private class ExpressionEvaluationSink extends DataFlow::ExprNode {
|
||||
ExpressionEvaluationSink() {
|
||||
exists(MethodAccess ma, Method m, Expr taintFrom |
|
||||
ma.getMethod() = m and taintFrom = this.asExpr()
|
||||
|
|
||||
m.getDeclaringType() instanceof ValueExpression and
|
||||
m.hasName(["getValue", "setValue"]) and
|
||||
ma.getQualifier() = taintFrom
|
||||
or
|
||||
m.getDeclaringType() instanceof MethodExpression and
|
||||
m.hasName("invoke") and
|
||||
ma.getQualifier() = taintFrom
|
||||
or
|
||||
m.getDeclaringType() instanceof LambdaExpression and
|
||||
m.hasName("invoke") and
|
||||
ma.getQualifier() = taintFrom
|
||||
or
|
||||
m.getDeclaringType() instanceof ELProcessor and
|
||||
m.hasName(["eval", "getValue", "setValue"]) and
|
||||
ma.getArgument(0) = taintFrom
|
||||
or
|
||||
m.getDeclaringType() instanceof ELProcessor and
|
||||
m.hasName("setVariable") and
|
||||
ma.getArgument(1) = taintFrom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines method calls that propagate tainted expressions.
|
||||
*/
|
||||
private class TaintPropagatingCall extends Call {
|
||||
Expr taintFromExpr;
|
||||
|
||||
TaintPropagatingCall() {
|
||||
taintFromExpr = this.getArgument(1) and
|
||||
(
|
||||
exists(Method m | this.(MethodAccess).getMethod() = m |
|
||||
m.getDeclaringType() instanceof ExpressionFactory and
|
||||
m.hasName(["createValueExpression", "createMethodExpression"]) and
|
||||
taintFromExpr.getType() instanceof TypeString
|
||||
)
|
||||
or
|
||||
exists(Constructor c | this.(ConstructorCall).getConstructor() = c |
|
||||
c.getDeclaringType() instanceof LambdaExpression and
|
||||
taintFromExpr.getType() instanceof ValueExpression
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
|
||||
private class JakartaType extends RefType {
|
||||
JakartaType() { getPackage().hasName(["javax.el", "jakarta.el"]) }
|
||||
}
|
||||
|
||||
private class ELProcessor extends JakartaType {
|
||||
ELProcessor() { hasName("ELProcessor") }
|
||||
}
|
||||
|
||||
private class ExpressionFactory extends JakartaType {
|
||||
ExpressionFactory() { hasName("ExpressionFactory") }
|
||||
}
|
||||
|
||||
private class ValueExpression extends JakartaType {
|
||||
ValueExpression() { hasName("ValueExpression") }
|
||||
}
|
||||
|
||||
private class MethodExpression extends JakartaType {
|
||||
MethodExpression() { hasName("MethodExpression") }
|
||||
}
|
||||
|
||||
private class LambdaExpression extends JakartaType {
|
||||
LambdaExpression() { hasName("LambdaExpression") }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import java
|
||||
import FlowUtils
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
|
||||
@@ -16,7 +17,7 @@ class JexlInjectionConfig extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
any(TaintPropagatingJexlMethodCall c).taintFlow(fromNode, toNode) or
|
||||
returnsDataFromBean(fromNode, toNode)
|
||||
hasGetterFlow(fromNode, toNode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,18 +153,6 @@ private predicate createsJexlEngine(DataFlow::Node fromNode, DataFlow::Node toNo
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
String input = getRemoteUserInput();
|
||||
String pattern = "(inside|outside)\\.(temperature|humidity)";
|
||||
if (!input.matches(pattern)) {
|
||||
throw new IllegalArgumentException("Unexpected expression");
|
||||
}
|
||||
String expression = "${" + input + "}";
|
||||
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
|
||||
ValueExpression e = factory.createValueExpression(context, expression, Object.class);
|
||||
SimpleContext context = getContext();
|
||||
Object result = e.getValue(context);
|
||||
@@ -0,0 +1,5 @@
|
||||
String expression = "${" + getRemoteUserInput() + "}";
|
||||
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
|
||||
ValueExpression e = factory.createValueExpression(context, expression, Object.class);
|
||||
SimpleContext context = getContext();
|
||||
Object result = e.getValue(context);
|
||||
@@ -0,0 +1,44 @@
|
||||
class SensitiveCookieNotHttpOnly {
|
||||
// GOOD - Create a sensitive cookie with the `HttpOnly` flag set.
|
||||
public void addCookie(String jwt_token, HttpServletRequest request, HttpServletResponse response) {
|
||||
Cookie jwtCookie =new Cookie("jwt_token", jwt_token);
|
||||
jwtCookie.setPath("/");
|
||||
jwtCookie.setMaxAge(3600*24*7);
|
||||
jwtCookie.setHttpOnly(true);
|
||||
response.addCookie(jwtCookie);
|
||||
}
|
||||
|
||||
// BAD - Create a sensitive cookie without the `HttpOnly` flag set.
|
||||
public void addCookie2(String jwt_token, String userId, HttpServletRequest request, HttpServletResponse response) {
|
||||
Cookie jwtCookie =new Cookie("jwt_token", jwt_token);
|
||||
jwtCookie.setPath("/");
|
||||
jwtCookie.setMaxAge(3600*24*7);
|
||||
response.addCookie(jwtCookie);
|
||||
}
|
||||
|
||||
// GOOD - Set a sensitive cookie header with the `HttpOnly` flag set.
|
||||
public void addCookie3(String authId, HttpServletRequest request, HttpServletResponse response) {
|
||||
response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure");
|
||||
}
|
||||
|
||||
// BAD - Set a sensitive cookie header without the `HttpOnly` flag set.
|
||||
public void addCookie4(String authId, HttpServletRequest request, HttpServletResponse response) {
|
||||
response.addHeader("Set-Cookie", "token=" +authId + ";Secure");
|
||||
}
|
||||
|
||||
// GOOD - Set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through string concatenation.
|
||||
public void addCookie5(String accessKey, HttpServletRequest request, HttpServletResponse response) {
|
||||
response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true) + ";HttpOnly");
|
||||
}
|
||||
|
||||
// BAD - Set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` without the `HttpOnly` flag set.
|
||||
public void addCookie6(String accessKey, HttpServletRequest request, HttpServletResponse response) {
|
||||
response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true).toString());
|
||||
}
|
||||
|
||||
// GOOD - Set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through the constructor.
|
||||
public void addCookie7(String accessKey, HttpServletRequest request, HttpServletResponse response) {
|
||||
NewCookie accessKeyCookie = new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true);
|
||||
response.setHeader("Set-Cookie", accessKeyCookie.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The <code>HttpOnly</code> flag directs compatible browsers to prevent client-side script from accessing cookies. Including the <code>HttpOnly</code> flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Use the <code>HttpOnly</code> flag when generating a cookie containing sensitive information to help mitigate the risk of client side script accessing the protected cookie.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following example shows two ways of generating sensitive cookies. In the 'BAD' cases, the <code>HttpOnly</code> flag is not set. In the 'GOOD' cases, the <code>HttpOnly</code> flag is set.</p>
|
||||
<sample src="SensitiveCookieNotHttpOnly.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
PortSwigger:
|
||||
<a href="https://portswigger.net/kb/issues/00500600_cookie-without-httponly-flag-set">Cookie without HttpOnly flag set</a>
|
||||
</li>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://owasp.org/www-community/HttpOnly">HttpOnly</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* @name Sensitive cookies without the HttpOnly response header set
|
||||
* @description Sensitive cookies without the 'HttpOnly' flag set leaves session cookies vulnerable to
|
||||
* an XSS attack.
|
||||
* @kind path-problem
|
||||
* @id java/sensitive-cookie-not-httponly
|
||||
* @tags security
|
||||
* external/cwe/cwe-1004
|
||||
*/
|
||||
|
||||
/*
|
||||
* Sketch of the structure of this query: we track cookie names that appear to be sensitive
|
||||
* (e.g. `session` or `token`) to a `ServletResponse.addHeader(...)` or `.addCookie(...)`
|
||||
* method that does not set the `httpOnly` flag. Subsidiary configurations
|
||||
* `MatchesHttpOnlyConfiguration` and `SetHttpOnlyInCookieConfiguration` are used to establish
|
||||
* when the `httpOnly` flag is likely to have been set, before configuration
|
||||
* `MissingHttpOnlyConfiguration` establishes that a non-`httpOnly` cookie has a sensitive-seeming name.
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSteps
|
||||
import semmle.code.java.frameworks.Servlets
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.TaintTracking2
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/** Gets a regular expression for matching common names of sensitive cookies. */
|
||||
string getSensitiveCookieNameRegex() { result = "(?i).*(auth|session|token|key|credential).*" }
|
||||
|
||||
/** Gets a regular expression for matching CSRF cookies. */
|
||||
string getCsrfCookieNameRegex() { result = "(?i).*(csrf).*" }
|
||||
|
||||
/**
|
||||
* Holds if a string is concatenated with the name of a sensitive cookie. Excludes CSRF cookies since
|
||||
* they are special cookies implementing the Synchronizer Token Pattern that can be used in JavaScript.
|
||||
*/
|
||||
predicate isSensitiveCookieNameExpr(Expr expr) {
|
||||
exists(string s | s = expr.(CompileTimeConstantExpr).getStringValue() |
|
||||
s.regexpMatch(getSensitiveCookieNameRegex()) and not s.regexpMatch(getCsrfCookieNameRegex())
|
||||
)
|
||||
or
|
||||
isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
|
||||
}
|
||||
|
||||
/** A sensitive cookie name. */
|
||||
class SensitiveCookieNameExpr extends Expr {
|
||||
SensitiveCookieNameExpr() { isSensitiveCookieNameExpr(this) }
|
||||
}
|
||||
|
||||
/** A method call that sets a `Set-Cookie` header. */
|
||||
class SetCookieMethodAccess extends MethodAccess {
|
||||
SetCookieMethodAccess() {
|
||||
(
|
||||
this.getMethod() instanceof ResponseAddHeaderMethod or
|
||||
this.getMethod() instanceof ResponseSetHeaderMethod
|
||||
) and
|
||||
this.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "set-cookie"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration tracking flow from the text `httponly` to argument 1 of
|
||||
* `SetCookieMethodAccess`.
|
||||
*/
|
||||
class MatchesHttpOnlyConfiguration extends TaintTracking2::Configuration {
|
||||
MatchesHttpOnlyConfiguration() { this = "MatchesHttpOnlyConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr().(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%")
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink.asExpr() = any(SetCookieMethodAccess ma).getArgument(1)
|
||||
}
|
||||
}
|
||||
|
||||
/** A class descended from `javax.servlet.http.Cookie` or `javax/jakarta.ws.rs.core.Cookie`. */
|
||||
class CookieClass extends RefType {
|
||||
CookieClass() {
|
||||
this.getASupertype*()
|
||||
.hasQualifiedName(["javax.servlet.http", "javax.ws.rs.core", "jakarta.ws.rs.core"], "Cookie")
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `expr` is any boolean-typed expression other than literal `false`. */
|
||||
// Inlined because this could be a very large result set if computed out of context
|
||||
pragma[inline]
|
||||
predicate mayBeBooleanTrue(Expr expr) {
|
||||
expr.getType() instanceof BooleanType and
|
||||
not expr.(CompileTimeConstantExpr).getBooleanValue() = false
|
||||
}
|
||||
|
||||
/** Holds if the method call may set the `HttpOnly` flag. */
|
||||
predicate setsCookieHttpOnly(MethodAccess ma) {
|
||||
ma.getMethod().getName() = "setHttpOnly" and
|
||||
// any use of setHttpOnly(x) where x isn't false is probably safe
|
||||
mayBeBooleanTrue(ma.getArgument(0))
|
||||
}
|
||||
|
||||
/** Holds if `ma` removes a cookie. */
|
||||
predicate removesCookie(MethodAccess ma) {
|
||||
ma.getMethod().getName() = "setMaxAge" and
|
||||
ma.getArgument(0).(IntegerLiteral).getIntValue() = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the MethodAccess `ma` is a test method call 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(MethodAccess ma) {
|
||||
exists(Method m |
|
||||
m = ma.getEnclosingCallable() and
|
||||
(
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration tracking flow of a method that sets the `HttpOnly` flag,
|
||||
* or one that removes a cookie, to a `ServletResponse.addCookie` call.
|
||||
*/
|
||||
class SetHttpOnlyOrRemovesCookieConfiguration extends TaintTracking2::Configuration {
|
||||
SetHttpOnlyOrRemovesCookieConfiguration() { this = "SetHttpOnlyOrRemovesCookieConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr() =
|
||||
any(MethodAccess ma | setsCookieHttpOnly(ma) or removesCookie(ma)).getQualifier()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink.asExpr() =
|
||||
any(MethodAccess ma | ma.getMethod() instanceof ResponseAddCookieMethod).getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cookie that is added to an HTTP response and which doesn't have `httpOnly` set, used as a sink
|
||||
* in `MissingHttpOnlyConfiguration`.
|
||||
*/
|
||||
class CookieResponseSink extends DataFlow::ExprNode {
|
||||
CookieResponseSink() {
|
||||
exists(MethodAccess ma |
|
||||
(
|
||||
ma.getMethod() instanceof ResponseAddCookieMethod and
|
||||
this.getExpr() = ma.getArgument(0) and
|
||||
not exists(SetHttpOnlyOrRemovesCookieConfiguration cc | cc.hasFlowTo(this))
|
||||
or
|
||||
ma instanceof SetCookieMethodAccess and
|
||||
this.getExpr() = ma.getArgument(1) and
|
||||
not exists(MatchesHttpOnlyConfiguration cc | cc.hasFlowTo(this)) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
|
||||
) and
|
||||
not isTestMethod(ma) // Test class or method
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `cie` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
|
||||
predicate setsHttpOnlyInNewCookie(ClassInstanceExpr cie) {
|
||||
cie.getConstructedType().hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and
|
||||
(
|
||||
cie.getNumArgument() = 6 and
|
||||
mayBeBooleanTrue(cie.getArgument(5)) // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
|
||||
or
|
||||
cie.getNumArgument() = 8 and
|
||||
cie.getArgument(6).getType() instanceof BooleanType and
|
||||
mayBeBooleanTrue(cie.getArgument(7)) // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
|
||||
or
|
||||
cie.getNumArgument() = 10 and
|
||||
mayBeBooleanTrue(cie.getArgument(9)) // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration tracking flow from a sensitive cookie without the `HttpOnly` flag
|
||||
* set to its HTTP response.
|
||||
*/
|
||||
class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
|
||||
MissingHttpOnlyConfiguration() { this = "MissingHttpOnlyConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr() instanceof SensitiveCookieNameExpr
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof CookieResponseSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
// JAX-RS's `new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)` and similar
|
||||
setsHttpOnlyInNewCookie(node.asExpr())
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(
|
||||
ConstructorCall cc // new Cookie(...)
|
||||
|
|
||||
cc.getConstructedType() instanceof CookieClass and
|
||||
pred.asExpr() = cc.getAnArgument() and
|
||||
succ.asExpr() = cc
|
||||
)
|
||||
or
|
||||
exists(
|
||||
MethodAccess ma // cookie.toString()
|
||||
|
|
||||
ma.getMethod().getName() = "toString" and
|
||||
ma.getQualifier().getType() instanceof CookieClass and
|
||||
pred.asExpr() = ma.getQualifier() and
|
||||
succ.asExpr() = ma
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, MissingHttpOnlyConfiguration c
|
||||
where c.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ doesn't have the HttpOnly flag set.", source.getNode(),
|
||||
"This sensitive cookie"
|
||||
@@ -68,7 +68,7 @@ predicate isBooleanTrue(Expr expr) {
|
||||
or
|
||||
exists(MethodAccess ma |
|
||||
expr = ma and
|
||||
ma.getMethod().hasName("toString") and
|
||||
ma.getMethod() instanceof ToStringMethod and
|
||||
ma.getQualifier().(FieldAccess).getField().hasName("TRUE") and
|
||||
ma.getQualifier()
|
||||
.(FieldAccess)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/** Json string type data. */
|
||||
abstract class JsonStringSource extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* Convert to String using Gson library. *
|
||||
*
|
||||
* For example, in the method access `Gson.toJson(...)`,
|
||||
* the `Object` type data is converted to the `String` type data.
|
||||
*/
|
||||
private class GsonString extends JsonStringSource {
|
||||
GsonString() {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
m.hasName("toJson") and
|
||||
m.getDeclaringType().getASupertype*().hasQualifiedName("com.google.gson", "Gson") and
|
||||
this.asExpr() = ma
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to String using Fastjson library.
|
||||
*
|
||||
* For example, in the method access `JSON.toJSONString(...)`,
|
||||
* the `Object` type data is converted to the `String` type data.
|
||||
*/
|
||||
private class FastjsonString extends JsonStringSource {
|
||||
FastjsonString() {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
m.hasName("toJSONString") and
|
||||
m.getDeclaringType().getASupertype*().hasQualifiedName("com.alibaba.fastjson", "JSON") and
|
||||
this.asExpr() = ma
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to String using Jackson library.
|
||||
*
|
||||
* For example, in the method access `ObjectMapper.writeValueAsString(...)`,
|
||||
* the `Object` type data is converted to the `String` type data.
|
||||
*/
|
||||
private class JacksonString extends JsonStringSource {
|
||||
JacksonString() {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
m.hasName("writeValueAsString") and
|
||||
m.getDeclaringType()
|
||||
.getASupertype*()
|
||||
.hasQualifiedName("com.fasterxml.jackson.databind", "ObjectMapper") and
|
||||
this.asExpr() = ma
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.Gson;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Controller
|
||||
public class JsonpInjection {
|
||||
|
||||
private static HashMap hashMap = new HashMap();
|
||||
|
||||
static {
|
||||
hashMap.put("username","admin");
|
||||
hashMap.put("password","123456");
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp1")
|
||||
@ResponseBody
|
||||
public String bad1(HttpServletRequest request) {
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
Gson gson = new Gson();
|
||||
String result = gson.toJson(hashMap);
|
||||
resultStr = jsonpCallback + "(" + result + ")";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp2")
|
||||
@ResponseBody
|
||||
public String bad2(HttpServletRequest request) {
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp3")
|
||||
@ResponseBody
|
||||
public String bad3(HttpServletRequest request) {
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
String jsonStr = getJsonStr(hashMap);
|
||||
resultStr = jsonpCallback + "(" + jsonStr + ")";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp4")
|
||||
@ResponseBody
|
||||
public String bad4(HttpServletRequest request) {
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
String restr = JSONObject.toJSONString(hashMap);
|
||||
resultStr = jsonpCallback + "(" + restr + ");";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp5")
|
||||
@ResponseBody
|
||||
public void bad5(HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
PrintWriter pw = null;
|
||||
Gson gson = new Gson();
|
||||
String result = gson.toJson(hashMap);
|
||||
String resultStr = null;
|
||||
pw = response.getWriter();
|
||||
resultStr = jsonpCallback + "(" + result + ")";
|
||||
pw.println(resultStr);
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp6")
|
||||
@ResponseBody
|
||||
public void bad6(HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
PrintWriter pw = null;
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String result = mapper.writeValueAsString(hashMap);
|
||||
String resultStr = null;
|
||||
pw = response.getWriter();
|
||||
resultStr = jsonpCallback + "(" + result + ")";
|
||||
pw.println(resultStr);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "jsonp7", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public String bad7(HttpServletRequest request) {
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
Gson gson = new Gson();
|
||||
String result = gson.toJson(hashMap);
|
||||
resultStr = jsonpCallback + "(" + result + ")";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "jsonp11")
|
||||
@ResponseBody
|
||||
public String good1(HttpServletRequest request) {
|
||||
JSONObject parameterObj = readToJSONObect(request);
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
String restr = JSONObject.toJSONString(hashMap);
|
||||
resultStr = jsonpCallback + "(" + restr + ");";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "jsonp12")
|
||||
@ResponseBody
|
||||
public String good2(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
|
||||
if(null == file){
|
||||
return "upload file error";
|
||||
}
|
||||
String fileName = file.getOriginalFilename();
|
||||
System.out.println("file operations");
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
String restr = JSONObject.toJSONString(hashMap);
|
||||
resultStr = jsonpCallback + "(" + restr + ");";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
public static JSONObject readToJSONObect(HttpServletRequest request){
|
||||
String jsonText = readPostContent(request);
|
||||
JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
public static String readPostContent(HttpServletRequest request){
|
||||
BufferedReader in= null;
|
||||
String content = null;
|
||||
String line = null;
|
||||
try {
|
||||
in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
|
||||
StringBuilder buf = new StringBuilder();
|
||||
while ((line = in.readLine()) != null) {
|
||||
buf.append(line);
|
||||
}
|
||||
content = buf.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String uri = request.getRequestURI();
|
||||
return content;
|
||||
}
|
||||
|
||||
public static String getJsonStr(Object result) {
|
||||
return JSONObject.toJSONString(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>The software uses external input as the function name to wrap JSON data and returns it to the client as a request response.
|
||||
When there is a cross-domain problem, this could lead to information leakage.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Adding <code>Referer</code>/<code>Origin</code> or random <code>token</code> verification processing can effectively prevent the leakage of sensitive information.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>The following examples show the bad case and the good case respectively. Bad cases, such as <code>bad1</code> to <code>bad7</code>,
|
||||
will cause information leakage when there are cross-domain problems. In a good case, for example, in the <code>good1</code>
|
||||
method and the <code>good2</code> method, When these two methods process the request, there must be a request body in the request, which does not meet the conditions of Jsonp injection.</p>
|
||||
|
||||
<sample src="JsonpInjection.java" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
OWASPLondon20161124_JSON_Hijacking_Gareth_Heyes:
|
||||
<a href="https://owasp.org/www-chapter-london/assets/slides/OWASPLondon20161124_JSON_Hijacking_Gareth_Heyes.pdf">JSON hijacking</a>.
|
||||
</li>
|
||||
<li>
|
||||
Practical JSONP Injection:
|
||||
<a href="https://securitycafe.ro/2017/01/18/practical-jsonp-injection">
|
||||
Completely controllable from the URL (GET variable)
|
||||
</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @name JSONP Injection
|
||||
* @description User-controlled callback function names that are not verified are vulnerable
|
||||
* to jsonp injection attacks.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/jsonp-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-352
|
||||
*/
|
||||
|
||||
import java
|
||||
import JsonpInjectionLib
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.deadcode.WebEntryPoints
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/** Taint-tracking configuration tracing flow from get method request sources to output jsonp data. */
|
||||
class RequestResponseFlowConfig extends TaintTracking::Configuration {
|
||||
RequestResponseFlowConfig() { this = "RequestResponseFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source instanceof RemoteFlowSource and
|
||||
any(RequestGetMethod m).polyCalls*(source.getEnclosingCallable())
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink instanceof XssSink and
|
||||
any(RequestGetMethod m).polyCalls*(sink.getEnclosingCallable())
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(MethodAccess ma |
|
||||
isRequestGetParamMethod(ma) and pred.asExpr() = ma.getQualifier() and succ.asExpr() = ma
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, RequestResponseFlowConfig conf
|
||||
where
|
||||
conf.hasFlowPath(source, sink) and
|
||||
exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
|
||||
select sink.getNode(), source, sink, "Jsonp response might include code from $@.", source.getNode(),
|
||||
"this user input"
|
||||
@@ -0,0 +1,118 @@
|
||||
import java
|
||||
import DataFlow
|
||||
import JsonStringLib
|
||||
import semmle.code.java.security.XSS
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import semmle.code.java.dataflow.DataFlow3
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.frameworks.spring.SpringController
|
||||
|
||||
/**
|
||||
* A method that is called to handle an HTTP GET request.
|
||||
*/
|
||||
abstract class RequestGetMethod extends Method {
|
||||
RequestGetMethod() {
|
||||
not exists(MethodAccess ma |
|
||||
// Exclude apparent GET handlers that read a request entity, because this likely indicates this is not in fact a GET handler.
|
||||
// This is particularly a problem with Spring handlers, which can sometimes neglect to specify a request method.
|
||||
// Even if it is in fact a GET handler, such a request method will be unusable in the context `<script src="...">`,
|
||||
// which is the typical use-case for JSONP but cannot supply a request body.
|
||||
ma.getMethod() instanceof ServletRequestGetBodyMethod and
|
||||
this.polyCalls*(ma.getEnclosingCallable())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Override method of `doGet` of `Servlet` subclass. */
|
||||
private class ServletGetMethod extends RequestGetMethod {
|
||||
ServletGetMethod() { isServletRequestMethod(this) and this.getName() = "doGet" }
|
||||
}
|
||||
|
||||
/** The method of SpringController class processing `get` request. */
|
||||
abstract class SpringControllerGetMethod extends RequestGetMethod { }
|
||||
|
||||
/** Method using `GetMapping` annotation in SpringController class. */
|
||||
class SpringControllerGetMappingGetMethod extends SpringControllerGetMethod {
|
||||
SpringControllerGetMappingGetMethod() {
|
||||
this.getAnAnnotation()
|
||||
.getType()
|
||||
.hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
|
||||
}
|
||||
}
|
||||
|
||||
/** The method that uses the `RequestMapping` annotation in the SpringController class and only handles the get request. */
|
||||
class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod {
|
||||
SpringControllerRequestMappingGetMethod() {
|
||||
this.getAnAnnotation()
|
||||
.getType()
|
||||
.hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
|
||||
(
|
||||
this.getAnAnnotation().getValue("method").(VarAccess).getVariable().getName() = "GET" or
|
||||
this.getAnAnnotation().getValue("method").(ArrayInit).getSize() = 0 //Java code example: @RequestMapping(value = "test")
|
||||
) and
|
||||
not this.getAParamType().getName() = "MultipartFile"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A concatenate expression using `(` and `)` or `);`.
|
||||
*
|
||||
* E.g: `functionName + "(" + json + ")"` or `functionName + "(" + json + ");"`
|
||||
*/
|
||||
class JsonpBuilderExpr extends AddExpr {
|
||||
JsonpBuilderExpr() {
|
||||
getRightOperand().(CompileTimeConstantExpr).getStringValue().regexpMatch("\\);?") and
|
||||
getLeftOperand()
|
||||
.(AddExpr)
|
||||
.getLeftOperand()
|
||||
.(AddExpr)
|
||||
.getRightOperand()
|
||||
.(CompileTimeConstantExpr)
|
||||
.getStringValue() = "("
|
||||
}
|
||||
|
||||
/** Get the jsonp function name of this expression. */
|
||||
Expr getFunctionName() {
|
||||
result = getLeftOperand().(AddExpr).getLeftOperand().(AddExpr).getLeftOperand()
|
||||
}
|
||||
|
||||
/** Get the json data of this expression. */
|
||||
Expr getJsonExpr() { result = getLeftOperand().(AddExpr).getRightOperand() }
|
||||
}
|
||||
|
||||
/** A data flow configuration tracing flow from remote sources to jsonp function name. */
|
||||
class RemoteFlowConfig extends DataFlow2::Configuration {
|
||||
RemoteFlowConfig() { this = "RemoteFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(JsonpBuilderExpr jhe | jhe.getFunctionName() = sink.asExpr())
|
||||
}
|
||||
}
|
||||
|
||||
/** A data flow configuration tracing flow from json data into the argument `json` of JSONP-like string `someFunctionName + "(" + json + ")"`. */
|
||||
class JsonDataFlowConfig extends DataFlow2::Configuration {
|
||||
JsonDataFlowConfig() { this = "JsonDataFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src) { src instanceof JsonStringSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(JsonpBuilderExpr jhe | jhe.getJsonExpr() = sink.asExpr())
|
||||
}
|
||||
}
|
||||
|
||||
/** Taint-tracking configuration tracing flow from probable jsonp data with a user-controlled function name to an outgoing HTTP entity. */
|
||||
class JsonpInjectionFlowConfig extends TaintTracking::Configuration {
|
||||
JsonpInjectionFlowConfig() { this = "JsonpInjectionFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src) {
|
||||
exists(JsonpBuilderExpr jhe, JsonDataFlowConfig jdfc, RemoteFlowConfig rfc |
|
||||
jhe = src.asExpr() and
|
||||
jdfc.hasFlowTo(DataFlow::exprNode(jhe.getJsonExpr())) and
|
||||
rfc.hasFlowTo(DataFlow::exprNode(jhe.getFunctionName()))
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Credentials management issues occur when credentials are stored in plaintext in
|
||||
an application's properties file. Common credentials include but are not limited
|
||||
to LDAP, mail, database, proxy account, and so on. Storing plaintext credentials
|
||||
in a properties file allows anyone who can read the file access to the protected
|
||||
resource. Good credentials management guidelines require that credentials never
|
||||
be stored in plaintext.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Credentials stored in properties files should be encrypted and recycled regularly.
|
||||
In a Java EE deployment scenario, utilities provided by application servers like
|
||||
keystores and password vaults can be used to encrypt and manage credentials.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the first example, the credentials for the LDAP and datasource properties are stored
|
||||
in cleartext in the properties file.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the second example, the credentials for the LDAP and datasource properties are stored
|
||||
in an encrypted format.
|
||||
</p>
|
||||
<sample src="configuration.properties" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://owasp.org/www-community/vulnerabilities/Password_Plaintext_Storage">Password Plaintext Storage</a>
|
||||
</li>
|
||||
<li>
|
||||
Medium (Rajeev Shukla):
|
||||
<a href="https://medium.com/@mail2rajeevshukla/hiding-encrypting-database-password-in-the-application-properties-34d59fe104eb">Encrypting database password in the application.properties file</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @name Cleartext Credentials in Properties File
|
||||
* @description Finds cleartext credentials in Java properties files.
|
||||
* @kind problem
|
||||
* @id java/credentials-in-properties
|
||||
* @tags security
|
||||
* external/cwe/cwe-555
|
||||
* external/cwe/cwe-256
|
||||
* external/cwe/cwe-260
|
||||
*/
|
||||
|
||||
/*
|
||||
* Note this query requires properties files to be indexed before it can produce results.
|
||||
* If creating your own database with the CodeQL CLI, you should run
|
||||
* `codeql database index-files --language=properties ...`
|
||||
* If using lgtm.com, you should add `properties_files: true` to the index block of your
|
||||
* lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction#customizing-index)
|
||||
*/
|
||||
|
||||
import java
|
||||
import experimental.semmle.code.java.frameworks.CredentialsInPropertiesFile
|
||||
|
||||
/**
|
||||
* Holds if the credentials are in a non-production properties file indicated by:
|
||||
* a) in a non-production directory
|
||||
* b) with a non-production file name
|
||||
*/
|
||||
predicate isNonProdCredentials(CredentialsConfig cc) {
|
||||
cc.getFile().getAbsolutePath().matches(["%dev%", "%test%", "%sample%"])
|
||||
}
|
||||
|
||||
from CredentialsConfig cc
|
||||
where not isNonProdCredentials(cc)
|
||||
select cc, cc.getConfigDesc()
|
||||
@@ -0,0 +1,26 @@
|
||||
#***************************** LDAP Credentials *****************************************#
|
||||
|
||||
ldap.ldapHost = ldap.example.com
|
||||
ldap.ldapPort = 636
|
||||
ldap.loginDN = cn=Directory Manager
|
||||
|
||||
#### BAD: LDAP credentials are stored in cleartext ####
|
||||
ldap.password = mysecpass
|
||||
|
||||
#### GOOD: LDAP credentials are stored in the encrypted format ####
|
||||
ldap.password = eFRZ3Cqo5zDJWMYLiaEupw==
|
||||
|
||||
ldap.domain1 = example
|
||||
ldap.domain2 = com
|
||||
ldap.url= ldaps://ldap.example.com:636/dc=example,dc=com
|
||||
|
||||
#*************************** MS SQL Database Connection **********************************#
|
||||
datasource1.driverClassName = com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
datasource1.url = jdbc:sqlserver://ms.example.com\\exampledb:1433;
|
||||
datasource1.username = sa
|
||||
|
||||
#### BAD: Datasource credentials are stored in cleartext ####
|
||||
datasource1.password = Passw0rd@123
|
||||
|
||||
#### GOOD: Datasource credentials are stored in the encrypted format ####
|
||||
datasource1.password = VvOgflYS1EUzJdVNDoBcnA==
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Provides classes for analyzing properties files.
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.configfiles.ConfigFiles
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.frameworks.Properties
|
||||
|
||||
private string possibleSecretName() {
|
||||
result =
|
||||
[
|
||||
"%password%", "%passwd%", "%account%", "%accnt%", "%credential%", "%token%", "%secret%",
|
||||
"%access%key%"
|
||||
]
|
||||
}
|
||||
|
||||
private string possibleEncryptedSecretName() { result = ["%hashed%", "%encrypted%", "%crypt%"] }
|
||||
|
||||
/** Holds if the value is not cleartext credentials. */
|
||||
bindingset[value]
|
||||
predicate isNotCleartextCredentials(string value) {
|
||||
value = "" // Empty string
|
||||
or
|
||||
value.length() < 7 // Typical credentials are no less than 6 characters
|
||||
or
|
||||
value.matches("% %") // Sentences containing spaces
|
||||
or
|
||||
value.regexpMatch(".*[^a-zA-Z\\d]{3,}.*") // Contain repeated non-alphanumeric characters such as a fake password pass**** or ????
|
||||
or
|
||||
value.matches("@%") // Starts with the "@" sign
|
||||
or
|
||||
value.regexpMatch("\\$\\{.*\\}") // Variable placeholder ${credentials}
|
||||
or
|
||||
value.matches("%=") // A basic check of encrypted credentials ending with padding characters
|
||||
or
|
||||
value.matches("ENC(%)") // Encrypted value
|
||||
or
|
||||
// Could be a message property for UI display or fake passwords, e.g. login.password_expired=Your current password has expired.
|
||||
value.toLowerCase().matches(possibleSecretName())
|
||||
}
|
||||
|
||||
/** A configuration property that appears to contain a cleartext secret. */
|
||||
class CredentialsConfig extends ConfigPair {
|
||||
CredentialsConfig() {
|
||||
this.getNameElement().getName().trim().toLowerCase().matches(possibleSecretName()) and
|
||||
not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName()) and
|
||||
not isNotCleartextCredentials(this.getValueElement().getValue().trim())
|
||||
}
|
||||
|
||||
/** Gets the whitespace-trimmed name of this property. */
|
||||
string getName() { result = this.getNameElement().getName().trim() }
|
||||
|
||||
/** Gets the whitespace-trimmed value of this property. */
|
||||
string getValue() { result = this.getValueElement().getValue().trim() }
|
||||
|
||||
/** Returns a description of this vulnerability. */
|
||||
string getConfigDesc() {
|
||||
result =
|
||||
"Plaintext credentials " + this.getName() + " have cleartext value " + this.getValue() +
|
||||
" in properties file"
|
||||
}
|
||||
}
|
||||
@@ -159,6 +159,8 @@ class Folder extends Container, @folder {
|
||||
|
||||
/** Gets the URL of this folder. */
|
||||
override string getURL() { result = "folder://" + getAbsolutePath() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "Folder" }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,6 +173,8 @@ class File extends Container, @file {
|
||||
|
||||
/** Gets the URL of this file. */
|
||||
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "File" }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,4 +208,6 @@ class JarFile extends File {
|
||||
string getManifestEntryAttribute(string entry, string key) {
|
||||
jarManifestEntries(this, entry, key, result)
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "JarFile" }
|
||||
}
|
||||
|
||||
@@ -27,4 +27,6 @@ class Exception extends Element, @exception {
|
||||
override predicate hasName(string name) { this.getType().hasName(name) }
|
||||
|
||||
override string toString() { result = this.getType().toString() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "Exception" }
|
||||
}
|
||||
|
||||
@@ -638,7 +638,21 @@ class BooleanLiteral extends Literal, @booleanliteral {
|
||||
override string getAPrimaryQlClass() { result = "BooleanLiteral" }
|
||||
}
|
||||
|
||||
/** An integer literal. For example, `23`. */
|
||||
/**
|
||||
* An integer literal. For example, `23`.
|
||||
*
|
||||
* An integer literal can never be negative except when:
|
||||
* - It is written in binary, octal or hexadecimal notation
|
||||
* - It is written in decimal notation, has the value `2147483648` and is preceded
|
||||
* by a minus; in this case the value of the IntegerLiteral is -2147483648 and
|
||||
* the preceding minus will *not* be modeled as `MinusExpr`.
|
||||
*
|
||||
* In all other cases the preceding minus, if any, will be modeled as a separate
|
||||
* `MinusExpr`.
|
||||
*
|
||||
* The last exception is necessary because `2147483648` on its own would not be
|
||||
* a valid integer literal (and could also not be parsed as CodeQL `int`).
|
||||
*/
|
||||
class IntegerLiteral extends Literal, @integerliteral {
|
||||
/** Gets the int representation of this literal. */
|
||||
int getIntValue() { result = getValue().toInt() }
|
||||
@@ -646,12 +660,32 @@ class IntegerLiteral extends Literal, @integerliteral {
|
||||
override string getAPrimaryQlClass() { result = "IntegerLiteral" }
|
||||
}
|
||||
|
||||
/** A long literal. For example, `23l`. */
|
||||
/**
|
||||
* A long literal. For example, `23L`.
|
||||
*
|
||||
* A long literal can never be negative except when:
|
||||
* - It is written in binary, octal or hexadecimal notation
|
||||
* - It is written in decimal notation, has the value `9223372036854775808` and
|
||||
* is preceded by a minus; in this case the value of the LongLiteral is
|
||||
* -9223372036854775808 and the preceding minus will *not* be modeled as
|
||||
* `MinusExpr`.
|
||||
*
|
||||
* In all other cases the preceding minus, if any, will be modeled as a separate
|
||||
* `MinusExpr`.
|
||||
*
|
||||
* The last exception is necessary because `9223372036854775808` on its own
|
||||
* would not be a valid long literal.
|
||||
*/
|
||||
class LongLiteral extends Literal, @longliteral {
|
||||
override string getAPrimaryQlClass() { result = "LongLiteral" }
|
||||
}
|
||||
|
||||
/** A floating point literal. For example, `4.2f`. */
|
||||
/**
|
||||
* A float literal. For example, `4.2f`.
|
||||
*
|
||||
* A float literal is never negative; a preceding minus, if any, will always
|
||||
* be modeled as separate `MinusExpr`.
|
||||
*/
|
||||
class FloatingPointLiteral extends Literal, @floatingpointliteral {
|
||||
/**
|
||||
* Gets the value of this literal as CodeQL 64-bit `float`. The value will
|
||||
@@ -662,7 +696,12 @@ class FloatingPointLiteral extends Literal, @floatingpointliteral {
|
||||
override string getAPrimaryQlClass() { result = "FloatingPointLiteral" }
|
||||
}
|
||||
|
||||
/** A double literal. For example, `4.2`. */
|
||||
/**
|
||||
* A double literal. For example, `4.2`.
|
||||
*
|
||||
* A double literal is never negative; a preceding minus, if any, will always
|
||||
* be modeled as separate `MinusExpr`.
|
||||
*/
|
||||
class DoubleLiteral extends Literal, @doubleliteral {
|
||||
/**
|
||||
* Gets the value of this literal as CodeQL 64-bit `float`. The result will
|
||||
@@ -1769,7 +1808,7 @@ class WildcardTypeAccess extends Expr, @wildcardtypeaccess {
|
||||
* This includes method calls, constructor and super constructor invocations,
|
||||
* and constructors invoked through class instantiation.
|
||||
*/
|
||||
class Call extends Top, @caller {
|
||||
class Call extends ExprParent, @caller {
|
||||
/** Gets an argument supplied in this call. */
|
||||
/*abstract*/ Expr getAnArgument() { none() }
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import Member
|
||||
import semmle.code.java.security.ExternalProcess
|
||||
|
||||
// --- Standard types ---
|
||||
/** The class `java.lang.Object`. */
|
||||
@@ -176,24 +177,37 @@ class TypeFile extends Class {
|
||||
}
|
||||
|
||||
// --- Standard methods ---
|
||||
/**
|
||||
* Any constructor of class `java.lang.ProcessBuilder`.
|
||||
*/
|
||||
class ProcessBuilderConstructor extends Constructor, ExecCallable {
|
||||
ProcessBuilderConstructor() { this.getDeclaringType() instanceof TypeProcessBuilder }
|
||||
|
||||
override int getAnExecutedArgument() { result = 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Any of the methods named `command` on class `java.lang.ProcessBuilder`.
|
||||
*/
|
||||
class MethodProcessBuilderCommand extends Method {
|
||||
class MethodProcessBuilderCommand extends Method, ExecCallable {
|
||||
MethodProcessBuilderCommand() {
|
||||
hasName("command") and
|
||||
getDeclaringType() instanceof TypeProcessBuilder
|
||||
}
|
||||
|
||||
override int getAnExecutedArgument() { result = 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Any method named `exec` on class `java.lang.Runtime`.
|
||||
*/
|
||||
class MethodRuntimeExec extends Method {
|
||||
class MethodRuntimeExec extends Method, ExecCallable {
|
||||
MethodRuntimeExec() {
|
||||
hasName("exec") and
|
||||
getDeclaringType() instanceof TypeRuntime
|
||||
}
|
||||
|
||||
override int getAnExecutedArgument() { result = 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -353,7 +367,7 @@ class EqualsMethod extends Method {
|
||||
class HashCodeMethod extends Method {
|
||||
HashCodeMethod() {
|
||||
this.hasName("hashCode") and
|
||||
this.getNumberOfParameters() = 0
|
||||
this.hasNoParameters()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,6 +379,14 @@ class CloneMethod extends Method {
|
||||
}
|
||||
}
|
||||
|
||||
/** A method with the same signature as `java.lang.Object.toString`. */
|
||||
class ToStringMethod extends Method {
|
||||
ToStringMethod() {
|
||||
this.hasName("toString") and
|
||||
this.hasNoParameters()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The public static `main` method, with a single formal parameter
|
||||
* of type `String[]` and return type `void`.
|
||||
|
||||
@@ -8,6 +8,8 @@ import Element
|
||||
class Modifier extends Element, @modifier {
|
||||
/** Gets the element to which this modifier applies. */
|
||||
Element getElement() { hasModifier(result, this) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "Modifier" }
|
||||
}
|
||||
|
||||
/** An element of the Java syntax tree that may have a modifier. */
|
||||
@@ -16,7 +18,9 @@ abstract class Modifiable extends Element {
|
||||
* Holds if this element has modifier `m`.
|
||||
*
|
||||
* For most purposes, the more specialized predicates `isAbstract`, `isPublic`, etc.
|
||||
* should be used, which also take implicit modifiers into account.
|
||||
* should be used.
|
||||
*
|
||||
* Both this method and those specialized predicates take implicit modifiers into account.
|
||||
* For instance, non-default instance methods in interfaces are implicitly
|
||||
* abstract, so `isAbstract()` will hold for them even if `hasModifier("abstract")`
|
||||
* does not.
|
||||
|
||||
@@ -29,4 +29,6 @@ class Package extends Element, Annotatable, @package {
|
||||
* since packages do not have locations.
|
||||
*/
|
||||
string getURL() { result = "file://:0:0:0:0" }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "Package" }
|
||||
}
|
||||
|
||||
@@ -433,9 +433,7 @@ final class ClassInterfaceNode extends ElementNode {
|
||||
or
|
||||
result.(FieldDeclaration).getAField().getDeclaringType() = ty
|
||||
or
|
||||
result.(NestedType).getEnclosingType().getSourceDeclaration() = ty and
|
||||
not result instanceof AnonymousClass and
|
||||
not result instanceof LocalClass
|
||||
result.(MemberType).getEnclosingType().getSourceDeclaration() = ty
|
||||
or
|
||||
isInitBlock(ty, result)
|
||||
}
|
||||
|
||||
@@ -517,7 +517,12 @@ class RefType extends Type, Annotatable, Modifiable, @reftype {
|
||||
/** Holds if this is a top-level type, which is not nested inside any other types. */
|
||||
predicate isTopLevel() { this instanceof TopLevelType }
|
||||
|
||||
/** Holds if this type is declared in a specified package with the specified name. */
|
||||
/**
|
||||
* Holds if this type is declared in a specified package with the specified name.
|
||||
*
|
||||
* For nested types the name of the nested type is prefixed with a `$` and appended
|
||||
* to the name of the enclosing type, which might be a nested type as well.
|
||||
*/
|
||||
predicate hasQualifiedName(string package, string type) {
|
||||
this.getPackage().hasName(package) and type = this.nestedName()
|
||||
}
|
||||
@@ -532,7 +537,12 @@ class RefType extends Type, Annotatable, Modifiable, @reftype {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the qualified name of this type.
|
||||
* Gets the qualified name of this type, consisting of the package name followed by
|
||||
* a `.` and the name of this type.
|
||||
*
|
||||
* For nested types the name of the nested type is prefixed with a `$` and appended
|
||||
* to the name of the enclosing type, which might be a nested type as well. For example:
|
||||
* `java.lang.Thread$State`.
|
||||
*/
|
||||
string getQualifiedName() {
|
||||
exists(string pkgName | pkgName = getPackage().getName() |
|
||||
@@ -540,7 +550,13 @@ class RefType extends Type, Annotatable, Modifiable, @reftype {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the nested name of this type. */
|
||||
/**
|
||||
* Gets the nested name of this type.
|
||||
*
|
||||
* If this type is not a nested type, the result is the same as `getName()`.
|
||||
* Otherwise the name of the nested type is prefixed with a `$` and appended to
|
||||
* the name of the enclosing type, which might be a nested type as well.
|
||||
*/
|
||||
string nestedName() {
|
||||
not this instanceof NestedType and result = this.getName()
|
||||
or
|
||||
@@ -788,6 +804,21 @@ class NestedType extends RefType {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A nested type which is a direct member of the enclosing type,
|
||||
* that is, neither an anonymous nor local class.
|
||||
*/
|
||||
class MemberType extends NestedType, Member {
|
||||
/**
|
||||
* Gets the qualified name of this member type.
|
||||
*
|
||||
* The qualified name consists of the package name, a `.`, the name of the declaring
|
||||
* type (which might be a nested or member type as well), followed by a `$` and the
|
||||
* name of this member type. For example: `java.lang.Thread$State`.
|
||||
*/
|
||||
override string getQualifiedName() { result = NestedType.super.getQualifiedName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class declared within another type.
|
||||
*
|
||||
@@ -797,8 +828,9 @@ class NestedType extends RefType {
|
||||
class NestedClass extends NestedType, Class { }
|
||||
|
||||
/**
|
||||
* An inner class is a nested class that is neither
|
||||
* explicitly nor implicitly declared static.
|
||||
* An inner class is a nested class that is neither explicitly nor
|
||||
* implicitly declared static. This includes anonymous and local
|
||||
* classes.
|
||||
*/
|
||||
class InnerClass extends NestedClass {
|
||||
InnerClass() { not this.isStatic() }
|
||||
|
||||
@@ -53,6 +53,8 @@ class LocalVariableDecl extends @localvar, LocalScopeVariable {
|
||||
|
||||
/** Gets the initializer expression of this local variable declaration. */
|
||||
override Expr getInitializer() { result = getDeclExpr().getInit() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "LocalVariableDecl" }
|
||||
}
|
||||
|
||||
/** A formal parameter of a callable. */
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
import java
|
||||
private import semmle.code.java.dataflow.DataFlow::DataFlow
|
||||
private import internal.DataFlowPrivate
|
||||
private import FlowSummary
|
||||
|
||||
/**
|
||||
* A module importing the frameworks that provide external flow data,
|
||||
@@ -78,6 +79,7 @@ private module Frameworks {
|
||||
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
|
||||
}
|
||||
|
||||
private predicate sourceModelCsv(string row) {
|
||||
@@ -487,7 +489,8 @@ module CsvValidation {
|
||||
summaryModel(_, _, _, _, _, _, input, _, _) and pred = "summary"
|
||||
|
|
||||
specSplit(input, part, _) and
|
||||
not part.regexpMatch("|Argument|ReturnValue") and
|
||||
not part.regexpMatch("|ReturnValue|ArrayElement|Element|MapKey|MapValue") and
|
||||
not (part = "Argument" and pred = "sink") and
|
||||
not parseArg(part, _) and
|
||||
msg = "Unrecognized input specification \"" + part + "\" in " + pred + " model."
|
||||
)
|
||||
@@ -498,7 +501,8 @@ module CsvValidation {
|
||||
summaryModel(_, _, _, _, _, _, _, output, _) and pred = "summary"
|
||||
|
|
||||
specSplit(output, part, _) and
|
||||
not part.regexpMatch("|Argument|Parameter|ReturnValue") and
|
||||
not part.regexpMatch("|ReturnValue|ArrayElement|Element|MapKey|MapValue") and
|
||||
not (part = ["Argument", "Parameter"] and pred = "source") and
|
||||
not parseArg(part, _) and
|
||||
not parseParam(part, _) and
|
||||
msg = "Unrecognized output specification \"" + part + "\" in " + pred + " model."
|
||||
@@ -703,6 +707,75 @@ private predicate summaryElementRef(Top ref, string input, string output, string
|
||||
)
|
||||
}
|
||||
|
||||
private SummaryComponent interpretComponent(string c) {
|
||||
specSplit(_, c, _) and
|
||||
(
|
||||
exists(int pos | parseArg(c, pos) and result = SummaryComponent::argument(pos))
|
||||
or
|
||||
exists(int pos | parseParam(c, pos) and result = SummaryComponent::parameter(pos))
|
||||
or
|
||||
c = "ReturnValue" and result = SummaryComponent::return()
|
||||
or
|
||||
c = "ArrayElement" and result = SummaryComponent::content(any(ArrayContent c0))
|
||||
or
|
||||
c = "Element" and result = SummaryComponent::content(any(CollectionContent c0))
|
||||
or
|
||||
c = "MapKey" and result = SummaryComponent::content(any(MapKeyContent c0))
|
||||
or
|
||||
c = "MapValue" and result = SummaryComponent::content(any(MapValueContent c0))
|
||||
)
|
||||
}
|
||||
|
||||
private predicate interpretSpec(string spec, int idx, SummaryComponentStack stack) {
|
||||
exists(string c |
|
||||
summaryElement(_, spec, _, _) or
|
||||
summaryElement(_, _, spec, _)
|
||||
|
|
||||
len(spec, idx + 1) and
|
||||
specSplit(spec, c, idx) and
|
||||
stack = SummaryComponentStack::singleton(interpretComponent(c))
|
||||
)
|
||||
or
|
||||
exists(SummaryComponent head, SummaryComponentStack tail |
|
||||
interpretSpec(spec, idx, head, tail) and
|
||||
stack = SummaryComponentStack::push(head, tail)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate interpretSpec(
|
||||
string output, int idx, SummaryComponent head, SummaryComponentStack tail
|
||||
) {
|
||||
exists(string c |
|
||||
interpretSpec(output, idx + 1, tail) and
|
||||
specSplit(output, c, idx) and
|
||||
head = interpretComponent(c)
|
||||
)
|
||||
}
|
||||
|
||||
private class MkStack extends RequiredSummaryComponentStack {
|
||||
MkStack() { interpretSpec(_, _, _, this) }
|
||||
|
||||
override predicate required(SummaryComponent c) { interpretSpec(_, _, c, this) }
|
||||
}
|
||||
|
||||
private class SummarizedCallableExternal extends SummarizedCallable {
|
||||
SummarizedCallableExternal() { summaryElement(this, _, _, _) }
|
||||
|
||||
override predicate propagatesFlow(
|
||||
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
) {
|
||||
exists(string inSpec, string outSpec, string kind |
|
||||
summaryElement(this, inSpec, outSpec, kind) and
|
||||
interpretSpec(inSpec, 0, input) and
|
||||
interpretSpec(outSpec, 0, output)
|
||||
|
|
||||
kind = "value" and preservesValue = true
|
||||
or
|
||||
kind = "taint" and preservesValue = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TAstOrNode =
|
||||
TAst(Top t) or
|
||||
TNode(Node n)
|
||||
@@ -789,15 +862,3 @@ predicate sinkNode(Node node, string kind) {
|
||||
interpretInput(input, 0, ref, TNode(node))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node1` to `node2` is specified as a flow step with the given kind
|
||||
* in a CSV flow model.
|
||||
*/
|
||||
predicate summaryStep(Node node1, Node node2, string kind) {
|
||||
exists(Top ref, string input, string output |
|
||||
summaryElementRef(ref, input, output, kind) and
|
||||
interpretInput(input, 0, ref, TNode(node1)) and
|
||||
interpretOutput(output, 0, ref, TNode(node2))
|
||||
)
|
||||
}
|
||||
|
||||
51
java/ql/src/semmle/code/java/dataflow/FlowSummary.qll
Normal file
51
java/ql/src/semmle/code/java/dataflow/FlowSummary.qll
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Provides classes and predicates for definining flow summaries.
|
||||
*/
|
||||
|
||||
import java
|
||||
private import internal.FlowSummaryImpl as Impl
|
||||
private import internal.DataFlowDispatch
|
||||
private import internal.DataFlowPrivate
|
||||
|
||||
// import all instances of SummarizedCallable below
|
||||
private module Summaries {
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
}
|
||||
|
||||
class SummaryComponent = Impl::Public::SummaryComponent;
|
||||
|
||||
/** Provides predicates for constructing summary components. */
|
||||
module SummaryComponent {
|
||||
import Impl::Public::SummaryComponent
|
||||
|
||||
/** Gets a summary component that represents a qualifier. */
|
||||
SummaryComponent qualifier() { result = argument(-1) }
|
||||
|
||||
/** Gets a summary component for field `f`. */
|
||||
SummaryComponent field(Field f) { result = content(any(FieldContent c | c.getField() = f)) }
|
||||
|
||||
/** Gets a summary component that represents the return value of a call. */
|
||||
SummaryComponent return() { result = return(_) }
|
||||
}
|
||||
|
||||
class SummaryComponentStack = Impl::Public::SummaryComponentStack;
|
||||
|
||||
/** Provides predicates for constructing stacks of summary components. */
|
||||
module SummaryComponentStack {
|
||||
import Impl::Public::SummaryComponentStack
|
||||
|
||||
/** Gets a singleton stack representing a qualifier. */
|
||||
SummaryComponentStack qualifier() { result = singleton(SummaryComponent::qualifier()) }
|
||||
|
||||
/** Gets a stack representing a field `f` of `object`. */
|
||||
SummaryComponentStack fieldOf(Field f, SummaryComponentStack object) {
|
||||
result = push(SummaryComponent::field(f), object)
|
||||
}
|
||||
|
||||
/** Gets a singleton stack representing a (normal) return. */
|
||||
SummaryComponentStack return() { result = singleton(SummaryComponent::return()) }
|
||||
}
|
||||
|
||||
class SummarizedCallable = Impl::Public::SummarizedCallable;
|
||||
|
||||
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
|
||||
@@ -2,9 +2,17 @@ private import java
|
||||
private import DataFlowPrivate
|
||||
private import DataFlowUtil
|
||||
private import semmle.code.java.dataflow.InstanceAccess
|
||||
import semmle.code.java.dispatch.VirtualDispatch
|
||||
private import semmle.code.java.dataflow.FlowSummary
|
||||
private import semmle.code.java.dispatch.VirtualDispatch as VirtualDispatch
|
||||
|
||||
private module DispatchImpl {
|
||||
/** Gets a viable implementation of the target of the given `Call`. */
|
||||
Callable viableCallable(Call c) {
|
||||
result = VirtualDispatch::viableCallable(c)
|
||||
or
|
||||
result.(SummarizedCallable) = c.getCallee().getSourceDeclaration()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the set of viable implementations that can be called by `ma`
|
||||
* might be improved by knowing the call context. This is the case if the
|
||||
@@ -12,7 +20,7 @@ private module DispatchImpl {
|
||||
*/
|
||||
private predicate mayBenefitFromCallContext(MethodAccess ma, Callable c, int i) {
|
||||
exists(Parameter p |
|
||||
2 <= strictcount(viableImpl(ma)) and
|
||||
2 <= strictcount(VirtualDispatch::viableImpl(ma)) and
|
||||
ma.getQualifier().(VarAccess).getVariable() = p and
|
||||
p.getPosition() = i and
|
||||
c.getAParameter() = p and
|
||||
@@ -21,7 +29,7 @@ private module DispatchImpl {
|
||||
)
|
||||
or
|
||||
exists(OwnInstanceAccess ia |
|
||||
2 <= strictcount(viableImpl(ma)) and
|
||||
2 <= strictcount(VirtualDispatch::viableImpl(ma)) and
|
||||
(ia.isExplicit(ma.getQualifier()) or ia.isImplicitMethodQualifier(ma)) and
|
||||
i = -1 and
|
||||
c = ma.getEnclosingCallable()
|
||||
@@ -60,7 +68,7 @@ private module DispatchImpl {
|
||||
or
|
||||
ctx.getArgument(i) = arg
|
||||
|
|
||||
src = variableTrack(arg) and
|
||||
src = VirtualDispatch::variableTrack(arg) and
|
||||
srctype = getPreciseType(src) and
|
||||
if src instanceof ClassInstanceExpr then exact = true else exact = false
|
||||
)
|
||||
@@ -93,18 +101,18 @@ private module DispatchImpl {
|
||||
* restricted to those `ma`s for which a context might make a difference.
|
||||
*/
|
||||
Method viableImplInCallContext(MethodAccess ma, Call ctx) {
|
||||
result = viableImpl(ma) and
|
||||
result = VirtualDispatch::viableImpl(ma) and
|
||||
exists(int i, Callable c, Method def, RefType t, boolean exact |
|
||||
mayBenefitFromCallContext(ma, c, i) and
|
||||
c = viableCallable(ctx) and
|
||||
contextArgHasType(ctx, i, t, exact) and
|
||||
ma.getMethod() = def
|
||||
|
|
||||
exact = true and result = exactMethodImpl(def, t.getSourceDeclaration())
|
||||
exact = true and result = VirtualDispatch::exactMethodImpl(def, t.getSourceDeclaration())
|
||||
or
|
||||
exact = false and
|
||||
exists(RefType t2 |
|
||||
result = viableMethodImpl(def, t.getSourceDeclaration(), t2) and
|
||||
result = VirtualDispatch::viableMethodImpl(def, t.getSourceDeclaration(), t2) and
|
||||
not failsUnification(t, t2)
|
||||
)
|
||||
)
|
||||
@@ -117,7 +125,7 @@ private module DispatchImpl {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate unificationTargetRight(ParameterizedType t2, GenericType g) {
|
||||
exists(viableMethodImpl(_, _, t2)) and t2.getGenericType() = g
|
||||
exists(VirtualDispatch::viableMethodImpl(_, _, t2)) and t2.getGenericType() = g
|
||||
}
|
||||
|
||||
private predicate unificationTargets(Type t1, Type t2) {
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
445
java/ql/src/semmle/code/java/dataflow/internal/DataFlowNodes.qll
Normal file
445
java/ql/src/semmle/code/java/dataflow/internal/DataFlowNodes.qll
Normal file
@@ -0,0 +1,445 @@
|
||||
private import java
|
||||
private import semmle.code.java.dataflow.InstanceAccess
|
||||
private import semmle.code.java.dataflow.FlowSummary
|
||||
private import semmle.code.java.dataflow.TypeFlow
|
||||
private import DataFlowPrivate
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
newtype TNode =
|
||||
TExprNode(Expr e) {
|
||||
not e.getType() instanceof VoidType and
|
||||
not e.getParent*() instanceof Annotation
|
||||
} or
|
||||
TExplicitParameterNode(Parameter p) {
|
||||
exists(p.getCallable().getBody()) or p.getCallable() instanceof SummarizedCallable
|
||||
} or
|
||||
TImplicitVarargsArray(Call c) {
|
||||
c.getCallee().isVarargs() and
|
||||
not exists(Argument arg | arg.getCall() = c and arg.isExplicitVarargsArray())
|
||||
} or
|
||||
TInstanceParameterNode(Callable c) {
|
||||
(exists(c.getBody()) or c instanceof SummarizedCallable) and
|
||||
not c.isStatic()
|
||||
} or
|
||||
TImplicitInstanceAccess(InstanceAccessExt ia) { not ia.isExplicit(_) } or
|
||||
TMallocNode(ClassInstanceExpr cie) or
|
||||
TExplicitExprPostUpdate(Expr e) {
|
||||
explicitInstanceArgument(_, e)
|
||||
or
|
||||
e instanceof Argument and not e.getType() instanceof ImmutableType
|
||||
or
|
||||
exists(FieldAccess fa | fa.getField() instanceof InstanceField and e = fa.getQualifier())
|
||||
or
|
||||
exists(ArrayAccess aa | e = aa.getArray())
|
||||
} or
|
||||
TImplicitExprPostUpdate(InstanceAccessExt ia) {
|
||||
implicitInstanceArgument(_, ia)
|
||||
or
|
||||
exists(FieldAccess fa |
|
||||
fa.getField() instanceof InstanceField and ia.isImplicitFieldQualifier(fa)
|
||||
)
|
||||
} or
|
||||
TSummaryInternalNode(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) {
|
||||
FlowSummaryImpl::Private::summaryNodeRange(c, state)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate summaryOutNodeCached(DataFlowCall c, Node out) {
|
||||
FlowSummaryImpl::Private::summaryOutNode(c, out, _)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate summaryArgumentNodeCached(DataFlowCall c, Node arg, int i) {
|
||||
FlowSummaryImpl::Private::summaryArgumentNode(c, arg, i)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate summaryPostUpdateNodeCached(Node post, ParameterNode pre) {
|
||||
FlowSummaryImpl::Private::summaryPostUpdateNode(post, pre)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate summaryReturnNodeCached(Node ret) {
|
||||
FlowSummaryImpl::Private::summaryReturnNode(ret, _)
|
||||
}
|
||||
}
|
||||
|
||||
private import Cached
|
||||
|
||||
private predicate explicitInstanceArgument(Call call, Expr instarg) {
|
||||
call instanceof MethodAccess and
|
||||
instarg = call.getQualifier() and
|
||||
not call.getCallee().isStatic()
|
||||
}
|
||||
|
||||
private predicate implicitInstanceArgument(Call call, InstanceAccessExt ia) {
|
||||
ia.isImplicitMethodQualifier(call) or
|
||||
ia.isImplicitThisConstructorArgument(call)
|
||||
}
|
||||
|
||||
module Public {
|
||||
/**
|
||||
* An element, viewed as a node in a data flow graph. Either an expression,
|
||||
* a parameter, or an implicit varargs array creation.
|
||||
*/
|
||||
class Node extends TNode {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { none() }
|
||||
|
||||
/** Gets the source location for this element. */
|
||||
Location getLocation() { none() }
|
||||
|
||||
/** Gets the expression corresponding to this node, if any. */
|
||||
Expr asExpr() { result = this.(ExprNode).getExpr() }
|
||||
|
||||
/** Gets the parameter corresponding to this node, if any. */
|
||||
Parameter asParameter() { result = this.(ExplicitParameterNode).getParameter() }
|
||||
|
||||
/** Gets the type of this node. */
|
||||
Type getType() {
|
||||
result = this.asExpr().getType()
|
||||
or
|
||||
result = this.asParameter().getType()
|
||||
or
|
||||
exists(Parameter p |
|
||||
result = p.getType() and
|
||||
p.isVarargs() and
|
||||
p = this.(ImplicitVarargsArray).getCall().getCallee().getAParameter()
|
||||
)
|
||||
or
|
||||
result = this.(InstanceParameterNode).getCallable().getDeclaringType()
|
||||
or
|
||||
result = this.(ImplicitInstanceAccess).getInstanceAccess().getType()
|
||||
or
|
||||
result = this.(MallocNode).getClassInstanceExpr().getType()
|
||||
or
|
||||
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getType()
|
||||
}
|
||||
|
||||
/** Gets the callable in which this node occurs. */
|
||||
Callable getEnclosingCallable() {
|
||||
result = this.asExpr().getEnclosingCallable() or
|
||||
result = this.asParameter().getCallable() or
|
||||
result = this.(ImplicitVarargsArray).getCall().getEnclosingCallable() or
|
||||
result = this.(InstanceParameterNode).getCallable() or
|
||||
result = this.(ImplicitInstanceAccess).getInstanceAccess().getEnclosingCallable() or
|
||||
result = this.(MallocNode).getClassInstanceExpr().getEnclosingCallable() or
|
||||
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getEnclosingCallable() or
|
||||
this = TSummaryInternalNode(result, _)
|
||||
}
|
||||
|
||||
private Type getImprovedTypeBound() {
|
||||
exprTypeFlow(this.asExpr(), result, _) or
|
||||
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getImprovedTypeBound()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an upper bound on the type of this node.
|
||||
*/
|
||||
Type getTypeBound() {
|
||||
result = getImprovedTypeBound()
|
||||
or
|
||||
result = getType() and not exists(getImprovedTypeBound())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression, viewed as a node in a data flow graph.
|
||||
*/
|
||||
class ExprNode extends Node, TExprNode {
|
||||
Expr expr;
|
||||
|
||||
ExprNode() { this = TExprNode(expr) }
|
||||
|
||||
override string toString() { result = expr.toString() }
|
||||
|
||||
override Location getLocation() { result = expr.getLocation() }
|
||||
|
||||
/** Gets the expression corresponding to this node. */
|
||||
Expr getExpr() { result = expr }
|
||||
}
|
||||
|
||||
/** Gets the node corresponding to `e`. */
|
||||
ExprNode exprNode(Expr e) { result.getExpr() = e }
|
||||
|
||||
/** An explicit or implicit parameter. */
|
||||
abstract class ParameterNode extends Node {
|
||||
/**
|
||||
* Holds if this node is the parameter of `c` at the specified (zero-based)
|
||||
* position. The implicit `this` parameter is considered to have index `-1`.
|
||||
*/
|
||||
abstract predicate isParameterOf(Callable c, int pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter, viewed as a node in a data flow graph.
|
||||
*/
|
||||
class ExplicitParameterNode extends ParameterNode, TExplicitParameterNode {
|
||||
Parameter param;
|
||||
|
||||
ExplicitParameterNode() { this = TExplicitParameterNode(param) }
|
||||
|
||||
override string toString() { result = param.toString() }
|
||||
|
||||
override Location getLocation() { result = param.getLocation() }
|
||||
|
||||
/** Gets the parameter corresponding to this node. */
|
||||
Parameter getParameter() { result = param }
|
||||
|
||||
override predicate isParameterOf(Callable c, int pos) { c.getParameter(pos) = param }
|
||||
}
|
||||
|
||||
/** Gets the node corresponding to `p`. */
|
||||
ExplicitParameterNode parameterNode(Parameter p) { result.getParameter() = p }
|
||||
|
||||
/**
|
||||
* An implicit varargs array creation expression.
|
||||
*
|
||||
* A call `f(x1, x2)` to a method `f(A... xs)` desugars to `f(new A[]{x1, x2})`,
|
||||
* and this node corresponds to such an implicit array creation.
|
||||
*/
|
||||
class ImplicitVarargsArray extends Node, TImplicitVarargsArray {
|
||||
Call call;
|
||||
|
||||
ImplicitVarargsArray() { this = TImplicitVarargsArray(call) }
|
||||
|
||||
override string toString() { result = "new ..[] { .. }" }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
|
||||
/** Gets the call containing this varargs array creation argument. */
|
||||
Call getCall() { result = call }
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance parameter for an instance method or constructor.
|
||||
*/
|
||||
class InstanceParameterNode extends ParameterNode, TInstanceParameterNode {
|
||||
Callable callable;
|
||||
|
||||
InstanceParameterNode() { this = TInstanceParameterNode(callable) }
|
||||
|
||||
override string toString() { result = "parameter this" }
|
||||
|
||||
override Location getLocation() { result = callable.getLocation() }
|
||||
|
||||
/** Gets the callable containing this `this` parameter. */
|
||||
Callable getCallable() { result = callable }
|
||||
|
||||
override predicate isParameterOf(Callable c, int pos) { callable = c and pos = -1 }
|
||||
}
|
||||
|
||||
/**
|
||||
* An implicit read of `this` or `A.this`.
|
||||
*/
|
||||
class ImplicitInstanceAccess extends Node, TImplicitInstanceAccess {
|
||||
InstanceAccessExt ia;
|
||||
|
||||
ImplicitInstanceAccess() { this = TImplicitInstanceAccess(ia) }
|
||||
|
||||
override string toString() { result = ia.toString() }
|
||||
|
||||
override Location getLocation() { result = ia.getLocation() }
|
||||
|
||||
/** Gets the instance access corresponding to this node. */
|
||||
InstanceAccessExt getInstanceAccess() { result = ia }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node associated with an object after an operation that might have
|
||||
* changed its state.
|
||||
*
|
||||
* This can be either the argument to a callable after the callable returns
|
||||
* (which might have mutated the argument), or the qualifier of a field after
|
||||
* an update to the field.
|
||||
*
|
||||
* Nodes corresponding to AST elements, for example `ExprNode`, usually refer
|
||||
* to the value before the update with the exception of `ClassInstanceExpr`,
|
||||
* which represents the value after the constructor has run.
|
||||
*/
|
||||
abstract class PostUpdateNode extends Node {
|
||||
/**
|
||||
* Gets the node before the state update.
|
||||
*/
|
||||
abstract Node getPreUpdateNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node that occurs as the qualifier of `fa`.
|
||||
*/
|
||||
Node getFieldQualifier(FieldAccess fa) {
|
||||
fa.getField() instanceof InstanceField and
|
||||
(
|
||||
result.asExpr() = fa.getQualifier() or
|
||||
result.(ImplicitInstanceAccess).getInstanceAccess().isImplicitFieldQualifier(fa)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the instance argument of a non-static call. */
|
||||
Node getInstanceArgument(Call call) {
|
||||
result.(MallocNode).getClassInstanceExpr() = call or
|
||||
explicitInstanceArgument(call, result.asExpr()) or
|
||||
implicitInstanceArgument(call, result.(ImplicitInstanceAccess).getInstanceAccess())
|
||||
}
|
||||
}
|
||||
|
||||
private import Public
|
||||
|
||||
private class NewExpr extends PostUpdateNode, TExprNode {
|
||||
NewExpr() { exists(ClassInstanceExpr cie | this = TExprNode(cie)) }
|
||||
|
||||
override Node getPreUpdateNode() { this = TExprNode(result.(MallocNode).getClassInstanceExpr()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `PostUpdateNode` that is not a `ClassInstanceExpr`.
|
||||
*/
|
||||
abstract private class ImplicitPostUpdateNode extends PostUpdateNode {
|
||||
override Location getLocation() { result = getPreUpdateNode().getLocation() }
|
||||
|
||||
override string toString() { result = getPreUpdateNode().toString() + " [post update]" }
|
||||
}
|
||||
|
||||
private class ExplicitExprPostUpdate extends ImplicitPostUpdateNode, TExplicitExprPostUpdate {
|
||||
override Node getPreUpdateNode() { this = TExplicitExprPostUpdate(result.asExpr()) }
|
||||
}
|
||||
|
||||
private class ImplicitExprPostUpdate extends ImplicitPostUpdateNode, TImplicitExprPostUpdate {
|
||||
override Node getPreUpdateNode() {
|
||||
this = TImplicitExprPostUpdate(result.(ImplicitInstanceAccess).getInstanceAccess())
|
||||
}
|
||||
}
|
||||
|
||||
module Private {
|
||||
/**
|
||||
* A data flow node that occurs as the argument of a call and is passed as-is
|
||||
* to the callable. Arguments that are wrapped in an implicit varargs array
|
||||
* creation are not included, but the implicitly created array is.
|
||||
* Instance arguments are also included.
|
||||
*/
|
||||
class ArgumentNode extends Node {
|
||||
ArgumentNode() {
|
||||
exists(Argument arg | this.asExpr() = arg | not arg.isVararg())
|
||||
or
|
||||
this instanceof ImplicitVarargsArray
|
||||
or
|
||||
this = getInstanceArgument(_)
|
||||
or
|
||||
this.(SummaryNode).isArgumentOf(_, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this argument occurs at the given position in the given call.
|
||||
* The instance argument is considered to have index `-1`.
|
||||
*/
|
||||
predicate argumentOf(DataFlowCall call, int pos) {
|
||||
exists(Argument arg | this.asExpr() = arg | call = arg.getCall() and pos = arg.getPosition())
|
||||
or
|
||||
call = this.(ImplicitVarargsArray).getCall() and
|
||||
pos = call.getCallee().getNumberOfParameters() - 1
|
||||
or
|
||||
pos = -1 and this = getInstanceArgument(call)
|
||||
or
|
||||
this.(SummaryNode).isArgumentOf(call, pos)
|
||||
}
|
||||
|
||||
/** Gets the call in which this node is an argument. */
|
||||
DataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
}
|
||||
|
||||
/** A data flow node that occurs as the result of a `ReturnStmt`. */
|
||||
class ReturnNode extends Node {
|
||||
ReturnNode() {
|
||||
exists(ReturnStmt ret | this.asExpr() = ret.getResult()) or
|
||||
this.(SummaryNode).isReturn()
|
||||
}
|
||||
|
||||
/** Gets the kind of this returned value. */
|
||||
ReturnKind getKind() { any() }
|
||||
}
|
||||
|
||||
/** A data flow node that represents the output of a call. */
|
||||
class OutNode extends Node {
|
||||
OutNode() {
|
||||
this.asExpr() instanceof MethodAccess
|
||||
or
|
||||
this.(SummaryNode).isOut(_)
|
||||
}
|
||||
|
||||
/** Gets the underlying call. */
|
||||
DataFlowCall getCall() {
|
||||
result = this.asExpr()
|
||||
or
|
||||
this.(SummaryNode).isOut(result)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node used to model flow summaries.
|
||||
*/
|
||||
class SummaryNode extends Node, TSummaryInternalNode {
|
||||
private SummarizedCallable c;
|
||||
private FlowSummaryImpl::Private::SummaryNodeState state;
|
||||
|
||||
SummaryNode() { this = TSummaryInternalNode(c, state) }
|
||||
|
||||
override Location getLocation() { result = c.getLocation() }
|
||||
|
||||
override string toString() { result = "[summary] " + state + " in " + c }
|
||||
|
||||
/** Holds if this summary node is the `i`th argument of `call`. */
|
||||
predicate isArgumentOf(DataFlowCall call, int i) { summaryArgumentNodeCached(call, this, i) }
|
||||
|
||||
/** Holds if this summary node is a return node. */
|
||||
predicate isReturn() { summaryReturnNodeCached(this) }
|
||||
|
||||
/** Holds if this summary node is an out node for `call`. */
|
||||
predicate isOut(DataFlowCall call) { summaryOutNodeCached(call, this) }
|
||||
}
|
||||
|
||||
SummaryNode getSummaryNode(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) {
|
||||
result = TSummaryInternalNode(c, state)
|
||||
}
|
||||
}
|
||||
|
||||
private import Private
|
||||
|
||||
/**
|
||||
* A node that corresponds to the value of a `ClassInstanceExpr` before the
|
||||
* constructor has run.
|
||||
*/
|
||||
private class MallocNode extends Node, TMallocNode {
|
||||
ClassInstanceExpr cie;
|
||||
|
||||
MallocNode() { this = TMallocNode(cie) }
|
||||
|
||||
override string toString() { result = cie.toString() + " [pre constructor]" }
|
||||
|
||||
override Location getLocation() { result = cie.getLocation() }
|
||||
|
||||
ClassInstanceExpr getClassInstanceExpr() { result = cie }
|
||||
}
|
||||
|
||||
private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode {
|
||||
private Node pre;
|
||||
|
||||
SummaryPostUpdateNode() { summaryPostUpdateNodeCached(this, pre) }
|
||||
|
||||
override Node getPreUpdateNode() { result = pre }
|
||||
}
|
||||
@@ -4,7 +4,8 @@ private import DataFlowImplCommon
|
||||
private import DataFlowDispatch
|
||||
private import semmle.code.java.controlflow.Guards
|
||||
private import semmle.code.java.dataflow.SSA
|
||||
private import semmle.code.java.dataflow.TypeFlow
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
import DataFlowNodes::Private
|
||||
|
||||
private newtype TReturnKind = TNormalReturnKind()
|
||||
|
||||
@@ -26,54 +27,6 @@ OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
|
||||
kind = TNormalReturnKind()
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that occurs as the argument of a call and is passed as-is
|
||||
* to the callable. Arguments that are wrapped in an implicit varargs array
|
||||
* creation are not included, but the implicitly created array is.
|
||||
* Instance arguments are also included.
|
||||
*/
|
||||
class ArgumentNode extends Node {
|
||||
ArgumentNode() {
|
||||
exists(Argument arg | this.asExpr() = arg | not arg.isVararg())
|
||||
or
|
||||
this instanceof ImplicitVarargsArray
|
||||
or
|
||||
this = getInstanceArgument(_)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this argument occurs at the given position in the given call.
|
||||
* The instance argument is considered to have index `-1`.
|
||||
*/
|
||||
predicate argumentOf(DataFlowCall call, int pos) {
|
||||
exists(Argument arg | this.asExpr() = arg | call = arg.getCall() and pos = arg.getPosition())
|
||||
or
|
||||
call = this.(ImplicitVarargsArray).getCall() and
|
||||
pos = call.getCallee().getNumberOfParameters() - 1
|
||||
or
|
||||
pos = -1 and this = getInstanceArgument(call)
|
||||
}
|
||||
|
||||
/** Gets the call in which this node is an argument. */
|
||||
DataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
}
|
||||
|
||||
/** A data flow node that occurs as the result of a `ReturnStmt`. */
|
||||
class ReturnNode extends ExprNode {
|
||||
ReturnNode() { exists(ReturnStmt ret | this.getExpr() = ret.getResult()) }
|
||||
|
||||
/** Gets the kind of this returned value. */
|
||||
ReturnKind getKind() { any() }
|
||||
}
|
||||
|
||||
/** A data flow node that represents the output of a call. */
|
||||
class OutNode extends ExprNode {
|
||||
OutNode() { this.getExpr() instanceof MethodAccess }
|
||||
|
||||
/** Gets the underlying call. */
|
||||
DataFlowCall getCall() { result = this.getExpr() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` through a static field.
|
||||
*/
|
||||
@@ -131,8 +84,10 @@ private predicate instanceFieldAssign(Expr src, FieldAccess fa) {
|
||||
|
||||
private newtype TContent =
|
||||
TFieldContent(InstanceField f) or
|
||||
TArrayContent() or
|
||||
TCollectionContent() or
|
||||
TArrayContent()
|
||||
TMapKeyContent() or
|
||||
TMapValueContent()
|
||||
|
||||
/**
|
||||
* A reference contained in an object. Examples include instance fields, the
|
||||
@@ -147,7 +102,7 @@ class Content extends TContent {
|
||||
}
|
||||
}
|
||||
|
||||
private class FieldContent extends Content, TFieldContent {
|
||||
class FieldContent extends Content, TFieldContent {
|
||||
InstanceField f;
|
||||
|
||||
FieldContent() { this = TFieldContent(f) }
|
||||
@@ -161,12 +116,20 @@ private class FieldContent extends Content, TFieldContent {
|
||||
}
|
||||
}
|
||||
|
||||
private class CollectionContent extends Content, TCollectionContent {
|
||||
override string toString() { result = "collection" }
|
||||
class ArrayContent extends Content, TArrayContent {
|
||||
override string toString() { result = "[]" }
|
||||
}
|
||||
|
||||
private class ArrayContent extends Content, TArrayContent {
|
||||
override string toString() { result = "array" }
|
||||
class CollectionContent extends Content, TCollectionContent {
|
||||
override string toString() { result = "<element>" }
|
||||
}
|
||||
|
||||
class MapKeyContent extends Content, TMapKeyContent {
|
||||
override string toString() { result = "<map.key>" }
|
||||
}
|
||||
|
||||
class MapValueContent extends Content, TMapValueContent {
|
||||
override string toString() { result = "<map.value>" }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,6 +143,8 @@ predicate storeStep(Node node1, Content f, PostUpdateNode node2) {
|
||||
node2.getPreUpdateNode() = getFieldQualifier(fa) and
|
||||
f.(FieldContent).getField() = fa.getField()
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, f, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,6 +170,8 @@ predicate readStep(Node node1, Content f, Node node2) {
|
||||
node1.asExpr() = get.getQualifier() and
|
||||
node2.asExpr() = get
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1, f, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,7 +180,14 @@ predicate readStep(Node node1, Content f, Node node2) {
|
||||
* in `x.f = newValue`.
|
||||
*/
|
||||
predicate clearsContent(Node n, Content c) {
|
||||
n = any(PostUpdateNode pun | storeStep(_, c, pun)).getPreUpdateNode()
|
||||
c instanceof FieldContent and
|
||||
(
|
||||
n = any(PostUpdateNode pun | storeStep(_, c, pun)).getPreUpdateNode()
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryStoresIntoArg(c, n)
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,7 +195,7 @@ predicate clearsContent(Node n, Content c) {
|
||||
* possible flow. A single type is used for all numeric types to account for
|
||||
* numeric conversions, and otherwise the erasure is used.
|
||||
*/
|
||||
private DataFlowType getErasedRepr(Type t) {
|
||||
DataFlowType getErasedRepr(Type t) {
|
||||
exists(Type e | e = t.getErasure() |
|
||||
if e instanceof NumericOrCharType
|
||||
then result.(BoxedType).getPrimitiveType().getName() = "double"
|
||||
@@ -235,7 +209,11 @@ private DataFlowType getErasedRepr(Type t) {
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
DataFlowType getNodeType(Node n) { result = getErasedRepr(n.getTypeBound()) }
|
||||
DataFlowType getNodeType(Node n) {
|
||||
result = getErasedRepr(n.getTypeBound())
|
||||
or
|
||||
result = FlowSummaryImpl::Private::summaryNodeType(n)
|
||||
}
|
||||
|
||||
/** Gets a string representation of a type returned by `getErasedRepr`. */
|
||||
string ppReprType(Type t) {
|
||||
@@ -337,7 +315,7 @@ predicate isImmutableOrUnobservable(Node n) {
|
||||
}
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) { none() }
|
||||
predicate nodeIsHidden(Node n) { n instanceof SummaryNode }
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
|
||||
|
||||
@@ -5,284 +5,13 @@
|
||||
private import java
|
||||
private import DataFlowPrivate
|
||||
private import semmle.code.java.dataflow.SSA
|
||||
private import semmle.code.java.dataflow.TypeFlow
|
||||
private import semmle.code.java.controlflow.Guards
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.FlowSteps
|
||||
private import semmle.code.java.dataflow.FlowSummary
|
||||
import semmle.code.java.dataflow.InstanceAccess
|
||||
|
||||
cached
|
||||
private newtype TNode =
|
||||
TExprNode(Expr e) {
|
||||
not e.getType() instanceof VoidType and
|
||||
not e.getParent*() instanceof Annotation
|
||||
} or
|
||||
TExplicitParameterNode(Parameter p) { exists(p.getCallable().getBody()) } or
|
||||
TImplicitVarargsArray(Call c) {
|
||||
c.getCallee().isVarargs() and
|
||||
not exists(Argument arg | arg.getCall() = c and arg.isExplicitVarargsArray())
|
||||
} or
|
||||
TInstanceParameterNode(Callable c) { exists(c.getBody()) and not c.isStatic() } or
|
||||
TImplicitInstanceAccess(InstanceAccessExt ia) { not ia.isExplicit(_) } or
|
||||
TMallocNode(ClassInstanceExpr cie) or
|
||||
TExplicitExprPostUpdate(Expr e) {
|
||||
explicitInstanceArgument(_, e)
|
||||
or
|
||||
e instanceof Argument and not e.getType() instanceof ImmutableType
|
||||
or
|
||||
exists(FieldAccess fa | fa.getField() instanceof InstanceField and e = fa.getQualifier())
|
||||
or
|
||||
exists(ArrayAccess aa | e = aa.getArray())
|
||||
} or
|
||||
TImplicitExprPostUpdate(InstanceAccessExt ia) {
|
||||
implicitInstanceArgument(_, ia)
|
||||
or
|
||||
exists(FieldAccess fa |
|
||||
fa.getField() instanceof InstanceField and ia.isImplicitFieldQualifier(fa)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An element, viewed as a node in a data flow graph. Either an expression,
|
||||
* a parameter, or an implicit varargs array creation.
|
||||
*/
|
||||
class Node extends TNode {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { none() }
|
||||
|
||||
/** Gets the source location for this element. */
|
||||
Location getLocation() { none() }
|
||||
|
||||
/** Gets the expression corresponding to this node, if any. */
|
||||
Expr asExpr() { result = this.(ExprNode).getExpr() }
|
||||
|
||||
/** Gets the parameter corresponding to this node, if any. */
|
||||
Parameter asParameter() { result = this.(ExplicitParameterNode).getParameter() }
|
||||
|
||||
/** Gets the type of this node. */
|
||||
Type getType() {
|
||||
result = this.asExpr().getType()
|
||||
or
|
||||
result = this.asParameter().getType()
|
||||
or
|
||||
exists(Parameter p |
|
||||
result = p.getType() and
|
||||
p.isVarargs() and
|
||||
p = this.(ImplicitVarargsArray).getCall().getCallee().getAParameter()
|
||||
)
|
||||
or
|
||||
result = this.(InstanceParameterNode).getCallable().getDeclaringType()
|
||||
or
|
||||
result = this.(ImplicitInstanceAccess).getInstanceAccess().getType()
|
||||
or
|
||||
result = this.(MallocNode).getClassInstanceExpr().getType()
|
||||
or
|
||||
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getType()
|
||||
}
|
||||
|
||||
private Callable getEnclosingCallableImpl() {
|
||||
result = this.asExpr().getEnclosingCallable() or
|
||||
result = this.asParameter().getCallable() or
|
||||
result = this.(ImplicitVarargsArray).getCall().getEnclosingCallable() or
|
||||
result = this.(InstanceParameterNode).getCallable() or
|
||||
result = this.(ImplicitInstanceAccess).getInstanceAccess().getEnclosingCallable() or
|
||||
result = this.(MallocNode).getClassInstanceExpr().getEnclosingCallable() or
|
||||
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getEnclosingCallableImpl()
|
||||
}
|
||||
|
||||
/** Gets the callable in which this node occurs. */
|
||||
Callable getEnclosingCallable() {
|
||||
result = unique(DataFlowCallable c | c = this.getEnclosingCallableImpl() | c)
|
||||
}
|
||||
|
||||
private Type getImprovedTypeBound() {
|
||||
exprTypeFlow(this.asExpr(), result, _) or
|
||||
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getImprovedTypeBound()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an upper bound on the type of this node.
|
||||
*/
|
||||
Type getTypeBound() {
|
||||
result = getImprovedTypeBound()
|
||||
or
|
||||
result = getType() and not exists(getImprovedTypeBound())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression, viewed as a node in a data flow graph.
|
||||
*/
|
||||
class ExprNode extends Node, TExprNode {
|
||||
Expr expr;
|
||||
|
||||
ExprNode() { this = TExprNode(expr) }
|
||||
|
||||
override string toString() { result = expr.toString() }
|
||||
|
||||
override Location getLocation() { result = expr.getLocation() }
|
||||
|
||||
/** Gets the expression corresponding to this node. */
|
||||
Expr getExpr() { result = expr }
|
||||
}
|
||||
|
||||
/** Gets the node corresponding to `e`. */
|
||||
ExprNode exprNode(Expr e) { result.getExpr() = e }
|
||||
|
||||
/** An explicit or implicit parameter. */
|
||||
abstract class ParameterNode extends Node {
|
||||
/**
|
||||
* Holds if this node is the parameter of `c` at the specified (zero-based)
|
||||
* position. The implicit `this` parameter is considered to have index `-1`.
|
||||
*/
|
||||
abstract predicate isParameterOf(Callable c, int pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter, viewed as a node in a data flow graph.
|
||||
*/
|
||||
class ExplicitParameterNode extends ParameterNode, TExplicitParameterNode {
|
||||
Parameter param;
|
||||
|
||||
ExplicitParameterNode() { this = TExplicitParameterNode(param) }
|
||||
|
||||
override string toString() { result = param.toString() }
|
||||
|
||||
override Location getLocation() { result = param.getLocation() }
|
||||
|
||||
/** Gets the parameter corresponding to this node. */
|
||||
Parameter getParameter() { result = param }
|
||||
|
||||
override predicate isParameterOf(Callable c, int pos) { c.getParameter(pos) = param }
|
||||
}
|
||||
|
||||
/** Gets the node corresponding to `p`. */
|
||||
ExplicitParameterNode parameterNode(Parameter p) { result.getParameter() = p }
|
||||
|
||||
/**
|
||||
* An implicit varargs array creation expression.
|
||||
*
|
||||
* A call `f(x1, x2)` to a method `f(A... xs)` desugars to `f(new A[]{x1, x2})`,
|
||||
* and this node corresponds to such an implicit array creation.
|
||||
*/
|
||||
class ImplicitVarargsArray extends Node, TImplicitVarargsArray {
|
||||
Call call;
|
||||
|
||||
ImplicitVarargsArray() { this = TImplicitVarargsArray(call) }
|
||||
|
||||
override string toString() { result = "new ..[] { .. }" }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
|
||||
/** Gets the call containing this varargs array creation argument. */
|
||||
Call getCall() { result = call }
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance parameter for an instance method or constructor.
|
||||
*/
|
||||
class InstanceParameterNode extends ParameterNode, TInstanceParameterNode {
|
||||
Callable callable;
|
||||
|
||||
InstanceParameterNode() { this = TInstanceParameterNode(callable) }
|
||||
|
||||
override string toString() { result = "parameter this" }
|
||||
|
||||
override Location getLocation() { result = callable.getLocation() }
|
||||
|
||||
/** Gets the callable containing this `this` parameter. */
|
||||
Callable getCallable() { result = callable }
|
||||
|
||||
override predicate isParameterOf(Callable c, int pos) { callable = c and pos = -1 }
|
||||
}
|
||||
|
||||
/**
|
||||
* An implicit read of `this` or `A.this`.
|
||||
*/
|
||||
class ImplicitInstanceAccess extends Node, TImplicitInstanceAccess {
|
||||
InstanceAccessExt ia;
|
||||
|
||||
ImplicitInstanceAccess() { this = TImplicitInstanceAccess(ia) }
|
||||
|
||||
override string toString() { result = ia.toString() }
|
||||
|
||||
override Location getLocation() { result = ia.getLocation() }
|
||||
|
||||
InstanceAccessExt getInstanceAccess() { result = ia }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node that corresponds to the value of a `ClassInstanceExpr` before the
|
||||
* constructor has run.
|
||||
*/
|
||||
private class MallocNode extends Node, TMallocNode {
|
||||
ClassInstanceExpr cie;
|
||||
|
||||
MallocNode() { this = TMallocNode(cie) }
|
||||
|
||||
override string toString() { result = cie.toString() + " [pre constructor]" }
|
||||
|
||||
override Location getLocation() { result = cie.getLocation() }
|
||||
|
||||
ClassInstanceExpr getClassInstanceExpr() { result = cie }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node associated with an object after an operation that might have
|
||||
* changed its state.
|
||||
*
|
||||
* This can be either the argument to a callable after the callable returns
|
||||
* (which might have mutated the argument), or the qualifier of a field after
|
||||
* an update to the field.
|
||||
*
|
||||
* Nodes corresponding to AST elements, for example `ExprNode`, usually refer
|
||||
* to the value before the update with the exception of `ClassInstanceExpr`,
|
||||
* which represents the value after the constructor has run.
|
||||
*/
|
||||
abstract class PostUpdateNode extends Node {
|
||||
/**
|
||||
* Gets the node before the state update.
|
||||
*/
|
||||
abstract Node getPreUpdateNode();
|
||||
}
|
||||
|
||||
private class NewExpr extends PostUpdateNode, TExprNode {
|
||||
NewExpr() { exists(ClassInstanceExpr cie | this = TExprNode(cie)) }
|
||||
|
||||
override Node getPreUpdateNode() { this = TExprNode(result.(MallocNode).getClassInstanceExpr()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `PostUpdateNode` that is not a `ClassInstanceExpr`.
|
||||
*/
|
||||
abstract private class ImplicitPostUpdateNode extends PostUpdateNode {
|
||||
override Location getLocation() { result = getPreUpdateNode().getLocation() }
|
||||
|
||||
override string toString() { result = getPreUpdateNode().toString() + " [post update]" }
|
||||
}
|
||||
|
||||
private class ExplicitExprPostUpdate extends ImplicitPostUpdateNode, TExplicitExprPostUpdate {
|
||||
override Node getPreUpdateNode() { this = TExplicitExprPostUpdate(result.asExpr()) }
|
||||
}
|
||||
|
||||
private class ImplicitExprPostUpdate extends ImplicitPostUpdateNode, TImplicitExprPostUpdate {
|
||||
override Node getPreUpdateNode() {
|
||||
this = TImplicitExprPostUpdate(result.(ImplicitInstanceAccess).getInstanceAccess())
|
||||
}
|
||||
}
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
import DataFlowNodes::Public
|
||||
|
||||
/** Holds if `n` is an access to an unqualified `this` at `cfgnode`. */
|
||||
private predicate thisAccess(Node n, ControlFlowNode cfgnode) {
|
||||
@@ -367,7 +96,13 @@ predicate hasNonlocalValue(FieldRead fr) {
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` in one local step.
|
||||
*/
|
||||
predicate localFlowStep(Node node1, Node node2) { simpleLocalFlowStep(node1, node2) }
|
||||
predicate localFlowStep(Node node1, Node node2) {
|
||||
simpleLocalFlowStep(node1, node2)
|
||||
or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
// step relation, even though flow is technically inter-procedural
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStep(node1, node2, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
@@ -407,41 +142,22 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
|
||||
or
|
||||
node2.asExpr().(AssignExpr).getSource() = node1.asExpr()
|
||||
or
|
||||
summaryStep(node1, node2, "value")
|
||||
or
|
||||
exists(MethodAccess ma, ValuePreservingMethod m, int argNo |
|
||||
ma.getCallee().getSourceDeclaration() = m and m.returnsValue(argNo)
|
||||
|
|
||||
node2.asExpr() = ma and
|
||||
node1.(ArgumentNode).argumentOf(ma, argNo)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node that occurs as the qualifier of `fa`.
|
||||
*/
|
||||
Node getFieldQualifier(FieldAccess fa) {
|
||||
fa.getField() instanceof InstanceField and
|
||||
(
|
||||
result.asExpr() = fa.getQualifier() or
|
||||
result.(ImplicitInstanceAccess).getInstanceAccess().isImplicitFieldQualifier(fa)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate explicitInstanceArgument(Call call, Expr instarg) {
|
||||
call instanceof MethodAccess and instarg = call.getQualifier() and not call.getCallee().isStatic()
|
||||
}
|
||||
|
||||
private predicate implicitInstanceArgument(Call call, InstanceAccessExt ia) {
|
||||
ia.isImplicitMethodQualifier(call) or
|
||||
ia.isImplicitThisConstructorArgument(call)
|
||||
}
|
||||
|
||||
/** Gets the instance argument of a non-static call. */
|
||||
Node getInstanceArgument(Call call) {
|
||||
result.(MallocNode).getClassInstanceExpr() = call or
|
||||
explicitInstanceArgument(call, result.asExpr()) or
|
||||
implicitInstanceArgument(call, result.(ImplicitInstanceAccess).getInstanceAccess())
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(node1, node2, true)
|
||||
or
|
||||
// If flow through a method updates a parameter from some input A, and that
|
||||
// parameter also is returned through B, then we'd like a combined flow from A
|
||||
// to B as well. As an example, this simplifies modeling of fluent methods:
|
||||
// for `StringBuilder.append(x)` with a specified value flow from qualifier to
|
||||
// return value and taint flow from argument 0 to the qualifier, then this
|
||||
// allows us to infer taint flow from argument 0 to the return value.
|
||||
node1.(SummaryNode).(PostUpdateNode).getPreUpdateNode().(ParameterNode) = node2
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,674 @@
|
||||
/**
|
||||
* Provides classes and predicates for defining flow summaries.
|
||||
*
|
||||
* The definitions in this file are language-independent, and language-specific
|
||||
* definitions are passed in via the `DataFlowImplSpecific` and
|
||||
* `FlowSummaryImplSpecific` modules.
|
||||
*/
|
||||
|
||||
private import FlowSummaryImplSpecific
|
||||
private import DataFlowImplSpecific::Private
|
||||
private import DataFlowImplSpecific::Public
|
||||
|
||||
/** Provides classes and predicates for defining flow summaries. */
|
||||
module Public {
|
||||
private import Private
|
||||
|
||||
/**
|
||||
* A component used in a flow summary.
|
||||
*
|
||||
* Either a parameter or an argument at a given position, a specific
|
||||
* content type, or a return kind.
|
||||
*/
|
||||
class SummaryComponent extends TSummaryComponent {
|
||||
/** Gets a textual representation of this summary component. */
|
||||
string toString() {
|
||||
exists(Content c | this = TContentSummaryComponent(c) and result = c.toString())
|
||||
or
|
||||
exists(int i | this = TParameterSummaryComponent(i) and result = "parameter " + i)
|
||||
or
|
||||
exists(int i | this = TArgumentSummaryComponent(i) and result = "argument " + i)
|
||||
or
|
||||
exists(ReturnKind rk | this = TReturnSummaryComponent(rk) and result = "return (" + rk + ")")
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for constructing summary components. */
|
||||
module SummaryComponent {
|
||||
/** Gets a summary component for content `c`. */
|
||||
SummaryComponent content(Content c) { result = TContentSummaryComponent(c) }
|
||||
|
||||
/** Gets a summary component for parameter `i`. */
|
||||
SummaryComponent parameter(int i) { result = TParameterSummaryComponent(i) }
|
||||
|
||||
/** Gets a summary component for argument `i`. */
|
||||
SummaryComponent argument(int i) { result = TArgumentSummaryComponent(i) }
|
||||
|
||||
/** Gets a summary component for a return of kind `rk`. */
|
||||
SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A (non-empty) stack of summary components.
|
||||
*
|
||||
* A stack is used to represent where data is read from (input) or where it
|
||||
* is written to (output). For example, an input stack `[Field f, Argument 0]`
|
||||
* means that data is read from field `f` from the `0`th argument, while an
|
||||
* output stack `[Field g, Return]` means that data is written to the field
|
||||
* `g` of the returned object.
|
||||
*/
|
||||
class SummaryComponentStack extends TSummaryComponentStack {
|
||||
/** Gets the head of this stack. */
|
||||
SummaryComponent head() {
|
||||
this = TSingletonSummaryComponentStack(result) or
|
||||
this = TConsSummaryComponentStack(result, _)
|
||||
}
|
||||
|
||||
/** Gets the tail of this stack, if any. */
|
||||
SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) }
|
||||
|
||||
/** Gets the length of this stack. */
|
||||
int length() {
|
||||
this = TSingletonSummaryComponentStack(_) and result = 1
|
||||
or
|
||||
result = 1 + this.tail().length()
|
||||
}
|
||||
|
||||
/** Gets the stack obtained by dropping the first `i` elements, if any. */
|
||||
SummaryComponentStack drop(int i) {
|
||||
i = 0 and result = this
|
||||
or
|
||||
result = this.tail().drop(i - 1)
|
||||
}
|
||||
|
||||
/** Holds if this stack contains summary component `c`. */
|
||||
predicate contains(SummaryComponent c) { c = this.drop(_).head() }
|
||||
|
||||
/** Gets a textual representation of this stack. */
|
||||
string toString() {
|
||||
exists(SummaryComponent head, SummaryComponentStack tail |
|
||||
head = this.head() and
|
||||
tail = this.tail() and
|
||||
result = head + " of " + tail
|
||||
)
|
||||
or
|
||||
exists(SummaryComponent c |
|
||||
this = TSingletonSummaryComponentStack(c) and
|
||||
result = c.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for constructing stacks of summary components. */
|
||||
module SummaryComponentStack {
|
||||
/** Gets a singleton stack containing `c`. */
|
||||
SummaryComponentStack singleton(SummaryComponent c) {
|
||||
result = TSingletonSummaryComponentStack(c)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stack obtained by pushing `head` onto `tail`.
|
||||
*
|
||||
* Make sure to override `RequiredSummaryComponentStack::required()` in order
|
||||
* to ensure that the constructed stack exists.
|
||||
*/
|
||||
SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) {
|
||||
result = TConsSummaryComponentStack(head, tail)
|
||||
}
|
||||
|
||||
/** Gets a singleton stack for argument `i`. */
|
||||
SummaryComponentStack argument(int i) { result = singleton(SummaryComponent::argument(i)) }
|
||||
|
||||
/** Gets a singleton stack representing a return of kind `rk`. */
|
||||
SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that exists for QL technical reasons only (the IPA type used
|
||||
* to represent component stacks needs to be bounded).
|
||||
*/
|
||||
abstract class RequiredSummaryComponentStack extends SummaryComponentStack {
|
||||
/**
|
||||
* Holds if the stack obtained by pushing `head` onto `tail` is required.
|
||||
*/
|
||||
abstract predicate required(SummaryComponent c);
|
||||
}
|
||||
|
||||
/** A callable with a flow summary. */
|
||||
abstract class SummarizedCallable extends DataFlowCallable {
|
||||
/**
|
||||
* Holds if data may flow from `input` to `output` through this callable.
|
||||
*
|
||||
* `preservesValue` indicates whether this is a value-preserving step
|
||||
* or a taint-step.
|
||||
*
|
||||
* Input specifications are restricted to stacks that end with
|
||||
* `SummaryComponent::argument(_)`, preceded by zero or more
|
||||
* `SummaryComponent::return(_)` or `SummaryComponent::content(_)` components.
|
||||
*
|
||||
* Output specifications are restricted to stacks that end with
|
||||
* `SummaryComponent::return(_)` or `SummaryComponent::argument(_)`.
|
||||
*
|
||||
* Output stacks ending with `SummaryComponent::return(_)` can be preceded by zero
|
||||
* or more `SummaryComponent::content(_)` components.
|
||||
*
|
||||
* Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an
|
||||
* optional `SummaryComponent::parameter(_)` component, which in turn can be preceded
|
||||
* by zero or more `SummaryComponent::content(_)` components.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate propagatesFlow(
|
||||
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if values stored inside `content` are cleared on objects passed as
|
||||
* the `i`th argument to this callable.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate clearsContent(int i, Content content) { none() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides predicates for compiling flow summaries down to atomic local steps,
|
||||
* read steps, and store steps.
|
||||
*/
|
||||
module Private {
|
||||
private import Public
|
||||
private import DataFlowImplCommon as DataFlowImplCommon
|
||||
|
||||
newtype TSummaryComponent =
|
||||
TContentSummaryComponent(Content c) or
|
||||
TParameterSummaryComponent(int i) { parameterPosition(i) } or
|
||||
TArgumentSummaryComponent(int i) { parameterPosition(i) } or
|
||||
TReturnSummaryComponent(ReturnKind rk)
|
||||
|
||||
newtype TSummaryComponentStack =
|
||||
TSingletonSummaryComponentStack(SummaryComponent c) or
|
||||
TConsSummaryComponentStack(SummaryComponent head, SummaryComponentStack tail) {
|
||||
tail.(RequiredSummaryComponentStack).required(head)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate summary(
|
||||
SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output,
|
||||
boolean preservesValue
|
||||
) {
|
||||
c.propagatesFlow(input, output, preservesValue)
|
||||
}
|
||||
|
||||
private newtype TSummaryNodeState =
|
||||
TSummaryNodeInputState(SummaryComponentStack s) {
|
||||
exists(SummaryComponentStack input |
|
||||
summary(_, input, _, _) and
|
||||
s = input.drop(_)
|
||||
)
|
||||
} or
|
||||
TSummaryNodeOutputState(SummaryComponentStack s) {
|
||||
exists(SummaryComponentStack output |
|
||||
summary(_, _, output, _) and
|
||||
s = output.drop(_)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A state used to break up (complex) flow summaries into atomic flow steps.
|
||||
* For a flow summary
|
||||
*
|
||||
* ```ql
|
||||
* propagatesFlow(
|
||||
* SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* the following states are used:
|
||||
*
|
||||
* - `TSummaryNodeInputState(SummaryComponentStack s)`:
|
||||
* this state represents that the components in `s` _have been read_ from the
|
||||
* input.
|
||||
* - `TSummaryNodeOutputState(SummaryComponentStack s)`:
|
||||
* this state represents that the components in `s` _remain to be written_ to
|
||||
* the output.
|
||||
*/
|
||||
class SummaryNodeState extends TSummaryNodeState {
|
||||
/** Holds if this state is a valid input state for `c`. */
|
||||
pragma[nomagic]
|
||||
predicate isInputState(SummarizedCallable c, SummaryComponentStack s) {
|
||||
this = TSummaryNodeInputState(s) and
|
||||
exists(SummaryComponentStack input |
|
||||
summary(c, input, _, _) and
|
||||
s = input.drop(_)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this state is a valid output state for `c`. */
|
||||
pragma[nomagic]
|
||||
predicate isOutputState(SummarizedCallable c, SummaryComponentStack s) {
|
||||
this = TSummaryNodeOutputState(s) and
|
||||
exists(SummaryComponentStack output |
|
||||
summary(c, _, output, _) and
|
||||
s = output.drop(_)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this state. */
|
||||
string toString() {
|
||||
exists(SummaryComponentStack s |
|
||||
this = TSummaryNodeInputState(s) and
|
||||
result = "read: " + s
|
||||
)
|
||||
or
|
||||
exists(SummaryComponentStack s |
|
||||
this = TSummaryNodeOutputState(s) and
|
||||
result = "to write: " + s
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `state` represents having read the `i`th argument for `c`. In this case
|
||||
* we are not synthesizing a data-flow node, but instead assume that a relevant
|
||||
* parameter node already exists.
|
||||
*/
|
||||
private predicate parameterReadState(SummarizedCallable c, SummaryNodeState state, int i) {
|
||||
state.isInputState(c, SummaryComponentStack::argument(i))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a synthesized summary node is needed for the state `state` in summarized
|
||||
* callable `c`.
|
||||
*/
|
||||
predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) {
|
||||
state.isInputState(c, _) and
|
||||
not parameterReadState(c, state, _)
|
||||
or
|
||||
state.isOutputState(c, _)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private Node summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) {
|
||||
exists(SummaryNodeState state | state.isInputState(c, s) |
|
||||
result = summaryNode(c, state)
|
||||
or
|
||||
exists(int i |
|
||||
parameterReadState(c, state, i) and
|
||||
result.(ParameterNode).isParameterOf(c, i)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private Node summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) {
|
||||
exists(SummaryNodeState state |
|
||||
state.isOutputState(c, s) and
|
||||
result = summaryNode(c, state)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a write targets `post`, which is a post-update node for the `i`th
|
||||
* parameter of `c`.
|
||||
*/
|
||||
private predicate isParameterPostUpdate(Node post, SummarizedCallable c, int i) {
|
||||
post = summaryNodeOutputState(c, SummaryComponentStack::argument(i))
|
||||
}
|
||||
|
||||
/** Holds if a parameter node is required for the `i`th parameter of `c`. */
|
||||
predicate summaryParameterNodeRange(SummarizedCallable c, int i) {
|
||||
parameterReadState(c, _, i)
|
||||
or
|
||||
isParameterPostUpdate(_, c, i)
|
||||
}
|
||||
|
||||
private predicate callbackOutput(
|
||||
SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk
|
||||
) {
|
||||
any(SummaryNodeState state).isInputState(c, s) and
|
||||
s.head() = TReturnSummaryComponent(rk) and
|
||||
receiver = summaryNodeInputState(c, s.drop(1))
|
||||
}
|
||||
|
||||
private Node pre(Node post) {
|
||||
summaryPostUpdateNode(post, result)
|
||||
or
|
||||
not summaryPostUpdateNode(post, _) and
|
||||
result = post
|
||||
}
|
||||
|
||||
private predicate callbackInput(
|
||||
SummarizedCallable c, SummaryComponentStack s, Node receiver, int i
|
||||
) {
|
||||
any(SummaryNodeState state).isOutputState(c, s) and
|
||||
s.head() = TParameterSummaryComponent(i) and
|
||||
receiver = pre(summaryNodeOutputState(c, s.drop(1)))
|
||||
}
|
||||
|
||||
/** Holds if a call targeting `receiver` should be synthesized inside `c`. */
|
||||
predicate summaryCallbackRange(SummarizedCallable c, Node receiver) {
|
||||
callbackOutput(c, _, receiver, _)
|
||||
or
|
||||
callbackInput(c, _, receiver, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of synthesized summary node `n`.
|
||||
*
|
||||
* The type is computed based on the language-specific predicates
|
||||
* `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and
|
||||
* `getCallbackReturnType()`.
|
||||
*/
|
||||
DataFlowType summaryNodeType(Node n) {
|
||||
exists(Node pre |
|
||||
summaryPostUpdateNode(n, pre) and
|
||||
result = getNodeType(pre)
|
||||
)
|
||||
or
|
||||
exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() |
|
||||
n = summaryNodeInputState(c, s) and
|
||||
(
|
||||
exists(Content cont |
|
||||
head = TContentSummaryComponent(cont) and result = getContentType(cont)
|
||||
)
|
||||
or
|
||||
exists(ReturnKind rk |
|
||||
head = TReturnSummaryComponent(rk) and
|
||||
result = getCallbackReturnType(getNodeType(summaryNodeInputState(c, s.drop(1))), rk)
|
||||
)
|
||||
)
|
||||
or
|
||||
n = summaryNodeOutputState(c, s) and
|
||||
(
|
||||
exists(Content cont |
|
||||
head = TContentSummaryComponent(cont) and result = getContentType(cont)
|
||||
)
|
||||
or
|
||||
s.length() = 1 and
|
||||
exists(ReturnKind rk |
|
||||
head = TReturnSummaryComponent(rk) and
|
||||
result = getReturnType(c, rk)
|
||||
)
|
||||
or
|
||||
exists(int i | head = TParameterSummaryComponent(i) |
|
||||
result = getCallbackParameterType(getNodeType(summaryNodeOutputState(c, s.drop(1))), i)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if summary node `out` contains output of kind `rk` from call `c`. */
|
||||
predicate summaryOutNode(DataFlowCall c, Node out, ReturnKind rk) {
|
||||
exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
|
||||
callbackOutput(callable, s, receiver, rk) and
|
||||
out = summaryNodeInputState(callable, s) and
|
||||
c = summaryDataFlowCall(receiver)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if summary node `arg` is the `i`th argument of call `c`. */
|
||||
predicate summaryArgumentNode(DataFlowCall c, Node arg, int i) {
|
||||
exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
|
||||
callbackInput(callable, s, receiver, i) and
|
||||
arg = summaryNodeOutputState(callable, s) and
|
||||
c = summaryDataFlowCall(receiver)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if summary node `post` is a post-update node with pre-update node `pre`. */
|
||||
predicate summaryPostUpdateNode(Node post, ParameterNode pre) {
|
||||
exists(SummarizedCallable c, int i |
|
||||
isParameterPostUpdate(post, c, i) and
|
||||
pre.isParameterOf(c, i)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if summary node `ret` is a return node of kind `rk`. */
|
||||
predicate summaryReturnNode(Node ret, ReturnKind rk) {
|
||||
exists(SummarizedCallable callable, SummaryComponentStack s |
|
||||
ret = summaryNodeOutputState(callable, s) and
|
||||
s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk))
|
||||
)
|
||||
}
|
||||
|
||||
/** Provides a compilation of flow summaries to atomic data-flow steps. */
|
||||
module Steps {
|
||||
/**
|
||||
* Holds if there is a local step from `pred` to `succ`, which is synthesized
|
||||
* from a flow summary.
|
||||
*/
|
||||
predicate summaryLocalStep(Node pred, Node succ, boolean preservesValue) {
|
||||
exists(
|
||||
SummarizedCallable c, SummaryComponentStack inputContents,
|
||||
SummaryComponentStack outputContents
|
||||
|
|
||||
summary(c, inputContents, outputContents, preservesValue) and
|
||||
pred = summaryNodeInputState(c, inputContents) and
|
||||
succ = summaryNodeOutputState(c, outputContents)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a read step of content `c` from `pred` to `succ`, which
|
||||
* is synthesized from a flow summary.
|
||||
*/
|
||||
predicate summaryReadStep(Node pred, Content c, Node succ) {
|
||||
exists(SummarizedCallable sc, SummaryComponentStack s |
|
||||
pred = summaryNodeInputState(sc, s.drop(1)) and
|
||||
succ = summaryNodeInputState(sc, s) and
|
||||
SummaryComponent::content(c) = s.head()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a store step of content `c` from `pred` to `succ`, which
|
||||
* is synthesized from a flow summary.
|
||||
*/
|
||||
predicate summaryStoreStep(Node pred, Content c, Node succ) {
|
||||
exists(SummarizedCallable sc, SummaryComponentStack s |
|
||||
pred = summaryNodeOutputState(sc, s) and
|
||||
succ = summaryNodeOutputState(sc, s.drop(1)) and
|
||||
SummaryComponent::content(c) = s.head()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if values stored inside content `c` are cleared when passed as
|
||||
* input of type `input` in `call`.
|
||||
*/
|
||||
predicate summaryClearsContent(ArgumentNode arg, Content c) {
|
||||
exists(DataFlowCall call, int i |
|
||||
viableCallable(call).(SummarizedCallable).clearsContent(i, c) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private ParameterNode summaryArgParam(
|
||||
ArgumentNode arg, DataFlowImplCommon::ReturnKindExt rk, DataFlowImplCommon::OutNodeExt out
|
||||
) {
|
||||
exists(DataFlowCall call, int pos, SummarizedCallable callable |
|
||||
arg.argumentOf(call, pos) and
|
||||
viableCallable(call) = callable and
|
||||
result.isParameterOf(callable, pos) and
|
||||
out = rk.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` flows to `out` using a simple flow summary, that is, a flow
|
||||
* summary without reads and stores.
|
||||
*
|
||||
* NOTE: This step should not be used in global data-flow/taint-tracking, but may
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summaryThroughStep(ArgumentNode arg, Node out, boolean preservesValue) {
|
||||
exists(DataFlowImplCommon::ReturnKindExt rk, DataFlowImplCommon::ReturnNodeExt ret |
|
||||
summaryLocalStep(summaryArgParam(arg, rk, out), ret, preservesValue) and
|
||||
ret.getKind() = rk
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a read(+taint) of `c` from `arg` to `out` using a
|
||||
* flow summary.
|
||||
*
|
||||
* NOTE: This step should not be used in global data-flow/taint-tracking, but may
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summaryGetterStep(ArgumentNode arg, Content c, Node out) {
|
||||
exists(DataFlowImplCommon::ReturnKindExt rk, Node mid, DataFlowImplCommon::ReturnNodeExt ret |
|
||||
summaryReadStep(summaryArgParam(arg, rk, out), c, mid) and
|
||||
summaryLocalStep(mid, ret, _) and
|
||||
ret.getKind() = rk
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a (taint+)store of `arg` into content `c` of `out` using a
|
||||
* flow summary.
|
||||
*
|
||||
* NOTE: This step should not be used in global data-flow/taint-tracking, but may
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summarySetterStep(ArgumentNode arg, Content c, Node out) {
|
||||
exists(DataFlowImplCommon::ReturnKindExt rk, Node mid, DataFlowImplCommon::ReturnNodeExt ret |
|
||||
summaryLocalStep(summaryArgParam(arg, rk, out), mid, _) and
|
||||
summaryStoreStep(mid, c, ret) and
|
||||
ret.getKind() = rk
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data is written into content `c` of argument `arg` using a flow summary.
|
||||
*
|
||||
* Depending on the type of `c`, this predicate may be relevant to include in the
|
||||
* definition of `clearsContent()`.
|
||||
*/
|
||||
predicate summaryStoresIntoArg(Content c, Node arg) {
|
||||
exists(
|
||||
DataFlowImplCommon::ParamUpdateReturnKind rk, DataFlowImplCommon::ReturnNodeExt ret,
|
||||
PostUpdateNode out
|
||||
|
|
||||
exists(DataFlowCall call, SummarizedCallable callable |
|
||||
DataFlowImplCommon::getNodeEnclosingCallable(ret) = callable and
|
||||
viableCallable(call) = callable and
|
||||
summaryStoreStep(_, c, ret) and
|
||||
ret.getKind() = pragma[only_bind_into](rk) and
|
||||
out = rk.getAnOutNode(call) and
|
||||
arg = out.getPreUpdateNode()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a means of translating externally (e.g., CSV) defined flow
|
||||
* summaries into a `SummarizedCallable`s.
|
||||
*/
|
||||
module External {
|
||||
/**
|
||||
* Provides a means of translating an externally (e.g., CSV) defined flow
|
||||
* summary into a `SummarizedCallable`.
|
||||
*/
|
||||
abstract class ExternalSummaryCompilation extends string {
|
||||
bindingset[this]
|
||||
ExternalSummaryCompilation() { any() }
|
||||
|
||||
/** Holds if this flow summary is for callable `c`. */
|
||||
abstract predicate callable(DataFlowCallable c, boolean preservesValue);
|
||||
|
||||
/** Holds if the `i`th input component is `c`. */
|
||||
abstract predicate input(int i, SummaryComponent c);
|
||||
|
||||
/** Holds if the `i`th output component is `c`. */
|
||||
abstract predicate output(int i, SummaryComponent c);
|
||||
|
||||
/**
|
||||
* Holds if the input components starting from index `i` translate into `suffix`.
|
||||
*/
|
||||
final predicate translateInput(int i, SummaryComponentStack suffix) {
|
||||
exists(SummaryComponent comp | this.input(i, comp) |
|
||||
i = max(int j | this.input(j, _)) and
|
||||
suffix = TSingletonSummaryComponentStack(comp)
|
||||
or
|
||||
exists(TSummaryComponent head, SummaryComponentStack tail |
|
||||
this.translateInputCons(i, head, tail) and
|
||||
suffix = TConsSummaryComponentStack(head, tail)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
final predicate translateInputCons(int i, SummaryComponent head, SummaryComponentStack tail) {
|
||||
this.input(i, head) and
|
||||
this.translateInput(i + 1, tail)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the output components starting from index `i` translate into `suffix`.
|
||||
*/
|
||||
predicate translateOutput(int i, SummaryComponentStack suffix) {
|
||||
exists(SummaryComponent comp | this.output(i, comp) |
|
||||
i = max(int j | this.output(j, _)) and
|
||||
suffix = TSingletonSummaryComponentStack(comp)
|
||||
or
|
||||
exists(TSummaryComponent head, SummaryComponentStack tail |
|
||||
this.translateOutputCons(i, head, tail) and
|
||||
suffix = TConsSummaryComponentStack(head, tail)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate translateOutputCons(int i, SummaryComponent head, SummaryComponentStack tail) {
|
||||
this.output(i, head) and
|
||||
this.translateOutput(i + 1, tail)
|
||||
}
|
||||
}
|
||||
|
||||
private class ExternalRequiredSummaryComponentStack extends RequiredSummaryComponentStack {
|
||||
private SummaryComponent head;
|
||||
|
||||
ExternalRequiredSummaryComponentStack() {
|
||||
any(ExternalSummaryCompilation s).translateInputCons(_, head, this) or
|
||||
any(ExternalSummaryCompilation s).translateOutputCons(_, head, this)
|
||||
}
|
||||
|
||||
override predicate required(SummaryComponent c) { c = head }
|
||||
}
|
||||
|
||||
class ExternalSummarizedCallableAdaptor extends SummarizedCallable {
|
||||
ExternalSummarizedCallableAdaptor() { any(ExternalSummaryCompilation s).callable(this, _) }
|
||||
|
||||
override predicate propagatesFlow(
|
||||
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
) {
|
||||
exists(ExternalSummaryCompilation s |
|
||||
s.callable(this, preservesValue) and
|
||||
s.translateInput(0, input) and
|
||||
s.translateOutput(0, output)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a query predicate for outputting a set of relevant flow summaries. */
|
||||
module TestOutput {
|
||||
/** A flow summary to include in the `summary/3` query predicate. */
|
||||
abstract class RelevantSummarizedCallable extends SummarizedCallable {
|
||||
/** Gets the string representation of this callable used by `summary/3`. */
|
||||
string getFullString() { result = this.toString() }
|
||||
}
|
||||
|
||||
/** A query predicate for outputting flow summaries in QL tests. */
|
||||
query predicate summary(string callable, string flow, boolean preservesValue) {
|
||||
exists(
|
||||
RelevantSummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output
|
||||
|
|
||||
callable = c.getFullString() and
|
||||
c.propagatesFlow(input, output, preservesValue) and
|
||||
flow = input + " -> " + output
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Provides Java specific classes and predicates for definining flow summaries.
|
||||
*/
|
||||
|
||||
private import java
|
||||
private import DataFlowPrivate
|
||||
private import DataFlowUtil
|
||||
private import FlowSummaryImpl::Private
|
||||
private import FlowSummaryImpl::Public
|
||||
|
||||
private module FlowSummaries {
|
||||
private import semmle.code.java.dataflow.FlowSummary as F
|
||||
}
|
||||
|
||||
/** Holds is `i` is a valid parameter position. */
|
||||
predicate parameterPosition(int i) { i in [-1 .. any(Parameter p).getPosition()] }
|
||||
|
||||
/** Gets the synthesized summary data-flow node for the given values. */
|
||||
Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = getSummaryNode(c, state) }
|
||||
|
||||
/** Gets the synthesized data-flow call for `receiver`. */
|
||||
DataFlowCall summaryDataFlowCall(Node receiver) { none() }
|
||||
|
||||
/** Gets the type of content `c`. */
|
||||
DataFlowType getContentType(Content c) {
|
||||
result = getErasedRepr(c.(FieldContent).getField().getType())
|
||||
or
|
||||
c instanceof CollectionContent and
|
||||
result instanceof TypeObject
|
||||
or
|
||||
c instanceof ArrayContent and
|
||||
result instanceof TypeObject
|
||||
}
|
||||
|
||||
/** Gets the return type of kind `rk` for callable `c`. */
|
||||
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
|
||||
result = getErasedRepr(c.getReturnType()) and
|
||||
exists(rk)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of the `i`th parameter in a synthesized call that targets a
|
||||
* callback of type `t`.
|
||||
*/
|
||||
DataFlowType getCallbackParameterType(DataFlowType t, int i) { none() }
|
||||
|
||||
/**
|
||||
* Gets the return type of kind `rk` in a synthesized call that targets a
|
||||
* callback of type `t`.
|
||||
*/
|
||||
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { none() }
|
||||
@@ -13,6 +13,7 @@ private import semmle.code.java.frameworks.Networking
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.internal.DataFlowPrivate
|
||||
import semmle.code.java.dataflow.FlowSteps
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
|
||||
/**
|
||||
* Holds if taint can flow from `src` to `sink` in zero or more
|
||||
@@ -33,7 +34,10 @@ predicate localExprTaint(Expr src, Expr sink) {
|
||||
*/
|
||||
predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
||||
DataFlow::localFlowStep(src, sink) or
|
||||
localAdditionalTaintStep(src, sink)
|
||||
localAdditionalTaintStep(src, sink) or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
// step relation, even though flow is technically inter-procedural
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStep(src, sink, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,45 +46,19 @@ predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
||||
* different objects.
|
||||
*/
|
||||
predicate localAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
||||
localAdditionalBasicTaintStep(src, sink)
|
||||
or
|
||||
composedValueAndTaintModelStep(src, sink)
|
||||
}
|
||||
|
||||
private predicate localAdditionalBasicTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
||||
localAdditionalTaintExprStep(src.asExpr(), sink.asExpr())
|
||||
or
|
||||
localAdditionalTaintUpdateStep(src.asExpr(),
|
||||
sink.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr())
|
||||
or
|
||||
summaryStep(src, sink, "taint") and
|
||||
not summaryStep(src, sink, "value")
|
||||
or
|
||||
exists(Argument arg |
|
||||
src.asExpr() = arg and
|
||||
arg.isVararg() and
|
||||
sink.(DataFlow::ImplicitVarargsArray).getCall() = arg.getCall()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an additional step from `src` to `sink` through a call can be inferred from the
|
||||
* combination of a value-preserving step providing an alias between an input and the output
|
||||
* and a taint step from `src` to one the aliased nodes. For example, if we know that `f(a, b)` returns
|
||||
* the exact value of `a` and also propagates taint from `b` to `a`, then we also know that
|
||||
* the return value is tainted after `f` completes.
|
||||
*/
|
||||
private predicate composedValueAndTaintModelStep(ArgumentNode src, DataFlow::Node sink) {
|
||||
exists(Call call, ArgumentNode valueSource, DataFlow::PostUpdateNode valueSourcePost |
|
||||
src.argumentOf(call, _) and
|
||||
valueSource.argumentOf(call, _) and
|
||||
src != valueSource and
|
||||
valueSourcePost.getPreUpdateNode() = valueSource and
|
||||
// in-x -value-> out-y and in-z -taint-> in-x ==> in-z -taint-> out-y
|
||||
localAdditionalBasicTaintStep(src, valueSourcePost) and
|
||||
DataFlow::localFlowStep(valueSource, DataFlow::exprNode(call)) and
|
||||
sink = DataFlow::exprNode(call)
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(src, sink, false) and
|
||||
not FlowSummaryImpl::Private::Steps::summaryLocalStep(src, sink, true)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -194,7 +194,7 @@ private predicate source(RefType t, ObjNode n) {
|
||||
private predicate sink(ObjNode n) {
|
||||
exists(MethodAccess toString |
|
||||
toString.getQualifier() = n.asExpr() and
|
||||
toString.getMethod().hasName("toString")
|
||||
toString.getMethod() instanceof ToStringMethod
|
||||
) and
|
||||
n.getTypeBound().getErasure() instanceof TypeObject
|
||||
}
|
||||
|
||||
@@ -149,9 +149,8 @@ private class ApacheHttpFlowStep extends SummaryModelCsv {
|
||||
"org.apache.hc.core5.http.message;RequestLine;true;getMethod;();;Argument[-1];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.message;RequestLine;true;getUri;();;Argument[-1];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.message;RequestLine;true;toString;();;Argument[-1];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.message;RequestLine;true;RequestLine;(HttpRequest);;Argument[0];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.message;RequestLine;true;RequestLine;(String,String,ProtocolVersion);;Argument[1];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.message;RequestLine;true;RequestLine;(String,String,ProtocolVersion);;Argument[1];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.message;RequestLine;true;RequestLine;(HttpRequest);;Argument[0];Argument[-1];taint",
|
||||
"org.apache.hc.core5.http.message;RequestLine;true;RequestLine;(String,String,ProtocolVersion);;Argument[1];Argument[-1];taint",
|
||||
"org.apache.hc.core5.function;Supplier;true;get;();;Argument[-1];ReturnValue;taint",
|
||||
"org.apache.hc.core5.net;URIAuthority;true;getHostName;();;Argument[-1];ReturnValue;taint",
|
||||
"org.apache.hc.core5.net;URIAuthority;true;toString;();;Argument[-1];ReturnValue;taint",
|
||||
@@ -181,16 +180,16 @@ private class ApacheHttpFlowStep extends SummaryModelCsv {
|
||||
"org.apache.hc.core5.http.io.entity;HttpEntities;true;withTrailers;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.http.entity;BasicHttpEntity;true;setContent;(InputStream);;Argument[0];Argument[-1];taint",
|
||||
"org.apache.http.entity;BufferedHttpEntity;true;BufferedHttpEntity;(HttpEntity);;Argument[0];ReturnValue;taint",
|
||||
"org.apache.http.entity;ByteArrayEntity;true;ByteArrayEntity;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.http.entity;ByteArrayEntity;true;ByteArrayEntity;;;Argument[0];Argument[-1];taint",
|
||||
"org.apache.http.entity;HttpEntityWrapper;true;HttpEntityWrapper;(HttpEntity);;Argument[0];ReturnValue;taint",
|
||||
"org.apache.http.entity;InputStreamEntity;true;InputStreamEntity;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.http.entity;StringEntity;true;StringEntity;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.http.entity;StringEntity;true;StringEntity;;;Argument[0];Argument[-1];taint",
|
||||
"org.apache.hc.core5.http.io.entity;BasicHttpEntity;true;BasicHttpEntity;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.io.entity;BufferedHttpEntity;true;BufferedHttpEntity;(HttpEntity);;Argument[0];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.io.entity;ByteArrayEntity;true;ByteArrayEntity;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.io.entity;ByteArrayEntity;true;ByteArrayEntity;;;Argument[0];Argument[-1];taint",
|
||||
"org.apache.hc.core5.http.io.entity;HttpEntityWrapper;true;HttpEntityWrapper;(HttpEntity);;Argument[0];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.io.entity;InputStreamEntity;true;InputStreamEntity;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.io.entity;StringEntity;true;StringEntity;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.hc.core5.http.io.entity;StringEntity;true;StringEntity;;;Argument[0];Argument[-1];taint",
|
||||
"org.apache.http.util;ByteArrayBuffer;true;append;(byte[],int,int);;Argument[0];Argument[-1];taint",
|
||||
"org.apache.http.util;ByteArrayBuffer;true;append;(char[],int,int);;Argument[0];Argument[-1];taint",
|
||||
"org.apache.http.util;ByteArrayBuffer;true;append;(CharArrayBuffer,int,int);;Argument[0];Argument[-1];taint",
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
/* Definitions related to the Apache Commons Exec library. */
|
||||
import semmle.code.java.Type
|
||||
import semmle.code.java.security.ExternalProcess
|
||||
|
||||
library class TypeCommandLine extends Class {
|
||||
/** The class `org.apache.commons.exec.CommandLine`. */
|
||||
private class TypeCommandLine extends Class {
|
||||
TypeCommandLine() { hasQualifiedName("org.apache.commons.exec", "CommandLine") }
|
||||
}
|
||||
|
||||
library class MethodCommandLineParse extends Method {
|
||||
/** The `parse()` method of the class `org.apache.commons.exec.CommandLine`. */
|
||||
private class MethodCommandLineParse extends Method, ExecCallable {
|
||||
MethodCommandLineParse() {
|
||||
getDeclaringType() instanceof TypeCommandLine and
|
||||
hasName("parse")
|
||||
}
|
||||
|
||||
override int getAnExecutedArgument() { result = 0 }
|
||||
}
|
||||
|
||||
library class MethodCommandLineAddArguments extends Method {
|
||||
/** The `addArguments()` method of the class `org.apache.commons.exec.CommandLine`. */
|
||||
private class MethodCommandLineAddArguments extends Method, ExecCallable {
|
||||
MethodCommandLineAddArguments() {
|
||||
getDeclaringType() instanceof TypeCommandLine and
|
||||
hasName("addArguments")
|
||||
}
|
||||
|
||||
override int getAnExecutedArgument() { result = 0 }
|
||||
}
|
||||
|
||||
@@ -626,12 +626,12 @@ private class ApacheObjectUtilsModel extends SummaryModelCsv {
|
||||
"org.apache.commons.lang3;ObjectUtils;false;CONST_BYTE;;;Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;CONST_SHORT;;;Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;defaultIfNull;;;Argument[0..1];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;firstNonNull;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;firstNonNull;;;ArrayElement of Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;getIfNull;;;Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;max;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;median;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;min;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;mode;;;Argument[0];ReturnValue;taint",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;max;;;ArrayElement of Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;median;;;ArrayElement of Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;min;;;ArrayElement of Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;mode;;;ArrayElement of Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;requireNonEmpty;;;Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;toString;(Object,String);;Argument[1];ReturnValue;value"
|
||||
]
|
||||
|
||||
@@ -13,7 +13,8 @@ private class GuavaBaseCsv extends SummaryModelCsv {
|
||||
"com.google.common.base;Strings;false;padStart;(String,int,char);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.base;Strings;false;padEnd;(String,int,char);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.base;Strings;false;repeat;(String,int);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.base;Strings;false;lenientFormat;(String,Object[]);;Argument;ReturnValue;taint",
|
||||
"com.google.common.base;Strings;false;lenientFormat;(String,Object[]);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.base;Strings;false;lenientFormat;(String,Object[]);;ArrayElement of Argument[1];ReturnValue;taint",
|
||||
"com.google.common.base;Joiner;false;on;(String);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.base;Joiner;false;skipNulls;();;Argument[-1];ReturnValue;taint",
|
||||
"com.google.common.base;Joiner;false;useForNull;(String);;Argument[-1];ReturnValue;taint",
|
||||
@@ -22,14 +23,14 @@ private class GuavaBaseCsv extends SummaryModelCsv {
|
||||
"com.google.common.base;Joiner;false;withKeyValueSeparator;(String);;Argument[-1];ReturnValue;taint",
|
||||
"com.google.common.base;Joiner;false;withKeyValueSeparator;(char);;Argument[-1];ReturnValue;taint",
|
||||
// Note: The signatures of some of the appendTo methods involve collection flow
|
||||
"com.google.common.base;Joiner;false;appendTo;;;Argument;Argument[0];taint",
|
||||
"com.google.common.base;Joiner;false;appendTo;;;Argument[-1..3];Argument[0];taint",
|
||||
"com.google.common.base;Joiner;false;appendTo;;;Argument[0];ReturnValue;value",
|
||||
"com.google.common.base;Joiner;false;join;;;Argument;ReturnValue;taint",
|
||||
"com.google.common.base;Joiner;false;join;;;Argument[-1..2];ReturnValue;taint",
|
||||
"com.google.common.base;Joiner$MapJoiner;false;useForNull;(String);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.base;Joiner$MapJoiner;false;useForNull;(String);;Argument[-1];ReturnValue;taint",
|
||||
"com.google.common.base;Joiner$MapJoiner;false;appendTo;;;Argument;Argument[0];taint",
|
||||
"com.google.common.base;Joiner$MapJoiner;false;appendTo;;;Argument[1];Argument[0];taint",
|
||||
"com.google.common.base;Joiner$MapJoiner;false;appendTo;;;Argument[0];ReturnValue;value",
|
||||
"com.google.common.base;Joiner$MapJoiner;false;join;;;Argument;ReturnValue;taint",
|
||||
"com.google.common.base;Joiner$MapJoiner;false;join;;;Argument[-1..0];ReturnValue;taint",
|
||||
"com.google.common.base;Splitter;false;split;(CharSequence);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.base;Splitter;false;splitToList;(CharSequence);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.base;Splitter;false;splitToStream;(CharSequence);;Argument[0];ReturnValue;taint",
|
||||
|
||||
@@ -61,7 +61,7 @@ private class GuavaIoCsv extends SummaryModelCsv {
|
||||
"com.google.common.io;Files;false;simplifyPath;(String);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.io;MoreFiles;false;getFileExtension;(Path);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.io;MoreFiles;false;getNameWithoutExtension;(Path);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.io;LineReader;false;LineReader;(Readable);;Argument[0];ReturnValue;taint",
|
||||
"com.google.common.io;LineReader;false;LineReader;(Readable);;Argument[0];Argument[-1];taint",
|
||||
"com.google.common.io;LineReader;true;readLine;();;Argument[-1];ReturnValue;taint",
|
||||
"com.google.common.io;ByteArrayDataOutput;true;toByteArray;();;Argument[-1];ReturnValue;taint",
|
||||
"com.google.common.io;ByteArrayDataOutput;true;write;(byte[]);;Argument[0];Argument[-1];taint",
|
||||
|
||||
@@ -44,14 +44,8 @@ class PlayAddCsrfTokenAnnotation extends Annotation {
|
||||
/**
|
||||
* The type `play.libs.F.Promise<Result>`.
|
||||
*/
|
||||
class PlayAsyncResultPromise extends Member {
|
||||
PlayAsyncResultPromise() {
|
||||
exists(Class c |
|
||||
c.hasQualifiedName("play.libs", "F") and
|
||||
this = c.getAMember() and
|
||||
this.getQualifiedName() = "F.Promise<Result>"
|
||||
)
|
||||
}
|
||||
class PlayAsyncResultPromise extends MemberType {
|
||||
PlayAsyncResultPromise() { hasQualifiedName("play.libs", "F$Promise<Result>") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,8 @@ import semmle.code.java.Expr
|
||||
import semmle.code.java.security.Validation
|
||||
|
||||
/**
|
||||
* Holds if `method` is a `toString()` method on a boxed type. These never return special characters.
|
||||
* Holds if `method` is a `toString()` method on a boxed type, with or without parameters.
|
||||
* These never return special characters.
|
||||
*/
|
||||
private predicate boxedToString(Method method) {
|
||||
method.getDeclaringType() instanceof BoxedType and
|
||||
@@ -44,11 +45,9 @@ private predicate controlledStringProp(Expr src, Expr dest) {
|
||||
exists(AddExpr concatOp | concatOp = dest | src = concatOp.getAnOperand())
|
||||
or
|
||||
// `toString()` on a safe string is safe.
|
||||
exists(MethodAccess toStringCall, Method toString |
|
||||
exists(MethodAccess toStringCall |
|
||||
src = toStringCall.getQualifier() and
|
||||
toString = toStringCall.getMethod() and
|
||||
toString.hasName("toString") and
|
||||
toString.getNumberOfParameters() = 0 and
|
||||
toStringCall.getMethod() instanceof ToStringMethod and
|
||||
dest = toStringCall
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
/* Definitions related to external processes. */
|
||||
import semmle.code.java.Member
|
||||
import semmle.code.java.JDK
|
||||
import semmle.code.java.frameworks.apache.Exec
|
||||
|
||||
private module Instances {
|
||||
private import semmle.code.java.JDK
|
||||
private import semmle.code.java.frameworks.apache.Exec
|
||||
}
|
||||
|
||||
/**
|
||||
* A callable that executes a command.
|
||||
*/
|
||||
abstract class ExecCallable extends Callable {
|
||||
/**
|
||||
* Gets the index of an argument that will be part of the command that is executed.
|
||||
*/
|
||||
abstract int getAnExecutedArgument();
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression used as an argument to a call that executes an external command. For calls to
|
||||
@@ -10,21 +23,10 @@ import semmle.code.java.frameworks.apache.Exec
|
||||
*/
|
||||
class ArgumentToExec extends Expr {
|
||||
ArgumentToExec() {
|
||||
exists(MethodAccess execCall, Method method |
|
||||
execCall.getArgument(0) = this and
|
||||
method = execCall.getMethod() and
|
||||
(
|
||||
method instanceof MethodRuntimeExec or
|
||||
method instanceof MethodProcessBuilderCommand or
|
||||
method instanceof MethodCommandLineParse or
|
||||
method instanceof MethodCommandLineAddArguments
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(ConstructorCall expr, Constructor cons |
|
||||
expr.getConstructor() = cons and
|
||||
cons.getDeclaringType().hasQualifiedName("java.lang", "ProcessBuilder") and
|
||||
expr.getArgument(0) = this
|
||||
exists(Call execCall, ExecCallable execCallable, int i |
|
||||
execCall.getArgument(pragma[only_bind_into](i)) = this and
|
||||
execCallable = execCall.getCallee() and
|
||||
i = execCallable.getAnExecutedArgument()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import semmle.code.java.frameworks.Jndi
|
||||
import semmle.code.java.frameworks.UnboundId
|
||||
import semmle.code.java.frameworks.SpringLdap
|
||||
import semmle.code.java.frameworks.ApacheLdap
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** A data flow sink for unvalidated user input that is used to construct LDAP queries. */
|
||||
abstract class LdapInjectionSink extends DataFlow::Node { }
|
||||
@@ -28,70 +29,56 @@ class LdapInjectionAdditionalTaintStep extends Unit {
|
||||
|
||||
/** Default sink for LDAP injection vulnerabilities. */
|
||||
private class DefaultLdapInjectionSink extends LdapInjectionSink {
|
||||
DefaultLdapInjectionSink() {
|
||||
exists(MethodAccess ma, Method m, int index |
|
||||
ma.getMethod() = m and
|
||||
ma.getArgument(index) = this.asExpr() and
|
||||
ldapInjectionSinkMethod(m, index)
|
||||
)
|
||||
DefaultLdapInjectionSink() { sinkNode(this, "ldap") }
|
||||
}
|
||||
|
||||
private class DefaultLdapInjectionSinkModel extends SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
// jndi
|
||||
"javax.naming.directory;DirContext;true;search;;;Argument[0..1];ldap",
|
||||
// apache
|
||||
"org.apache.directory.ldap.client.api;LdapConnection;true;search;;;Argument[0..2];ldap",
|
||||
// UnboundID: search
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(ReadOnlySearchRequest);;Argument[0];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchRequest);;Argument[0];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,DereferencePolicy,int,int,boolean,Filter,String[]);;Argument[0..7];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,DereferencePolicy,int,int,boolean,String,String[]);;Argument[0..7];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,Filter,String[]);;Argument[0..3];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,String,String[]);;Argument[0..3];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,DereferencePolicy,int,int,boolean,Filter,String[]);;Argument[0..6];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,DereferencePolicy,int,int,boolean,String,String[]);;Argument[0..6];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,Filter,String[]);;Argument[0..2];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,String,String[]);;Argument[0..2];ldap",
|
||||
// UnboundID: searchForEntry
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(ReadOnlySearchRequest);;Argument[0];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(SearchRequest);;Argument[0];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,DereferencePolicy,int,boolean,Filter,String[]);;Argument[0..5];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,DereferencePolicy,int,boolean,String,String[]);;Argument[0..5];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,Filter,String[]);;Argument[0..2];ldap",
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,String,String[]);;Argument[0..2];ldap",
|
||||
// UnboundID: asyncSearch
|
||||
"com.unboundid.ldap.sdk;LDAPConnection;false;asyncSearch;;;Argument[0];ldap",
|
||||
// Spring
|
||||
"org.springframework.ldap.core;LdapTemplate;false;find;;;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;findOne;;;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;search;;;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;searchForContext;;;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;searchForObject;;;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(LdapQuery,String);;Argument[0];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String);;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String,AuthenticatedLdapEntryContextCallback);;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String,AuthenticatedLdapEntryContextCallback,AuthenticationErrorCallback);;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String,AuthenticationErrorCallback);;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String);;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String,AuthenticatedLdapEntryContextCallback);;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String,AuthenticatedLdapEntryContextCallback,AuthenticationErrorCallback);;Argument[0..1];ldap",
|
||||
"org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String,AuthenticationErrorCallback);;Argument[0..1];ldap"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if the method parameter at `index` is susceptible to an LDAP injection attack. */
|
||||
private predicate ldapInjectionSinkMethod(Method m, int index) {
|
||||
jndiLdapInjectionSinkMethod(m, index) or
|
||||
unboundIdLdapInjectionSinkMethod(m, index) or
|
||||
springLdapInjectionSinkMethod(m, index) or
|
||||
apacheLdapInjectionSinkMethod(m, index)
|
||||
}
|
||||
|
||||
/** Holds if the JNDI method parameter at `index` is susceptible to an LDAP injection attack. */
|
||||
private predicate jndiLdapInjectionSinkMethod(Method m, int index) {
|
||||
m.getDeclaringType().getAnAncestor() instanceof TypeDirContext and
|
||||
m.hasName("search") and
|
||||
index in [0 .. 1]
|
||||
}
|
||||
|
||||
/** Holds if the UnboundID method parameter at `index` is susceptible to an LDAP injection attack. */
|
||||
private predicate unboundIdLdapInjectionSinkMethod(Method m, int index) {
|
||||
exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() |
|
||||
m instanceof MethodUnboundIdLDAPConnectionSearch or
|
||||
m instanceof MethodUnboundIdLDAPConnectionAsyncSearch or
|
||||
m instanceof MethodUnboundIdLDAPConnectionSearchForEntry
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the Spring method parameter at `index` is susceptible to an LDAP injection attack. */
|
||||
private predicate springLdapInjectionSinkMethod(Method m, int index) {
|
||||
// LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method
|
||||
(
|
||||
m instanceof MethodSpringLdapTemplateAuthenticate or
|
||||
m instanceof MethodSpringLdapTemplateFind or
|
||||
m instanceof MethodSpringLdapTemplateFindOne or
|
||||
m instanceof MethodSpringLdapTemplateSearch or
|
||||
m instanceof MethodSpringLdapTemplateSearchForContext or
|
||||
m instanceof MethodSpringLdapTemplateSearchForObject
|
||||
) and
|
||||
(
|
||||
// Parameter index is 1 (DN or query) or 2 (filter) if method is not authenticate
|
||||
index in [0 .. 1] and
|
||||
not m instanceof MethodSpringLdapTemplateAuthenticate
|
||||
or
|
||||
// But it's not the last parameter in case of authenticate method (last param is password)
|
||||
index in [0 .. 1] and
|
||||
index < m.getNumberOfParameters() - 1 and
|
||||
m instanceof MethodSpringLdapTemplateAuthenticate
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the Apache LDAP API method parameter at `index` is susceptible to an LDAP injection attack. */
|
||||
private predicate apacheLdapInjectionSinkMethod(Method m, int index) {
|
||||
exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() |
|
||||
m.getDeclaringType().getAnAncestor() instanceof TypeApacheLdapConnection and
|
||||
m.hasName("search")
|
||||
)
|
||||
}
|
||||
|
||||
/** A sanitizer that clears the taint on (boxed) primitive types. */
|
||||
private class DefaultLdapSanitizer extends LdapInjectionSanitizer {
|
||||
DefaultLdapSanitizer() {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
| pom.xml:29:9:32:22 | dependency | Insecure configuration of Spring Boot Actuator exposes sensitive endpoints. |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql
|
||||
@@ -0,0 +1,13 @@
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
public class SensitiveInfo {
|
||||
@RequestMapping
|
||||
public void handleLogin(@RequestParam String username, @RequestParam String password) throws Exception {
|
||||
if (!username.equals("") && password.equals("")) {
|
||||
//Blank processing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#management.endpoints.web.base-path=/admin
|
||||
|
||||
# vulnerable configuration (spring boot 1.0 - 1.4): exposes actuators by default
|
||||
|
||||
# vulnerable configuration (spring boot 1.5+): requires value false to expose sensitive actuators
|
||||
management.security.enabled=false
|
||||
|
||||
# vulnerable configuration (spring boot 2+): exposes health and info only by default, here overridden to expose everything
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoints.web.exposure.exclude=beans
|
||||
|
||||
management.endpoint.shutdown.enabled=true
|
||||
|
||||
management.endpoint.health.show-details=when_authorized
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>spring-boot-actuator-app</groupId>
|
||||
<artifactId>spring-boot-actuator-app</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.8.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
</dependency>
|
||||
<!-- dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,48 +1,81 @@
|
||||
edges
|
||||
| XsltInjection.java:30:44:30:66 | getInputStream(...) : InputStream | XsltInjection.java:31:5:31:59 | newTransformer(...) |
|
||||
| XsltInjection.java:35:66:35:88 | getInputStream(...) : InputStream | XsltInjection.java:36:5:36:74 | newTransformer(...) |
|
||||
| XsltInjection.java:40:45:40:70 | param : String | XsltInjection.java:43:5:43:59 | newTransformer(...) |
|
||||
| XsltInjection.java:47:54:47:76 | getInputStream(...) : InputStream | XsltInjection.java:48:5:48:74 | newTransformer(...) |
|
||||
| XsltInjection.java:52:82:52:104 | getInputStream(...) : InputStream | XsltInjection.java:53:5:53:59 | newTransformer(...) |
|
||||
| XsltInjection.java:30:27:30:67 | new StreamSource(...) : StreamSource | XsltInjection.java:31:5:31:59 | newTransformer(...) |
|
||||
| XsltInjection.java:30:44:30:66 | getInputStream(...) : InputStream | XsltInjection.java:30:27:30:67 | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:35:27:35:90 | new StreamSource(...) : StreamSource | XsltInjection.java:36:5:36:74 | newTransformer(...) |
|
||||
| XsltInjection.java:35:44:35:89 | new InputStreamReader(...) : InputStreamReader | XsltInjection.java:35:27:35:90 | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:35:66:35:88 | getInputStream(...) : InputStream | XsltInjection.java:35:44:35:89 | new InputStreamReader(...) : InputStreamReader |
|
||||
| XsltInjection.java:40:45:40:70 | param : String | XsltInjection.java:42:61:42:64 | xslt : String |
|
||||
| XsltInjection.java:42:27:42:66 | new StreamSource(...) : StreamSource | XsltInjection.java:43:5:43:59 | newTransformer(...) |
|
||||
| XsltInjection.java:42:44:42:65 | new StringReader(...) : StringReader | XsltInjection.java:42:27:42:66 | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:42:61:42:64 | xslt : String | XsltInjection.java:42:44:42:65 | new StringReader(...) : StringReader |
|
||||
| XsltInjection.java:47:24:47:78 | new SAXSource(...) : SAXSource | XsltInjection.java:48:5:48:74 | newTransformer(...) |
|
||||
| XsltInjection.java:47:38:47:77 | new InputSource(...) : InputSource | XsltInjection.java:47:24:47:78 | new SAXSource(...) : SAXSource |
|
||||
| XsltInjection.java:47:54:47:76 | getInputStream(...) : InputStream | XsltInjection.java:47:38:47:77 | new InputSource(...) : InputSource |
|
||||
| XsltInjection.java:52:24:52:107 | new SAXSource(...) : SAXSource | XsltInjection.java:53:5:53:59 | newTransformer(...) |
|
||||
| XsltInjection.java:52:44:52:106 | new InputSource(...) : InputSource | XsltInjection.java:52:24:52:107 | new SAXSource(...) : SAXSource |
|
||||
| XsltInjection.java:52:60:52:105 | new InputStreamReader(...) : InputStreamReader | XsltInjection.java:52:44:52:106 | new InputSource(...) : InputSource |
|
||||
| XsltInjection.java:52:82:52:104 | getInputStream(...) : InputStream | XsltInjection.java:52:60:52:105 | new InputStreamReader(...) : InputStreamReader |
|
||||
| XsltInjection.java:57:91:57:113 | getInputStream(...) : InputStream | XsltInjection.java:58:5:58:59 | newTransformer(...) |
|
||||
| XsltInjection.java:62:120:62:142 | getInputStream(...) : InputStream | XsltInjection.java:63:5:63:74 | newTransformer(...) |
|
||||
| XsltInjection.java:62:98:62:143 | new InputStreamReader(...) : InputStreamReader | XsltInjection.java:63:5:63:74 | newTransformer(...) |
|
||||
| XsltInjection.java:62:120:62:142 | getInputStream(...) : InputStream | XsltInjection.java:62:98:62:143 | new InputStreamReader(...) : InputStreamReader |
|
||||
| XsltInjection.java:67:102:67:124 | getInputStream(...) : InputStream | XsltInjection.java:68:5:68:59 | newTransformer(...) |
|
||||
| XsltInjection.java:72:44:72:66 | getInputStream(...) : InputStream | XsltInjection.java:76:5:76:34 | newTransformer(...) |
|
||||
| XsltInjection.java:80:44:80:66 | getInputStream(...) : InputStream | XsltInjection.java:83:5:83:34 | newTransformer(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:90:5:90:35 | load(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:91:5:91:37 | load30(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:92:5:92:37 | load30(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:93:5:93:37 | load30(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:94:5:94:37 | load30(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:95:5:95:37 | load30(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:96:5:96:37 | load30(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:97:5:97:37 | load30(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:98:5:98:37 | load30(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:99:5:99:37 | load30(...) |
|
||||
| XsltInjection.java:103:36:103:61 | param : String | XsltInjection.java:108:5:108:46 | load(...) |
|
||||
| XsltInjection.java:103:36:103:61 | param : String | XsltInjection.java:110:5:110:50 | load(...) |
|
||||
| XsltInjection.java:105:44:105:66 | getInputStream(...) : InputStream | XsltInjection.java:109:5:109:49 | load(...) |
|
||||
| XsltInjection.java:72:27:72:67 | new StreamSource(...) : StreamSource | XsltInjection.java:76:5:76:34 | newTransformer(...) |
|
||||
| XsltInjection.java:72:44:72:66 | getInputStream(...) : InputStream | XsltInjection.java:72:27:72:67 | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:80:27:80:67 | new StreamSource(...) : StreamSource | XsltInjection.java:83:5:83:34 | newTransformer(...) |
|
||||
| XsltInjection.java:80:44:80:66 | getInputStream(...) : InputStream | XsltInjection.java:80:27:80:67 | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | XsltInjection.java:90:5:90:35 | load(...) |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | XsltInjection.java:91:5:91:37 | load30(...) |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | XsltInjection.java:92:5:92:37 | load30(...) |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | XsltInjection.java:93:5:93:37 | load30(...) |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | XsltInjection.java:94:5:94:37 | load30(...) |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | XsltInjection.java:95:5:95:37 | load30(...) |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | XsltInjection.java:96:5:96:37 | load30(...) |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | XsltInjection.java:97:5:97:37 | load30(...) |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | XsltInjection.java:98:5:98:37 | load30(...) |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | XsltInjection.java:99:5:99:37 | load30(...) |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:103:36:103:61 | param : String | XsltInjection.java:104:23:104:27 | param : String |
|
||||
| XsltInjection.java:104:15:104:28 | new URI(...) : URI | XsltInjection.java:108:5:108:46 | load(...) |
|
||||
| XsltInjection.java:104:15:104:28 | new URI(...) : URI | XsltInjection.java:110:5:110:50 | load(...) |
|
||||
| XsltInjection.java:104:23:104:27 | param : String | XsltInjection.java:104:15:104:28 | new URI(...) : URI |
|
||||
| XsltInjection.java:105:27:105:67 | new StreamSource(...) : StreamSource | XsltInjection.java:109:5:109:49 | load(...) |
|
||||
| XsltInjection.java:105:44:105:66 | getInputStream(...) : InputStream | XsltInjection.java:105:27:105:67 | new StreamSource(...) : StreamSource |
|
||||
nodes
|
||||
| XsltInjection.java:30:27:30:67 | new StreamSource(...) : StreamSource | semmle.label | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:30:44:30:66 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:31:5:31:59 | newTransformer(...) | semmle.label | newTransformer(...) |
|
||||
| XsltInjection.java:35:27:35:90 | new StreamSource(...) : StreamSource | semmle.label | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:35:44:35:89 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader |
|
||||
| XsltInjection.java:35:66:35:88 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:36:5:36:74 | newTransformer(...) | semmle.label | newTransformer(...) |
|
||||
| XsltInjection.java:40:45:40:70 | param : String | semmle.label | param : String |
|
||||
| XsltInjection.java:42:27:42:66 | new StreamSource(...) : StreamSource | semmle.label | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:42:44:42:65 | new StringReader(...) : StringReader | semmle.label | new StringReader(...) : StringReader |
|
||||
| XsltInjection.java:42:61:42:64 | xslt : String | semmle.label | xslt : String |
|
||||
| XsltInjection.java:43:5:43:59 | newTransformer(...) | semmle.label | newTransformer(...) |
|
||||
| XsltInjection.java:47:24:47:78 | new SAXSource(...) : SAXSource | semmle.label | new SAXSource(...) : SAXSource |
|
||||
| XsltInjection.java:47:38:47:77 | new InputSource(...) : InputSource | semmle.label | new InputSource(...) : InputSource |
|
||||
| XsltInjection.java:47:54:47:76 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:48:5:48:74 | newTransformer(...) | semmle.label | newTransformer(...) |
|
||||
| XsltInjection.java:52:24:52:107 | new SAXSource(...) : SAXSource | semmle.label | new SAXSource(...) : SAXSource |
|
||||
| XsltInjection.java:52:44:52:106 | new InputSource(...) : InputSource | semmle.label | new InputSource(...) : InputSource |
|
||||
| XsltInjection.java:52:60:52:105 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader |
|
||||
| XsltInjection.java:52:82:52:104 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:53:5:53:59 | newTransformer(...) | semmle.label | newTransformer(...) |
|
||||
| XsltInjection.java:57:91:57:113 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:58:5:58:59 | newTransformer(...) | semmle.label | newTransformer(...) |
|
||||
| XsltInjection.java:62:98:62:143 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader |
|
||||
| XsltInjection.java:62:120:62:142 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:63:5:63:74 | newTransformer(...) | semmle.label | newTransformer(...) |
|
||||
| XsltInjection.java:67:102:67:124 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:68:5:68:59 | newTransformer(...) | semmle.label | newTransformer(...) |
|
||||
| XsltInjection.java:72:27:72:67 | new StreamSource(...) : StreamSource | semmle.label | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:72:44:72:66 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:76:5:76:34 | newTransformer(...) | semmle.label | newTransformer(...) |
|
||||
| XsltInjection.java:80:27:80:67 | new StreamSource(...) : StreamSource | semmle.label | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:80:44:80:66 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:83:5:83:34 | newTransformer(...) | semmle.label | newTransformer(...) |
|
||||
| XsltInjection.java:87:27:87:67 | new StreamSource(...) : StreamSource | semmle.label | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:87:44:87:66 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:90:5:90:35 | load(...) | semmle.label | load(...) |
|
||||
| XsltInjection.java:91:5:91:37 | load30(...) | semmle.label | load30(...) |
|
||||
@@ -55,6 +88,9 @@ nodes
|
||||
| XsltInjection.java:98:5:98:37 | load30(...) | semmle.label | load30(...) |
|
||||
| XsltInjection.java:99:5:99:37 | load30(...) | semmle.label | load30(...) |
|
||||
| XsltInjection.java:103:36:103:61 | param : String | semmle.label | param : String |
|
||||
| XsltInjection.java:104:15:104:28 | new URI(...) : URI | semmle.label | new URI(...) : URI |
|
||||
| XsltInjection.java:104:23:104:27 | param : String | semmle.label | param : String |
|
||||
| XsltInjection.java:105:27:105:67 | new StreamSource(...) : StreamSource | semmle.label | new StreamSource(...) : StreamSource |
|
||||
| XsltInjection.java:105:44:105:66 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XsltInjection.java:108:5:108:46 | load(...) | semmle.label | load(...) |
|
||||
| XsltInjection.java:109:5:109:49 | load(...) | semmle.label | load(...) |
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groovy.lang.GroovyCodeSource;
|
||||
import groovy.lang.GroovyObject;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class GroovyClassLoaderTest extends HttpServlet {
|
||||
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
String script = request.getParameter("script");
|
||||
final GroovyClassLoader classLoader = new GroovyClassLoader();
|
||||
Class groovy = classLoader.parseClass(script);
|
||||
GroovyObject groovyObj = (GroovyObject) groovy.newInstance();
|
||||
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
String script = request.getParameter("script");
|
||||
final GroovyClassLoader classLoader = new GroovyClassLoader();
|
||||
GroovyCodeSource gcs = new GroovyCodeSource(script, "test", "Test");
|
||||
Class groovy = classLoader.parseClass(gcs);
|
||||
GroovyObject groovyObj = (GroovyObject) groovy.newInstance();
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import groovy.util.Eval;
|
||||
|
||||
public class GroovyEvalTest extends HttpServlet {
|
||||
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String script = request.getParameter("script");
|
||||
Eval.me(script);
|
||||
}
|
||||
|
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String script = request.getParameter("script");
|
||||
Eval.me("test", "result", script);
|
||||
}
|
||||
|
||||
protected void doPut(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String script = request.getParameter("script");
|
||||
Eval.x("result2", script);
|
||||
|
||||
}
|
||||
|
||||
protected void doDelete(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String script = request.getParameter("script");
|
||||
Eval.xy("result3", "result4", script);
|
||||
}
|
||||
|
||||
protected void doPatch(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String script = request.getParameter("script");
|
||||
Eval.xyz("result3", "result4", "aaa", script);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
edges
|
||||
| ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:22:29:22:51 | expression : String | ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:23:31:23:40 | expression |
|
||||
| ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:30:44:30:66 | expression : String | ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:31:27:31:36 | expression |
|
||||
| GroovyClassLoaderTest.java:16:29:16:58 | getParameter(...) : String | GroovyClassLoaderTest.java:18:51:18:56 | script |
|
||||
| GroovyClassLoaderTest.java:29:29:29:58 | getParameter(...) : String | GroovyClassLoaderTest.java:32:51:32:53 | gcs |
|
||||
| GroovyEvalTest.java:12:25:12:54 | getParameter(...) : String | GroovyEvalTest.java:13:17:13:22 | script |
|
||||
| GroovyEvalTest.java:12:25:12:54 | getParameter(...) : String | GroovyEvalTest.java:13:17:13:22 | script : String |
|
||||
| GroovyEvalTest.java:13:17:13:22 | script : String | ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:22:29:22:51 | expression : String |
|
||||
| GroovyEvalTest.java:18:25:18:54 | getParameter(...) : String | GroovyEvalTest.java:19:35:19:40 | script |
|
||||
| GroovyEvalTest.java:24:25:24:54 | getParameter(...) : String | GroovyEvalTest.java:25:27:25:32 | script |
|
||||
| GroovyEvalTest.java:24:25:24:54 | getParameter(...) : String | GroovyEvalTest.java:25:27:25:32 | script : String |
|
||||
| GroovyEvalTest.java:25:27:25:32 | script : String | ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:30:44:30:66 | expression : String |
|
||||
| GroovyEvalTest.java:31:25:31:54 | getParameter(...) : String | GroovyEvalTest.java:32:39:32:44 | script |
|
||||
| GroovyEvalTest.java:37:25:37:54 | getParameter(...) : String | GroovyEvalTest.java:38:47:38:52 | script |
|
||||
| GroovyShellTest.java:15:25:15:54 | getParameter(...) : String | GroovyShellTest.java:16:24:16:29 | script |
|
||||
| GroovyShellTest.java:22:25:22:54 | getParameter(...) : String | GroovyShellTest.java:23:24:23:29 | script |
|
||||
| GroovyShellTest.java:29:25:29:54 | getParameter(...) : String | GroovyShellTest.java:30:24:30:29 | script |
|
||||
| GroovyShellTest.java:36:25:36:54 | getParameter(...) : String | GroovyShellTest.java:37:19:37:24 | script |
|
||||
| GroovyShellTest.java:43:25:43:54 | getParameter(...) : String | GroovyShellTest.java:45:19:45:21 | gcs |
|
||||
| GroovyShellTest.java:51:25:51:54 | getParameter(...) : String | GroovyShellTest.java:53:24:53:26 | gcs |
|
||||
| GroovyShellTest.java:59:25:59:54 | getParameter(...) : String | GroovyShellTest.java:60:21:60:26 | script |
|
||||
nodes
|
||||
| ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:22:29:22:51 | expression : String | semmle.label | expression : String |
|
||||
| ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:23:31:23:40 | expression | semmle.label | expression |
|
||||
| ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:30:44:30:66 | expression : String | semmle.label | expression : String |
|
||||
| ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:31:27:31:36 | expression | semmle.label | expression |
|
||||
| GroovyClassLoaderTest.java:16:29:16:58 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyClassLoaderTest.java:18:51:18:56 | script | semmle.label | script |
|
||||
| GroovyClassLoaderTest.java:29:29:29:58 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyClassLoaderTest.java:32:51:32:53 | gcs | semmle.label | gcs |
|
||||
| GroovyEvalTest.java:12:25:12:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyEvalTest.java:13:17:13:22 | script | semmle.label | script |
|
||||
| GroovyEvalTest.java:13:17:13:22 | script : String | semmle.label | script : String |
|
||||
| GroovyEvalTest.java:18:25:18:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyEvalTest.java:19:35:19:40 | script | semmle.label | script |
|
||||
| GroovyEvalTest.java:24:25:24:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyEvalTest.java:25:27:25:32 | script | semmle.label | script |
|
||||
| GroovyEvalTest.java:25:27:25:32 | script : String | semmle.label | script : String |
|
||||
| GroovyEvalTest.java:31:25:31:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyEvalTest.java:32:39:32:44 | script | semmle.label | script |
|
||||
| GroovyEvalTest.java:37:25:37:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyEvalTest.java:38:47:38:52 | script | semmle.label | script |
|
||||
| GroovyShellTest.java:15:25:15:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyShellTest.java:16:24:16:29 | script | semmle.label | script |
|
||||
| GroovyShellTest.java:22:25:22:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyShellTest.java:23:24:23:29 | script | semmle.label | script |
|
||||
| GroovyShellTest.java:29:25:29:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyShellTest.java:30:24:30:29 | script | semmle.label | script |
|
||||
| GroovyShellTest.java:36:25:36:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyShellTest.java:37:19:37:24 | script | semmle.label | script |
|
||||
| GroovyShellTest.java:43:25:43:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyShellTest.java:45:19:45:21 | gcs | semmle.label | gcs |
|
||||
| GroovyShellTest.java:51:25:51:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyShellTest.java:53:24:53:26 | gcs | semmle.label | gcs |
|
||||
| GroovyShellTest.java:59:25:59:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| GroovyShellTest.java:60:21:60:26 | script | semmle.label | script |
|
||||
#select
|
||||
| ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:23:31:23:40 | expression | GroovyEvalTest.java:12:25:12:54 | getParameter(...) : String | ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:23:31:23:40 | expression | Groovy Injection from $@. | GroovyEvalTest.java:12:25:12:54 | getParameter(...) | this user input |
|
||||
| ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:31:27:31:36 | expression | GroovyEvalTest.java:24:25:24:54 | getParameter(...) : String | ../../../stubs/groovy-all-3.0.7/groovy/util/Eval.java:31:27:31:36 | expression | Groovy Injection from $@. | GroovyEvalTest.java:24:25:24:54 | getParameter(...) | this user input |
|
||||
| GroovyClassLoaderTest.java:18:51:18:56 | script | GroovyClassLoaderTest.java:16:29:16:58 | getParameter(...) : String | GroovyClassLoaderTest.java:18:51:18:56 | script | Groovy Injection from $@. | GroovyClassLoaderTest.java:16:29:16:58 | getParameter(...) | this user input |
|
||||
| GroovyClassLoaderTest.java:32:51:32:53 | gcs | GroovyClassLoaderTest.java:29:29:29:58 | getParameter(...) : String | GroovyClassLoaderTest.java:32:51:32:53 | gcs | Groovy Injection from $@. | GroovyClassLoaderTest.java:29:29:29:58 | getParameter(...) | this user input |
|
||||
| GroovyEvalTest.java:13:17:13:22 | script | GroovyEvalTest.java:12:25:12:54 | getParameter(...) : String | GroovyEvalTest.java:13:17:13:22 | script | Groovy Injection from $@. | GroovyEvalTest.java:12:25:12:54 | getParameter(...) | this user input |
|
||||
| GroovyEvalTest.java:19:35:19:40 | script | GroovyEvalTest.java:18:25:18:54 | getParameter(...) : String | GroovyEvalTest.java:19:35:19:40 | script | Groovy Injection from $@. | GroovyEvalTest.java:18:25:18:54 | getParameter(...) | this user input |
|
||||
| GroovyEvalTest.java:25:27:25:32 | script | GroovyEvalTest.java:24:25:24:54 | getParameter(...) : String | GroovyEvalTest.java:25:27:25:32 | script | Groovy Injection from $@. | GroovyEvalTest.java:24:25:24:54 | getParameter(...) | this user input |
|
||||
| GroovyEvalTest.java:32:39:32:44 | script | GroovyEvalTest.java:31:25:31:54 | getParameter(...) : String | GroovyEvalTest.java:32:39:32:44 | script | Groovy Injection from $@. | GroovyEvalTest.java:31:25:31:54 | getParameter(...) | this user input |
|
||||
| GroovyEvalTest.java:38:47:38:52 | script | GroovyEvalTest.java:37:25:37:54 | getParameter(...) : String | GroovyEvalTest.java:38:47:38:52 | script | Groovy Injection from $@. | GroovyEvalTest.java:37:25:37:54 | getParameter(...) | this user input |
|
||||
| GroovyShellTest.java:16:24:16:29 | script | GroovyShellTest.java:15:25:15:54 | getParameter(...) : String | GroovyShellTest.java:16:24:16:29 | script | Groovy Injection from $@. | GroovyShellTest.java:15:25:15:54 | getParameter(...) | this user input |
|
||||
| GroovyShellTest.java:23:24:23:29 | script | GroovyShellTest.java:22:25:22:54 | getParameter(...) : String | GroovyShellTest.java:23:24:23:29 | script | Groovy Injection from $@. | GroovyShellTest.java:22:25:22:54 | getParameter(...) | this user input |
|
||||
| GroovyShellTest.java:30:24:30:29 | script | GroovyShellTest.java:29:25:29:54 | getParameter(...) : String | GroovyShellTest.java:30:24:30:29 | script | Groovy Injection from $@. | GroovyShellTest.java:29:25:29:54 | getParameter(...) | this user input |
|
||||
| GroovyShellTest.java:37:19:37:24 | script | GroovyShellTest.java:36:25:36:54 | getParameter(...) : String | GroovyShellTest.java:37:19:37:24 | script | Groovy Injection from $@. | GroovyShellTest.java:36:25:36:54 | getParameter(...) | this user input |
|
||||
| GroovyShellTest.java:45:19:45:21 | gcs | GroovyShellTest.java:43:25:43:54 | getParameter(...) : String | GroovyShellTest.java:45:19:45:21 | gcs | Groovy Injection from $@. | GroovyShellTest.java:43:25:43:54 | getParameter(...) | this user input |
|
||||
| GroovyShellTest.java:53:24:53:26 | gcs | GroovyShellTest.java:51:25:51:54 | getParameter(...) : String | GroovyShellTest.java:53:24:53:26 | gcs | Groovy Injection from $@. | GroovyShellTest.java:51:25:51:54 | getParameter(...) | this user input |
|
||||
| GroovyShellTest.java:60:21:60:26 | script | GroovyShellTest.java:59:25:59:54 | getParameter(...) : String | GroovyShellTest.java:60:21:60:26 | script | Groovy Injection from $@. | GroovyShellTest.java:59:25:59:54 | getParameter(...) | this user input |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-094/GroovyInjection.ql
|
||||
@@ -0,0 +1,63 @@
|
||||
import groovy.lang.GroovyCodeSource;
|
||||
import groovy.lang.GroovyShell;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class GroovyShellTest extends HttpServlet {
|
||||
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
GroovyShell shell = new GroovyShell();
|
||||
String script = request.getParameter("script");
|
||||
shell.evaluate(script);
|
||||
}
|
||||
|
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
GroovyShell shell = new GroovyShell();
|
||||
String script = request.getParameter("script");
|
||||
shell.evaluate(script, "test");
|
||||
}
|
||||
|
||||
protected void doPut(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
GroovyShell shell = new GroovyShell();
|
||||
String script = request.getParameter("script");
|
||||
shell.evaluate(script, "test", "test2");
|
||||
}
|
||||
|
||||
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
GroovyShell shell = new GroovyShell();
|
||||
String script = request.getParameter("script");
|
||||
shell.run(script, "_", new String[]{});
|
||||
}
|
||||
|
||||
protected void doHead(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
GroovyShell shell = new GroovyShell();
|
||||
String script = request.getParameter("script");
|
||||
GroovyCodeSource gcs = new GroovyCodeSource(script, "test", "Test");
|
||||
shell.run(gcs, new String[]{});
|
||||
}
|
||||
|
||||
protected void doDelete(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
GroovyShell shell = new GroovyShell();
|
||||
String script = request.getParameter("script");
|
||||
GroovyCodeSource gcs = new GroovyCodeSource(script, "test", "Test");
|
||||
shell.evaluate(gcs);
|
||||
}
|
||||
|
||||
protected void doPatch(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
GroovyShell shell = new GroovyShell();
|
||||
String script = request.getParameter("script");
|
||||
shell.parse(script);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
edges
|
||||
| JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:23:54:23:58 | bytes [post update] : byte[] |
|
||||
| JakartaExpressionInjection.java:23:54:23:58 | bytes [post update] : byte[] | JakartaExpressionInjection.java:25:31:25:40 | expression : String |
|
||||
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:32:24:32:33 | expression : String |
|
||||
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:40:24:40:33 | expression : String |
|
||||
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:48:24:48:33 | expression : String |
|
||||
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:59:24:59:33 | expression : String |
|
||||
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:67:24:67:33 | expression : String |
|
||||
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:75:24:75:33 | expression : String |
|
||||
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:85:24:85:33 | expression : String |
|
||||
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:95:24:95:33 | expression : String |
|
||||
| JakartaExpressionInjection.java:32:24:32:33 | expression : String | JakartaExpressionInjection.java:34:28:34:37 | expression |
|
||||
| JakartaExpressionInjection.java:40:24:40:33 | expression : String | JakartaExpressionInjection.java:42:32:42:41 | expression |
|
||||
| JakartaExpressionInjection.java:48:24:48:33 | expression : String | JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression |
|
||||
| JakartaExpressionInjection.java:59:24:59:33 | expression : String | JakartaExpressionInjection.java:61:32:61:41 | expression |
|
||||
| JakartaExpressionInjection.java:67:24:67:33 | expression : String | JakartaExpressionInjection.java:69:43:69:52 | expression |
|
||||
| JakartaExpressionInjection.java:75:24:75:33 | expression : String | JakartaExpressionInjection.java:79:13:79:13 | e |
|
||||
| JakartaExpressionInjection.java:85:24:85:33 | expression : String | JakartaExpressionInjection.java:89:13:89:13 | e |
|
||||
| JakartaExpressionInjection.java:95:24:95:33 | expression : String | JakartaExpressionInjection.java:99:13:99:13 | e |
|
||||
nodes
|
||||
| JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| JakartaExpressionInjection.java:23:54:23:58 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
|
||||
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | semmle.label | expression : String |
|
||||
| JakartaExpressionInjection.java:32:24:32:33 | expression : String | semmle.label | expression : String |
|
||||
| JakartaExpressionInjection.java:34:28:34:37 | expression | semmle.label | expression |
|
||||
| JakartaExpressionInjection.java:40:24:40:33 | expression : String | semmle.label | expression : String |
|
||||
| JakartaExpressionInjection.java:42:32:42:41 | expression | semmle.label | expression |
|
||||
| JakartaExpressionInjection.java:48:24:48:33 | expression : String | semmle.label | expression : String |
|
||||
| JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression | semmle.label | lambdaExpression |
|
||||
| JakartaExpressionInjection.java:59:24:59:33 | expression : String | semmle.label | expression : String |
|
||||
| JakartaExpressionInjection.java:61:32:61:41 | expression | semmle.label | expression |
|
||||
| JakartaExpressionInjection.java:67:24:67:33 | expression : String | semmle.label | expression : String |
|
||||
| JakartaExpressionInjection.java:69:43:69:52 | expression | semmle.label | expression |
|
||||
| JakartaExpressionInjection.java:75:24:75:33 | expression : String | semmle.label | expression : String |
|
||||
| JakartaExpressionInjection.java:79:13:79:13 | e | semmle.label | e |
|
||||
| JakartaExpressionInjection.java:85:24:85:33 | expression : String | semmle.label | expression : String |
|
||||
| JakartaExpressionInjection.java:89:13:89:13 | e | semmle.label | e |
|
||||
| JakartaExpressionInjection.java:95:24:95:33 | expression : String | semmle.label | expression : String |
|
||||
| JakartaExpressionInjection.java:99:13:99:13 | e | semmle.label | e |
|
||||
#select
|
||||
| JakartaExpressionInjection.java:34:28:34:37 | expression | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:34:28:34:37 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input |
|
||||
| JakartaExpressionInjection.java:42:32:42:41 | expression | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:42:32:42:41 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input |
|
||||
| JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input |
|
||||
| JakartaExpressionInjection.java:61:32:61:41 | expression | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:61:32:61:41 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input |
|
||||
| JakartaExpressionInjection.java:69:43:69:52 | expression | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:69:43:69:52 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input |
|
||||
| JakartaExpressionInjection.java:79:13:79:13 | e | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:79:13:79:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input |
|
||||
| JakartaExpressionInjection.java:89:13:89:13 | e | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:89:13:89:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input |
|
||||
| JakartaExpressionInjection.java:99:13:99:13 | e | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:99:13:99:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input |
|
||||
@@ -0,0 +1,103 @@
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.el.ELContext;
|
||||
import javax.el.ELManager;
|
||||
import javax.el.ELProcessor;
|
||||
import javax.el.ExpressionFactory;
|
||||
import javax.el.LambdaExpression;
|
||||
import javax.el.MethodExpression;
|
||||
import javax.el.StandardELContext;
|
||||
import javax.el.ValueExpression;
|
||||
|
||||
public class JakartaExpressionInjection {
|
||||
|
||||
// calls a consumer with a string received from a socket
|
||||
private static void testWithSocket(Consumer<String> action) throws IOException {
|
||||
try (ServerSocket serverSocket = new ServerSocket(0)) {
|
||||
try (Socket socket = serverSocket.accept()) {
|
||||
byte[] bytes = new byte[1024];
|
||||
int n = socket.getInputStream().read(bytes);
|
||||
String expression = new String(bytes, 0, n);
|
||||
action.accept(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BAD (untrusted input to ELProcessor.eval)
|
||||
private static void testWithELProcessorEval() throws IOException {
|
||||
testWithSocket(expression -> {
|
||||
ELProcessor processor = new ELProcessor();
|
||||
processor.eval(expression);
|
||||
});
|
||||
}
|
||||
|
||||
// BAD (untrusted input to ELProcessor.getValue)
|
||||
private static void testWithELProcessorGetValue() throws IOException {
|
||||
testWithSocket(expression -> {
|
||||
ELProcessor processor = new ELProcessor();
|
||||
processor.getValue(expression, Object.class);
|
||||
});
|
||||
}
|
||||
|
||||
// BAD (untrusted input to LambdaExpression.invoke)
|
||||
private static void testWithLambdaExpressionInvoke() throws IOException {
|
||||
testWithSocket(expression -> {
|
||||
ExpressionFactory factory = ELManager.getExpressionFactory();
|
||||
StandardELContext context = new StandardELContext(factory);
|
||||
ValueExpression valueExpression = factory.createValueExpression(context, expression, Object.class);
|
||||
LambdaExpression lambdaExpression = new LambdaExpression(new ArrayList<>(), valueExpression);
|
||||
lambdaExpression.invoke(context, new Object[0]);
|
||||
});
|
||||
}
|
||||
|
||||
// BAD (untrusted input to ELProcessor.setValue)
|
||||
private static void testWithELProcessorSetValue() throws IOException {
|
||||
testWithSocket(expression -> {
|
||||
ELProcessor processor = new ELProcessor();
|
||||
processor.setValue(expression, new Object());
|
||||
});
|
||||
}
|
||||
|
||||
// BAD (untrusted input to ELProcessor.setVariable)
|
||||
private static void testWithELProcessorSetVariable() throws IOException {
|
||||
testWithSocket(expression -> {
|
||||
ELProcessor processor = new ELProcessor();
|
||||
processor.setVariable("test", expression);
|
||||
});
|
||||
}
|
||||
|
||||
// BAD (untrusted input to ValueExpression.getValue when it was created by JUEL)
|
||||
private static void testWithJuelValueExpressionGetValue() throws IOException {
|
||||
testWithSocket(expression -> {
|
||||
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
|
||||
ELContext context = new de.odysseus.el.util.SimpleContext();
|
||||
ValueExpression e = factory.createValueExpression(context, expression, Object.class);
|
||||
e.getValue(context);
|
||||
});
|
||||
}
|
||||
|
||||
// BAD (untrusted input to ValueExpression.setValue when it was created by JUEL)
|
||||
private static void testWithJuelValueExpressionSetValue() throws IOException {
|
||||
testWithSocket(expression -> {
|
||||
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
|
||||
ELContext context = new de.odysseus.el.util.SimpleContext();
|
||||
ValueExpression e = factory.createValueExpression(context, expression, Object.class);
|
||||
e.setValue(context, new Object());
|
||||
});
|
||||
}
|
||||
|
||||
// BAD (untrusted input to MethodExpression.invoke when it was created by JUEL)
|
||||
private static void testWithJuelMethodExpressionInvoke() throws IOException {
|
||||
testWithSocket(expression -> {
|
||||
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
|
||||
ELContext context = new de.odysseus.el.util.SimpleContext();
|
||||
MethodExpression e = factory.createMethodExpression(context, expression, Object.class, new Class[0]);
|
||||
e.invoke(context, new Object[0]);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
|
||||
@@ -8,7 +8,8 @@ edges
|
||||
| Jexl2Injection.java:54:73:54:87 | jexlExpr : String | Jexl2Injection.java:57:9:57:35 | parse(...) |
|
||||
| Jexl2Injection.java:60:72:60:86 | jexlExpr : String | Jexl2Injection.java:63:9:63:35 | parse(...) |
|
||||
| Jexl2Injection.java:66:73:66:87 | jexlExpr : String | Jexl2Injection.java:69:9:69:44 | createTemplate(...) |
|
||||
| Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:78:31:78:38 | jexlExpr : String |
|
||||
| Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:76:54:76:58 | bytes [post update] : byte[] |
|
||||
| Jexl2Injection.java:76:54:76:58 | bytes [post update] : byte[] | Jexl2Injection.java:78:31:78:38 | jexlExpr : String |
|
||||
| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:86:24:86:56 | jexlExpr : String |
|
||||
| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:90:24:90:68 | jexlExpr : String |
|
||||
| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:94:24:94:52 | jexlExpr : String |
|
||||
@@ -46,7 +47,8 @@ edges
|
||||
| Jexl3Injection.java:66:73:66:87 | jexlExpr : String | Jexl3Injection.java:69:9:69:39 | createExpression(...) |
|
||||
| Jexl3Injection.java:72:72:72:86 | jexlExpr : String | Jexl3Injection.java:75:9:75:37 | createTemplate(...) |
|
||||
| Jexl3Injection.java:78:54:78:68 | jexlExpr : String | Jexl3Injection.java:84:13:84:13 | e |
|
||||
| Jexl3Injection.java:94:25:94:47 | getInputStream(...) : InputStream | Jexl3Injection.java:96:31:96:38 | jexlExpr : String |
|
||||
| Jexl3Injection.java:94:25:94:47 | getInputStream(...) : InputStream | Jexl3Injection.java:94:54:94:58 | bytes [post update] : byte[] |
|
||||
| Jexl3Injection.java:94:54:94:58 | bytes [post update] : byte[] | Jexl3Injection.java:96:31:96:38 | jexlExpr : String |
|
||||
| Jexl3Injection.java:96:31:96:38 | jexlExpr : String | Jexl3Injection.java:104:24:104:56 | jexlExpr : String |
|
||||
| Jexl3Injection.java:96:31:96:38 | jexlExpr : String | Jexl3Injection.java:108:24:108:68 | jexlExpr : String |
|
||||
| Jexl3Injection.java:96:31:96:38 | jexlExpr : String | Jexl3Injection.java:112:24:112:52 | jexlExpr : String |
|
||||
@@ -103,6 +105,7 @@ nodes
|
||||
| Jexl2Injection.java:66:73:66:87 | jexlExpr : String | semmle.label | jexlExpr : String |
|
||||
| Jexl2Injection.java:69:9:69:44 | createTemplate(...) | semmle.label | createTemplate(...) |
|
||||
| Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| Jexl2Injection.java:76:54:76:58 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
|
||||
| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | semmle.label | jexlExpr : String |
|
||||
| Jexl2Injection.java:86:24:86:56 | jexlExpr : String | semmle.label | jexlExpr : String |
|
||||
| Jexl2Injection.java:86:24:86:56 | jexlExpr : String | semmle.label | jexlExpr : String |
|
||||
@@ -143,6 +146,7 @@ nodes
|
||||
| Jexl3Injection.java:78:54:78:68 | jexlExpr : String | semmle.label | jexlExpr : String |
|
||||
| Jexl3Injection.java:84:13:84:13 | e | semmle.label | e |
|
||||
| Jexl3Injection.java:94:25:94:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| Jexl3Injection.java:94:54:94:58 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
|
||||
| Jexl3Injection.java:96:31:96:38 | jexlExpr : String | semmle.label | jexlExpr : String |
|
||||
| Jexl3Injection.java:104:24:104:56 | jexlExpr : String | semmle.label | jexlExpr : String |
|
||||
| Jexl3Injection.java:104:24:104:56 | jexlExpr : String | semmle.label | jexlExpr : String |
|
||||
|
||||
@@ -10,7 +10,9 @@ edges
|
||||
| MvelInjection.java:77:40:77:51 | read(...) : String | MvelInjection.java:77:7:77:52 | compileTemplate(...) |
|
||||
| MvelInjection.java:81:54:81:65 | read(...) : String | MvelInjection.java:82:29:82:46 | compile(...) |
|
||||
| MvelInjection.java:86:58:86:69 | read(...) : String | MvelInjection.java:88:32:88:41 | expression |
|
||||
| MvelInjection.java:92:27:92:49 | getInputStream(...) : InputStream | MvelInjection.java:95:14:95:36 | new String(...) : String |
|
||||
| MvelInjection.java:92:27:92:49 | getInputStream(...) : InputStream | MvelInjection.java:94:15:94:16 | is : InputStream |
|
||||
| MvelInjection.java:94:15:94:16 | is : InputStream | MvelInjection.java:94:23:94:27 | bytes [post update] : byte[] |
|
||||
| MvelInjection.java:94:23:94:27 | bytes [post update] : byte[] | MvelInjection.java:95:14:95:36 | new String(...) : String |
|
||||
| MvelInjection.java:95:14:95:36 | new String(...) : String | MvelInjection.java:25:15:25:26 | read(...) |
|
||||
| MvelInjection.java:95:14:95:36 | new String(...) : String | MvelInjection.java:29:54:29:65 | read(...) : String |
|
||||
| MvelInjection.java:95:14:95:36 | new String(...) : String | MvelInjection.java:34:58:34:69 | read(...) : String |
|
||||
@@ -46,6 +48,8 @@ nodes
|
||||
| MvelInjection.java:86:58:86:69 | read(...) : String | semmle.label | read(...) : String |
|
||||
| MvelInjection.java:88:32:88:41 | expression | semmle.label | expression |
|
||||
| MvelInjection.java:92:27:92:49 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| MvelInjection.java:94:15:94:16 | is : InputStream | semmle.label | is : InputStream |
|
||||
| MvelInjection.java:94:23:94:27 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
|
||||
| MvelInjection.java:95:14:95:36 | new String(...) : String | semmle.label | new String(...) : String |
|
||||
#select
|
||||
| MvelInjection.java:25:15:25:26 | read(...) | MvelInjection.java:92:27:92:49 | getInputStream(...) : InputStream | MvelInjection.java:25:15:25:26 | read(...) | MVEL injection from $@. | MvelInjection.java:92:27:92:49 | getInputStream(...) | this user input |
|
||||
|
||||
@@ -1,22 +1,46 @@
|
||||
edges
|
||||
| SpelInjection.java:15:22:15:44 | getInputStream(...) : InputStream | SpelInjection.java:23:5:23:14 | expression |
|
||||
| SpelInjection.java:27:22:27:44 | getInputStream(...) : InputStream | SpelInjection.java:34:5:34:14 | expression |
|
||||
| SpelInjection.java:38:22:38:44 | getInputStream(...) : InputStream | SpelInjection.java:48:5:48:14 | expression |
|
||||
| SpelInjection.java:52:22:52:44 | getInputStream(...) : InputStream | SpelInjection.java:59:5:59:14 | expression |
|
||||
| SpelInjection.java:63:22:63:44 | getInputStream(...) : InputStream | SpelInjection.java:70:5:70:14 | expression |
|
||||
| SpelInjection.java:74:22:74:44 | getInputStream(...) : InputStream | SpelInjection.java:83:5:83:14 | expression |
|
||||
| SpelInjection.java:15:22:15:44 | getInputStream(...) : InputStream | SpelInjection.java:18:13:18:14 | in : InputStream |
|
||||
| SpelInjection.java:18:13:18:14 | in : InputStream | SpelInjection.java:18:21:18:25 | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:18:21:18:25 | bytes [post update] : byte[] | SpelInjection.java:23:5:23:14 | expression |
|
||||
| SpelInjection.java:27:22:27:44 | getInputStream(...) : InputStream | SpelInjection.java:30:13:30:14 | in : InputStream |
|
||||
| SpelInjection.java:30:13:30:14 | in : InputStream | SpelInjection.java:30:21:30:25 | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:30:21:30:25 | bytes [post update] : byte[] | SpelInjection.java:34:5:34:14 | expression |
|
||||
| SpelInjection.java:38:22:38:44 | getInputStream(...) : InputStream | SpelInjection.java:41:13:41:14 | in : InputStream |
|
||||
| SpelInjection.java:41:13:41:14 | in : InputStream | SpelInjection.java:41:21:41:25 | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:41:21:41:25 | bytes [post update] : byte[] | SpelInjection.java:48:5:48:14 | expression |
|
||||
| SpelInjection.java:52:22:52:44 | getInputStream(...) : InputStream | SpelInjection.java:55:13:55:14 | in : InputStream |
|
||||
| SpelInjection.java:55:13:55:14 | in : InputStream | SpelInjection.java:55:21:55:25 | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:55:21:55:25 | bytes [post update] : byte[] | SpelInjection.java:59:5:59:14 | expression |
|
||||
| SpelInjection.java:63:22:63:44 | getInputStream(...) : InputStream | SpelInjection.java:66:13:66:14 | in : InputStream |
|
||||
| SpelInjection.java:66:13:66:14 | in : InputStream | SpelInjection.java:66:21:66:25 | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:66:21:66:25 | bytes [post update] : byte[] | SpelInjection.java:70:5:70:14 | expression |
|
||||
| SpelInjection.java:74:22:74:44 | getInputStream(...) : InputStream | SpelInjection.java:77:13:77:14 | in : InputStream |
|
||||
| SpelInjection.java:77:13:77:14 | in : InputStream | SpelInjection.java:77:21:77:25 | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:77:21:77:25 | bytes [post update] : byte[] | SpelInjection.java:83:5:83:14 | expression |
|
||||
nodes
|
||||
| SpelInjection.java:15:22:15:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| SpelInjection.java:18:13:18:14 | in : InputStream | semmle.label | in : InputStream |
|
||||
| SpelInjection.java:18:21:18:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:23:5:23:14 | expression | semmle.label | expression |
|
||||
| SpelInjection.java:27:22:27:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| SpelInjection.java:30:13:30:14 | in : InputStream | semmle.label | in : InputStream |
|
||||
| SpelInjection.java:30:21:30:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:34:5:34:14 | expression | semmle.label | expression |
|
||||
| SpelInjection.java:38:22:38:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| SpelInjection.java:41:13:41:14 | in : InputStream | semmle.label | in : InputStream |
|
||||
| SpelInjection.java:41:21:41:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:48:5:48:14 | expression | semmle.label | expression |
|
||||
| SpelInjection.java:52:22:52:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| SpelInjection.java:55:13:55:14 | in : InputStream | semmle.label | in : InputStream |
|
||||
| SpelInjection.java:55:21:55:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:59:5:59:14 | expression | semmle.label | expression |
|
||||
| SpelInjection.java:63:22:63:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| SpelInjection.java:66:13:66:14 | in : InputStream | semmle.label | in : InputStream |
|
||||
| SpelInjection.java:66:21:66:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:70:5:70:14 | expression | semmle.label | expression |
|
||||
| SpelInjection.java:74:22:74:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| SpelInjection.java:77:13:77:14 | in : InputStream | semmle.label | in : InputStream |
|
||||
| SpelInjection.java:77:21:77:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
|
||||
| SpelInjection.java:83:5:83:14 | expression | semmle.label | expression |
|
||||
#select
|
||||
| SpelInjection.java:23:5:23:14 | expression | SpelInjection.java:15:22:15:44 | getInputStream(...) : InputStream | SpelInjection.java:23:5:23:14 | expression | SpEL injection from $@. | SpelInjection.java:15:22:15:44 | getInputStream(...) | this user input |
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../../stubs/apache-commons-jexl-3.1:${testdir}/../../../../stubs/scriptengine
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../../stubs/apache-commons-jexl-3.1:${testdir}/../../../../stubs/scriptengine:${testdir}/../../../../stubs/java-ee-el:${testdir}/../../../../stubs/juel-2.2:${testdir}/../../../stubs/groovy-all-3.0.7:${testdir}/../../../../stubs/servlet-api-2.4
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
edges
|
||||
| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:25:39:25:52 | tokenCookieStr : String |
|
||||
| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie |
|
||||
| SensitiveCookieNotHttpOnly.java:25:28:25:64 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie |
|
||||
| SensitiveCookieNotHttpOnly.java:25:39:25:52 | tokenCookieStr : String | SensitiveCookieNotHttpOnly.java:25:28:25:64 | new Cookie(...) : Cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... |
|
||||
| SensitiveCookieNotHttpOnly.java:42:42:42:57 | ... + ... : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... |
|
||||
| SensitiveCookieNotHttpOnly.java:52:56:52:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:52:42:52:124 | toString(...) |
|
||||
| SensitiveCookieNotHttpOnly.java:63:51:63:70 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:65:42:65:47 | keyStr |
|
||||
| SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
|
||||
| SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
|
||||
| SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
|
||||
| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:89:36:89:51 | PRESTO_UI_COOKIE : String |
|
||||
| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:89:25:89:57 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:89:36:89:51 | PRESTO_UI_COOKIE : String | SensitiveCookieNotHttpOnly.java:89:25:89:57 | new Cookie(...) : Cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | SensitiveCookieNotHttpOnly.java:110:25:110:64 | createAuthenticationCookie(...) : Cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:110:25:110:64 | createAuthenticationCookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie |
|
||||
nodes
|
||||
| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | semmle.label | "jwt_token" : String |
|
||||
| SensitiveCookieNotHttpOnly.java:25:28:25:64 | new Cookie(...) : Cookie | semmle.label | new Cookie(...) : Cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:25:39:25:52 | tokenCookieStr : String | semmle.label | tokenCookieStr : String |
|
||||
| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | semmle.label | jwtCookie |
|
||||
| SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" : String | semmle.label | "token=" : String |
|
||||
| SensitiveCookieNotHttpOnly.java:42:42:42:57 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | semmle.label | ... + ... |
|
||||
| SensitiveCookieNotHttpOnly.java:52:42:52:124 | toString(...) | semmle.label | toString(...) |
|
||||
| SensitiveCookieNotHttpOnly.java:52:56:52:75 | "session-access-key" : String | semmle.label | "session-access-key" : String |
|
||||
| SensitiveCookieNotHttpOnly.java:63:51:63:70 | "session-access-key" : String | semmle.label | "session-access-key" : String |
|
||||
| SensitiveCookieNotHttpOnly.java:65:42:65:47 | keyStr | semmle.label | keyStr |
|
||||
| SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" : String | semmle.label | "token=" : String |
|
||||
| SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | semmle.label | secString |
|
||||
| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | semmle.label | "Presto-UI-Token" : String |
|
||||
| SensitiveCookieNotHttpOnly.java:89:25:89:57 | new Cookie(...) : Cookie | semmle.label | new Cookie(...) : Cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:89:36:89:51 | PRESTO_UI_COOKIE : String | semmle.label | PRESTO_UI_COOKIE : String |
|
||||
| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | semmle.label | cookie : Cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:110:25:110:64 | createAuthenticationCookie(...) : Cookie | semmle.label | createAuthenticationCookie(...) : Cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie | semmle.label | cookie |
|
||||
#select
|
||||
| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" | This sensitive cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" | This sensitive cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:57 | ... + ... : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:42:42:42:57 | ... + ... | This sensitive cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | This sensitive cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:52:42:52:124 | toString(...) | SensitiveCookieNotHttpOnly.java:52:56:52:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:52:42:52:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:52:56:52:75 | "session-access-key" | This sensitive cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:65:42:65:47 | keyStr | SensitiveCookieNotHttpOnly.java:63:51:63:70 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:65:42:65:47 | keyStr | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:63:51:63:70 | "session-access-key" | This sensitive cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" | This sensitive cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... | This sensitive cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... | This sensitive cookie |
|
||||
| SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" | This sensitive cookie |
|
||||
@@ -0,0 +1,164 @@
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import javax.ws.rs.core.NewCookie;
|
||||
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
|
||||
class SensitiveCookieNotHttpOnly {
|
||||
// GOOD - Tests adding a sensitive cookie with the `HttpOnly` flag set.
|
||||
public void addCookie(String jwt_token, HttpServletRequest request, HttpServletResponse response) {
|
||||
Cookie jwtCookie = new Cookie("jwt_token", jwt_token);
|
||||
jwtCookie.setPath("/");
|
||||
jwtCookie.setMaxAge(3600*24*7);
|
||||
jwtCookie.setHttpOnly(true);
|
||||
response.addCookie(jwtCookie);
|
||||
}
|
||||
|
||||
// BAD - Tests adding a sensitive cookie without the `HttpOnly` flag set.
|
||||
public void addCookie2(String jwt_token, String userId, HttpServletRequest request, HttpServletResponse response) {
|
||||
String tokenCookieStr = "jwt_token";
|
||||
Cookie jwtCookie = new Cookie(tokenCookieStr, jwt_token);
|
||||
Cookie userIdCookie = new Cookie("user_id", userId);
|
||||
jwtCookie.setPath("/");
|
||||
userIdCookie.setPath("/");
|
||||
jwtCookie.setMaxAge(3600*24*7);
|
||||
userIdCookie.setMaxAge(3600*24*7);
|
||||
response.addCookie(jwtCookie);
|
||||
response.addCookie(userIdCookie);
|
||||
}
|
||||
|
||||
// GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set.
|
||||
public void addCookie3(String authId, HttpServletRequest request, HttpServletResponse response) {
|
||||
response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure");
|
||||
}
|
||||
|
||||
// BAD - Tests set a sensitive cookie header without the `HttpOnly` flag set.
|
||||
public void addCookie4(String authId, HttpServletRequest request, HttpServletResponse response) {
|
||||
response.addHeader("Set-Cookie", "token=" +authId + ";Secure");
|
||||
}
|
||||
|
||||
// GOOD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through string concatenation.
|
||||
public void addCookie5(String accessKey, HttpServletRequest request, HttpServletResponse response) {
|
||||
response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true) + ";HttpOnly");
|
||||
}
|
||||
|
||||
// BAD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` without the `HttpOnly` flag set.
|
||||
public void addCookie6(String accessKey, HttpServletRequest request, HttpServletResponse response) {
|
||||
response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true).toString());
|
||||
}
|
||||
|
||||
// GOOD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through the constructor.
|
||||
public void addCookie7(String accessKey, HttpServletRequest request, HttpServletResponse response) {
|
||||
NewCookie accessKeyCookie = new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true);
|
||||
response.setHeader("Set-Cookie", accessKeyCookie.toString());
|
||||
}
|
||||
|
||||
// BAD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` without the `HttpOnly` flag set.
|
||||
public void addCookie8(String accessKey, HttpServletRequest request, HttpServletResponse response) {
|
||||
NewCookie accessKeyCookie = new NewCookie("session-access-key", accessKey, "/", null, 0, null, 86400, true);
|
||||
String keyStr = accessKeyCookie.toString();
|
||||
response.setHeader("Set-Cookie", keyStr);
|
||||
}
|
||||
|
||||
// BAD - Tests set a sensitive cookie header using a variable without the `HttpOnly` flag set.
|
||||
public void addCookie9(String authId, HttpServletRequest request, HttpServletResponse response) {
|
||||
String secString = "token=" +authId + ";Secure";
|
||||
response.addHeader("Set-Cookie", secString);
|
||||
}
|
||||
|
||||
// GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set using `String.format(...)`.
|
||||
public void addCookie10(HttpServletRequest request, HttpServletResponse response) {
|
||||
response.addHeader("SET-COOKIE", String.format("%s=%s;HttpOnly", "sessionkey", request.getSession().getAttribute("sessionkey")));
|
||||
}
|
||||
|
||||
public Cookie createHttpOnlyAuthenticationCookie(HttpServletRequest request, String jwt) {
|
||||
String PRESTO_UI_COOKIE = "Presto-UI-Token";
|
||||
Cookie cookie = new Cookie(PRESTO_UI_COOKIE, jwt);
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setPath("/ui");
|
||||
return cookie;
|
||||
}
|
||||
|
||||
public Cookie createAuthenticationCookie(HttpServletRequest request, String jwt) {
|
||||
String PRESTO_UI_COOKIE = "Presto-UI-Token";
|
||||
Cookie cookie = new Cookie(PRESTO_UI_COOKIE, jwt);
|
||||
cookie.setPath("/ui");
|
||||
return cookie;
|
||||
}
|
||||
|
||||
public Cookie removeAuthenticationCookie(HttpServletRequest request, String jwt) {
|
||||
String PRESTO_UI_COOKIE = "Presto-UI-Token";
|
||||
Cookie cookie = new Cookie(PRESTO_UI_COOKIE, jwt);
|
||||
cookie.setPath("/ui");
|
||||
cookie.setMaxAge(0);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
// GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set using a wrapper method.
|
||||
public void addCookie11(HttpServletRequest request, HttpServletResponse response, String jwt) {
|
||||
Cookie cookie = createHttpOnlyAuthenticationCookie(request, jwt);
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
// BAD - Tests set a sensitive cookie header without the `HttpOnly` flag set using a wrapper method.
|
||||
public void addCookie12(HttpServletRequest request, HttpServletResponse response, String jwt) {
|
||||
Cookie cookie = createAuthenticationCookie(request, jwt);
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
// GOOD - Tests remove a sensitive cookie header without the `HttpOnly` flag set using a wrapper method.
|
||||
public void addCookie13(HttpServletRequest request, HttpServletResponse response, String jwt) {
|
||||
Cookie cookie = removeAuthenticationCookie(request, jwt);
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
private Cookie createCookie(String name, String value, Boolean httpOnly){
|
||||
Cookie cookie = null;
|
||||
cookie = new Cookie(name, value);
|
||||
cookie.setDomain("/");
|
||||
cookie.setHttpOnly(httpOnly);
|
||||
|
||||
//for production https
|
||||
cookie.setSecure(true);
|
||||
|
||||
cookie.setMaxAge(60*60*24*30);
|
||||
cookie.setPath("/");
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
// GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set through a boolean variable using a wrapper method.
|
||||
public void addCookie14(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
|
||||
response.addCookie(createCookie("refresh_token", refreshToken, true));
|
||||
}
|
||||
|
||||
// BAD (but not detected) - Tests set a sensitive cookie header with the `HttpOnly` flag not set through a boolean variable using a wrapper method.
|
||||
// This example is missed because the `cookie.setHttpOnly` call in `createCookie` is thought to maybe set the HTTP-only flag, and the `cookie`
|
||||
// object flows to this `addCookie` call.
|
||||
public void addCookie15(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
|
||||
response.addCookie(createCookie("refresh_token", refreshToken, false));
|
||||
}
|
||||
|
||||
// GOOD - CSRF token doesn't need to have the `HttpOnly` flag set.
|
||||
public void addCsrfCookie(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
// Spring put the CSRF token in session attribute "_csrf"
|
||||
CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
|
||||
|
||||
// Send the cookie only if the token has changed
|
||||
String actualToken = request.getHeader("X-CSRF-TOKEN");
|
||||
if (actualToken == null || !actualToken.equals(csrfToken.getToken())) {
|
||||
// Session cookie that can be used by AngularJS
|
||||
String pCookieName = "CSRF-TOKEN";
|
||||
Cookie cookie = new Cookie(pCookieName, csrfToken.getToken());
|
||||
cookie.setMaxAge(-1);
|
||||
cookie.setHttpOnly(false);
|
||||
cookie.setPath("/");
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
|
||||
@@ -0,0 +1 @@
|
||||
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/jsr311-api-1.1.1:${testdir}/../../../../stubs/springframework-5.2.3
|
||||
@@ -0,0 +1,161 @@
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.Gson;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Controller
|
||||
public class JsonpController {
|
||||
|
||||
private static HashMap hashMap = new HashMap();
|
||||
|
||||
static {
|
||||
hashMap.put("username","admin");
|
||||
hashMap.put("password","123456");
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp1")
|
||||
@ResponseBody
|
||||
public String bad1(HttpServletRequest request) {
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
Gson gson = new Gson();
|
||||
String result = gson.toJson(hashMap);
|
||||
resultStr = jsonpCallback + "(" + result + ")";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp2")
|
||||
@ResponseBody
|
||||
public String bad2(HttpServletRequest request) {
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp3")
|
||||
@ResponseBody
|
||||
public String bad3(HttpServletRequest request) {
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
String jsonStr = getJsonStr(hashMap);
|
||||
resultStr = jsonpCallback + "(" + jsonStr + ")";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp4")
|
||||
@ResponseBody
|
||||
public String bad4(HttpServletRequest request) {
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
String restr = JSONObject.toJSONString(hashMap);
|
||||
resultStr = jsonpCallback + "(" + restr + ");";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp5")
|
||||
@ResponseBody
|
||||
public void bad5(HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
PrintWriter pw = null;
|
||||
Gson gson = new Gson();
|
||||
String result = gson.toJson(hashMap);
|
||||
String resultStr = null;
|
||||
pw = response.getWriter();
|
||||
resultStr = jsonpCallback + "(" + result + ")";
|
||||
pw.println(resultStr);
|
||||
}
|
||||
|
||||
@GetMapping(value = "jsonp6")
|
||||
@ResponseBody
|
||||
public void bad6(HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
PrintWriter pw = null;
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String result = mapper.writeValueAsString(hashMap);
|
||||
String resultStr = null;
|
||||
pw = response.getWriter();
|
||||
resultStr = jsonpCallback + "(" + result + ")";
|
||||
pw.println(resultStr);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "jsonp7", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public String bad7(HttpServletRequest request) {
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
Gson gson = new Gson();
|
||||
String result = gson.toJson(hashMap);
|
||||
resultStr = jsonpCallback + "(" + result + ")";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "jsonp11")
|
||||
@ResponseBody
|
||||
public String good1(HttpServletRequest request) {
|
||||
JSONObject parameterObj = readToJSONObect(request);
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
String restr = JSONObject.toJSONString(hashMap);
|
||||
resultStr = jsonpCallback + "(" + restr + ");";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "jsonp12")
|
||||
@ResponseBody
|
||||
public String good2(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
|
||||
if(null == file){
|
||||
return "upload file error";
|
||||
}
|
||||
String fileName = file.getOriginalFilename();
|
||||
System.out.println("file operations");
|
||||
String resultStr = null;
|
||||
String jsonpCallback = request.getParameter("jsonpCallback");
|
||||
String restr = JSONObject.toJSONString(hashMap);
|
||||
resultStr = jsonpCallback + "(" + restr + ");";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
public static JSONObject readToJSONObect(HttpServletRequest request){
|
||||
String jsonText = readPostContent(request);
|
||||
JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
public static String readPostContent(HttpServletRequest request){
|
||||
BufferedReader in= null;
|
||||
String content = null;
|
||||
String line = null;
|
||||
try {
|
||||
in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
|
||||
StringBuilder buf = new StringBuilder();
|
||||
while ((line = in.readLine()) != null) {
|
||||
buf.append(line);
|
||||
}
|
||||
content = buf.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String uri = request.getRequestURI();
|
||||
return content;
|
||||
}
|
||||
|
||||
public static String getJsonStr(Object result) {
|
||||
return JSONObject.toJSONString(result);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user