mirror of
https://github.com/github/codeql.git
synced 2026-05-01 03:35:13 +02:00
Merge remote-tracking branch 'upstream/main' into UseOfLessTrustedSource
This commit is contained in:
2
java/change-notes/2021-04-14-membertype.md
Normal file
2
java/change-notes/2021-04-14-membertype.md
Normal file
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* A CodeQL class `MemberType` is introduced to describe nested classes. Its `getQualifiedName` method returns `$`-delimited nested type names (for example, `mypackage.Outer$Middle$Inner`), where previously the same type would be named differently depending on whether it was addressed as a `NestedType` or a `Member`.
|
||||
@@ -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,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() }
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@ class EqualsMethod extends Method {
|
||||
class HashCodeMethod extends Method {
|
||||
HashCodeMethod() {
|
||||
this.hasName("hashCode") and
|
||||
this.getNumberOfParameters() = 0
|
||||
this.hasNoParameters()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,6 +365,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. */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
49
java/ql/src/semmle/code/java/dataflow/FlowSummary.qll
Normal file
49
java/ql/src/semmle/code/java/dataflow/FlowSummary.qll
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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 below
|
||||
private module Summaries { }
|
||||
|
||||
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.
|
||||
*/
|
||||
@@ -147,7 +100,7 @@ class Content extends TContent {
|
||||
}
|
||||
}
|
||||
|
||||
private class FieldContent extends Content, TFieldContent {
|
||||
class FieldContent extends Content, TFieldContent {
|
||||
InstanceField f;
|
||||
|
||||
FieldContent() { this = TFieldContent(f) }
|
||||
@@ -161,11 +114,11 @@ private class FieldContent extends Content, TFieldContent {
|
||||
}
|
||||
}
|
||||
|
||||
private class CollectionContent extends Content, TCollectionContent {
|
||||
class CollectionContent extends Content, TCollectionContent {
|
||||
override string toString() { result = "collection" }
|
||||
}
|
||||
|
||||
private class ArrayContent extends Content, TArrayContent {
|
||||
class ArrayContent extends Content, TArrayContent {
|
||||
override string toString() { result = "array" }
|
||||
}
|
||||
|
||||
@@ -180,6 +133,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 +160,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 +170,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 +185,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 +199,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 +305,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.
|
||||
@@ -415,33 +150,8 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,6 +65,8 @@ private predicate localAdditionalBasicTaintStep(DataFlow::Node src, DataFlow::No
|
||||
arg.isVararg() and
|
||||
sink.(DataFlow::ImplicitVarargsArray).getCall() = arg.getCall()
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(src, sink, false)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,46 @@
|
||||
edges
|
||||
| JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | 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: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
|
||||
@@ -1,2 +1 @@
|
||||
//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
|
||||
@@ -0,0 +1,41 @@
|
||||
edges
|
||||
| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie |
|
||||
| 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:91:16:91:21 | 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: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: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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
edges
|
||||
| JsonpController.java:33:32:33:68 | getParameter(...) : String | JsonpController.java:37:16:37:24 | resultStr |
|
||||
| JsonpController.java:36:21:36:54 | ... + ... : String | JsonpController.java:37:16:37:24 | resultStr |
|
||||
| JsonpController.java:44:32:44:68 | getParameter(...) : String | JsonpController.java:46:16:46:24 | resultStr |
|
||||
| JsonpController.java:45:21:45:80 | ... + ... : String | JsonpController.java:46:16:46:24 | resultStr |
|
||||
| JsonpController.java:53:32:53:68 | getParameter(...) : String | JsonpController.java:56:16:56:24 | resultStr |
|
||||
| JsonpController.java:55:21:55:55 | ... + ... : String | JsonpController.java:56:16:56:24 | resultStr |
|
||||
| JsonpController.java:63:32:63:68 | getParameter(...) : String | JsonpController.java:66:16:66:24 | resultStr |
|
||||
| JsonpController.java:65:21:65:54 | ... + ... : String | JsonpController.java:66:16:66:24 | resultStr |
|
||||
| JsonpController.java:73:32:73:68 | getParameter(...) : String | JsonpController.java:80:20:80:28 | resultStr |
|
||||
| JsonpController.java:79:21:79:54 | ... + ... : String | JsonpController.java:80:20:80:28 | resultStr |
|
||||
| JsonpController.java:87:32:87:68 | getParameter(...) : String | JsonpController.java:94:20:94:28 | resultStr |
|
||||
| JsonpController.java:93:21:93:54 | ... + ... : String | JsonpController.java:94:20:94:28 | resultStr |
|
||||
| JsonpController.java:101:32:101:68 | getParameter(...) : String | JsonpController.java:105:16:105:24 | resultStr |
|
||||
| JsonpController.java:104:21:104:54 | ... + ... : String | JsonpController.java:105:16:105:24 | resultStr |
|
||||
| JsonpController.java:115:21:115:54 | ... + ... : String | JsonpController.java:116:16:116:24 | resultStr |
|
||||
| JsonpController.java:130:21:130:54 | ... + ... : String | JsonpController.java:131:16:131:24 | resultStr |
|
||||
nodes
|
||||
| JsonpController.java:33:32:33:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| JsonpController.java:36:21:36:54 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| JsonpController.java:37:16:37:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:37:16:37:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:44:32:44:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| JsonpController.java:45:21:45:80 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| JsonpController.java:46:16:46:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:46:16:46:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:53:32:53:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| JsonpController.java:55:21:55:55 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| JsonpController.java:56:16:56:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:56:16:56:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:63:32:63:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| JsonpController.java:65:21:65:54 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| JsonpController.java:66:16:66:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:66:16:66:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:73:32:73:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| JsonpController.java:79:21:79:54 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| JsonpController.java:80:20:80:28 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:80:20:80:28 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:87:32:87:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| JsonpController.java:93:21:93:54 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| JsonpController.java:94:20:94:28 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:94:20:94:28 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:101:32:101:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| JsonpController.java:104:21:104:54 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| JsonpController.java:105:16:105:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:105:16:105:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:115:21:115:54 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| JsonpController.java:116:16:116:24 | resultStr | semmle.label | resultStr |
|
||||
| JsonpController.java:130:21:130:54 | ... + ... : String | semmle.label | ... + ... : String |
|
||||
| JsonpController.java:131:16:131:24 | resultStr | semmle.label | resultStr |
|
||||
#select
|
||||
| JsonpController.java:37:16:37:24 | resultStr | JsonpController.java:33:32:33:68 | getParameter(...) : String | JsonpController.java:37:16:37:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:33:32:33:68 | getParameter(...) | this user input |
|
||||
| JsonpController.java:46:16:46:24 | resultStr | JsonpController.java:44:32:44:68 | getParameter(...) : String | JsonpController.java:46:16:46:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:44:32:44:68 | getParameter(...) | this user input |
|
||||
| JsonpController.java:56:16:56:24 | resultStr | JsonpController.java:53:32:53:68 | getParameter(...) : String | JsonpController.java:56:16:56:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:53:32:53:68 | getParameter(...) | this user input |
|
||||
| JsonpController.java:66:16:66:24 | resultStr | JsonpController.java:63:32:63:68 | getParameter(...) : String | JsonpController.java:66:16:66:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:63:32:63:68 | getParameter(...) | this user input |
|
||||
| JsonpController.java:80:20:80:28 | resultStr | JsonpController.java:73:32:73:68 | getParameter(...) : String | JsonpController.java:80:20:80:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:73:32:73:68 | getParameter(...) | this user input |
|
||||
| JsonpController.java:94:20:94:28 | resultStr | JsonpController.java:87:32:87:68 | getParameter(...) : String | JsonpController.java:94:20:94:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:87:32:87:68 | getParameter(...) | this user input |
|
||||
| JsonpController.java:105:16:105:24 | resultStr | JsonpController.java:101:32:101:68 | getParameter(...) : String | JsonpController.java:105:16:105:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:101:32:101:68 | getParameter(...) | this user input |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-352/JsonpInjection.ql
|
||||
@@ -0,0 +1 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/fastjson-1.2.74/:${testdir}/../../../../stubs/gson-2.8.6/:${testdir}/../../../../stubs/jackson-databind-2.10/:${testdir}/../../../../stubs/springframework-5.2.3/
|
||||
@@ -0,0 +1,5 @@
|
||||
| configuration.properties:6:1:6:25 | ldap.password=mysecpass | Plaintext credentials ldap.password have cleartext value mysecpass in properties file |
|
||||
| configuration.properties:18:1:18:35 | datasource1.password=Passw0rd@123 | Plaintext credentials datasource1.password have cleartext value Passw0rd@123 in properties file |
|
||||
| configuration.properties:25:1:25:31 | mail.password=MysecPWxWa@1993 | Plaintext credentials mail.password have cleartext value MysecPWxWa@1993 in properties file |
|
||||
| configuration.properties:33:1:33:50 | com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA | Plaintext credentials com.example.aws.s3.access_key have cleartext value AKMAMQPBYMCD6YSAYCBA in properties file |
|
||||
| configuration.properties:34:1:34:70 | com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k | Plaintext credentials com.example.aws.s3.secret_key have cleartext value 8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k in properties file |
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Note this is similar to src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
|
||||
* except we do not filter out test files.
|
||||
*/
|
||||
|
||||
import java
|
||||
import experimental.semmle.code.java.frameworks.CredentialsInPropertiesFile
|
||||
|
||||
from CredentialsConfig cc
|
||||
select cc, cc.getConfigDesc()
|
||||
@@ -0,0 +1,37 @@
|
||||
#***************************** 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==
|
||||
|
||||
#*************************** Mail Connection **********************************#
|
||||
mail.username = test@example.com
|
||||
#### BAD: Mail credentials are stored in cleartext ####
|
||||
mail.password = MysecPWxWa@1993
|
||||
#### GOOD: Mail credentials are stored in the encrypted format ####
|
||||
mail.password = M*********@1993
|
||||
|
||||
#*************************** AWS S3 Connection **********************************#
|
||||
com.example.aws.s3.bucket_name=com-bucket-1
|
||||
com.example.aws.s3.directory_name=com-directory-1
|
||||
#### BAD: Access keys are stored in properties file in cleartext ####
|
||||
com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA
|
||||
com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k
|
||||
#### GOOD: Access keys are not stored in properties file ####
|
||||
com.example.aws.s3.access_key=${ENV:AWS_ACCESS_KEY_ID}
|
||||
com.example.aws.s3.secret_key=${ENV:AWS_SECRET_ACCESS_KEY}
|
||||
@@ -0,0 +1,9 @@
|
||||
# GOOD: UI display messages; not credentials
|
||||
prompt.username=Username
|
||||
prompt.password=Password
|
||||
|
||||
forgot_password.error=Please enter a valid email address.
|
||||
reset_password.error=Passwords must match and not be empty.
|
||||
|
||||
login.password_expired=Your current password has expired. Please reset your password.
|
||||
login.login_failure=Unable to verify username or password. Please try again.
|
||||
@@ -2,5 +2,7 @@ import semmle.code.java.dataflow.DataFlow
|
||||
import semmle.code.java.dataflow.internal.TaintTrackingUtil
|
||||
|
||||
from DataFlow::Node src, DataFlow::Node sink
|
||||
where localAdditionalTaintStep(src, sink)
|
||||
where
|
||||
localAdditionalTaintStep(src, sink) and
|
||||
src.getLocation().getFile().getExtension() = "java"
|
||||
select src, sink
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
class NumericLiterals {
|
||||
void negativeLiterals() {
|
||||
float f = -1f;
|
||||
double d = -1d;
|
||||
int i1 = -2147483647;
|
||||
int i2 = -2147483648; // CodeQL models minus as part of literal
|
||||
int i3 = -0b10000000000000000000000000000000; // binary
|
||||
int i4 = -020000000000; // octal
|
||||
int i5 = -0x80000000; // hex
|
||||
long l1 = -9223372036854775807L;
|
||||
long l2 = -9223372036854775808L; // CodeQL models minus as part of literal
|
||||
long l3 = -0b1000000000000000000000000000000000000000000000000000000000000000L; // binary
|
||||
long l4 = -01000000000000000000000L; // octal
|
||||
long l5 = -0x8000000000000000L; // hex
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
| NumericLiterals.java:3:14:3:15 | 1f | 1.0 | NumericLiterals.java:3:13:3:15 | -... |
|
||||
| NumericLiterals.java:4:15:4:16 | 1d | 1.0 | NumericLiterals.java:4:14:4:16 | -... |
|
||||
| NumericLiterals.java:5:13:5:22 | 2147483647 | 2147483647 | NumericLiterals.java:5:12:5:22 | -... |
|
||||
| NumericLiterals.java:6:12:6:22 | -2147483648 | -2147483648 | NumericLiterals.java:6:7:6:22 | i2 |
|
||||
| NumericLiterals.java:7:13:7:46 | 0b10000000000000000000000000000000 | -2147483648 | NumericLiterals.java:7:12:7:46 | -... |
|
||||
| NumericLiterals.java:8:13:8:24 | 020000000000 | -2147483648 | NumericLiterals.java:8:12:8:24 | -... |
|
||||
| NumericLiterals.java:9:13:9:22 | 0x80000000 | -2147483648 | NumericLiterals.java:9:12:9:22 | -... |
|
||||
| NumericLiterals.java:10:14:10:33 | 9223372036854775807L | 9223372036854775807 | NumericLiterals.java:10:13:10:33 | -... |
|
||||
| NumericLiterals.java:11:13:11:33 | -9223372036854775808L | -9223372036854775808 | NumericLiterals.java:11:8:11:33 | l2 |
|
||||
| NumericLiterals.java:12:14:12:80 | 0b1000000000000000000000000000000000000000000000000000000000000000L | -9223372036854775808 | NumericLiterals.java:12:13:12:80 | -... |
|
||||
| NumericLiterals.java:13:14:13:37 | 01000000000000000000000L | -9223372036854775808 | NumericLiterals.java:13:13:13:37 | -... |
|
||||
| NumericLiterals.java:14:14:14:32 | 0x8000000000000000L | -9223372036854775808 | NumericLiterals.java:14:13:14:32 | -... |
|
||||
@@ -0,0 +1,9 @@
|
||||
import java
|
||||
|
||||
from Literal l
|
||||
where
|
||||
l instanceof IntegerLiteral or
|
||||
l instanceof LongLiteral or
|
||||
l instanceof FloatingPointLiteral or
|
||||
l instanceof DoubleLiteral
|
||||
select l, l.getValue(), l.getParent()
|
||||
@@ -2,12 +2,19 @@ edges
|
||||
| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:24:20:24:23 | temp |
|
||||
| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:27:21:27:24 | temp |
|
||||
| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:30:44:30:47 | temp |
|
||||
| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:34:21:34:24 | temp |
|
||||
| Test.java:79:74:79:97 | getInputStream(...) : ServletInputStream | Test.java:82:67:82:81 | ... + ... |
|
||||
nodes
|
||||
| Test.java:19:18:19:38 | getHostName(...) : String | semmle.label | getHostName(...) : String |
|
||||
| Test.java:24:20:24:23 | temp | semmle.label | temp |
|
||||
| Test.java:27:21:27:24 | temp | semmle.label | temp |
|
||||
| Test.java:30:44:30:47 | temp | semmle.label | temp |
|
||||
| Test.java:34:21:34:24 | temp | semmle.label | temp |
|
||||
| Test.java:79:74:79:97 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream |
|
||||
| Test.java:82:67:82:81 | ... + ... | semmle.label | ... + ... |
|
||||
#select
|
||||
| Test.java:24:11:24:24 | new File(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:24:20:24:23 | temp | $@ flows to here and is used in a path. | Test.java:19:18:19:38 | getHostName(...) | User-provided value |
|
||||
| Test.java:27:11:27:25 | get(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:27:21:27:24 | temp | $@ flows to here and is used in a path. | Test.java:19:18:19:38 | getHostName(...) | User-provided value |
|
||||
| Test.java:30:11:30:48 | getPath(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:30:44:30:47 | temp | $@ flows to here and is used in a path. | Test.java:19:18:19:38 | getHostName(...) | User-provided value |
|
||||
| Test.java:34:12:34:25 | new File(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:34:21:34:24 | temp | $@ flows to here and is used in a path. | Test.java:19:18:19:38 | getHostName(...) | User-provided value |
|
||||
| Test.java:82:52:82:88 | new FileWriter(...) | Test.java:79:74:79:97 | getInputStream(...) : ServletInputStream | Test.java:82:67:82:81 | ... + ... | $@ flows to here and is used in a path. | Test.java:79:74:79:97 | getInputStream(...) | User-provided value |
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
package test.cwe22.semmle.tests;
|
||||
|
||||
|
||||
import javax.servlet.http.*;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -28,6 +28,11 @@ class Test {
|
||||
|
||||
// BAD: construct a path with user input
|
||||
path = FileSystems.getDefault().getPath(temp);
|
||||
|
||||
// BAD: insufficient check
|
||||
if (temp.startsWith("/some_safe_dir/")) {
|
||||
file = new File(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void doGet2(InetAddress address)
|
||||
@@ -68,4 +73,13 @@ class Test {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public class MyServlet extends HttpServlet {
|
||||
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
|
||||
String filename = br.readLine();
|
||||
// BAD: construct a file path with user input
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter("dir/"+filename, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/servlet-api-2.4
|
||||
@@ -0,0 +1,41 @@
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.transform.sax.SAXSource;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.XMLReader;
|
||||
import org.xml.sax.helpers.XMLReaderFactory;
|
||||
|
||||
public class SAXSourceTests {
|
||||
|
||||
public void unsafeSource(Socket sock) throws Exception {
|
||||
XMLReader reader = XMLReaderFactory.createXMLReader();
|
||||
SAXSource source = new SAXSource(reader, new InputSource(sock.getInputStream()));
|
||||
JAXBContext jc = JAXBContext.newInstance(Object.class);
|
||||
Unmarshaller um = jc.createUnmarshaller();
|
||||
um.unmarshal(source); // BAD
|
||||
}
|
||||
|
||||
public void explicitlySafeSource1(Socket sock) throws Exception {
|
||||
XMLReader reader = XMLReaderFactory.createXMLReader();
|
||||
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd",false);
|
||||
SAXSource source = new SAXSource(reader, new InputSource(sock.getInputStream())); // GOOD
|
||||
}
|
||||
|
||||
public void createdSafeSource(Socket sock) throws Exception {
|
||||
SAXParserFactory factory = SAXParserFactory.newInstance();
|
||||
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
SAXParser parser = factory.newSAXParser();
|
||||
XMLReader reader = parser.getXMLReader();
|
||||
SAXSource source = new SAXSource(parser.getXMLReader(), new InputSource(sock.getInputStream())); // GOOD
|
||||
SAXSource source2 = new SAXSource(reader, new InputSource(sock.getInputStream())); // GOOD
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.sax.SAXSource;
|
||||
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
public class UnmarshallerTests {
|
||||
|
||||
public void safeUnmarshal(Socket sock) throws Exception {
|
||||
SAXParserFactory spf = SAXParserFactory.newInstance();
|
||||
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
JAXBContext jc = JAXBContext.newInstance(Object.class);
|
||||
Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(), new InputSource(sock.getInputStream()));
|
||||
Unmarshaller um = jc.createUnmarshaller();
|
||||
um.unmarshal(xmlSource); //safe
|
||||
}
|
||||
|
||||
public void unsafeUnmarshal(Socket sock) throws Exception {
|
||||
SAXParserFactory spf = SAXParserFactory.newInstance();
|
||||
JAXBContext jc = JAXBContext.newInstance(Object.class);
|
||||
Unmarshaller um = jc.createUnmarshaller();
|
||||
um.unmarshal(sock.getInputStream()); //unsafe
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ edges
|
||||
| DocumentBuilderTests.java:93:51:93:71 | getInputStream(...) : InputStream | DocumentBuilderTests.java:94:16:94:38 | getInputSource(...) |
|
||||
| DocumentBuilderTests.java:100:41:100:61 | getInputStream(...) : InputStream | DocumentBuilderTests.java:101:16:101:52 | sourceToInputSource(...) |
|
||||
| DocumentBuilderTests.java:100:41:100:61 | getInputStream(...) : InputStream | DocumentBuilderTests.java:102:16:102:38 | getInputStream(...) |
|
||||
| SAXSourceTests.java:17:62:17:82 | getInputStream(...) : InputStream | SAXSourceTests.java:20:18:20:23 | source |
|
||||
| SchemaTests.java:12:56:12:76 | getInputStream(...) : InputStream | SchemaTests.java:12:39:12:77 | new StreamSource(...) |
|
||||
| SchemaTests.java:25:56:25:76 | getInputStream(...) : InputStream | SchemaTests.java:25:39:25:77 | new StreamSource(...) |
|
||||
| SchemaTests.java:31:56:31:76 | getInputStream(...) : InputStream | SchemaTests.java:31:39:31:77 | new StreamSource(...) |
|
||||
@@ -78,6 +79,8 @@ nodes
|
||||
| SAXReaderTests.java:45:17:45:37 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||
| SAXReaderTests.java:53:17:53:37 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||
| SAXReaderTests.java:61:17:61:37 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||
| SAXSourceTests.java:17:62:17:82 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| SAXSourceTests.java:20:18:20:23 | source | semmle.label | source |
|
||||
| SchemaTests.java:12:39:12:77 | new StreamSource(...) | semmle.label | new StreamSource(...) |
|
||||
| SchemaTests.java:12:56:12:76 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| SchemaTests.java:25:39:25:77 | new StreamSource(...) | semmle.label | new StreamSource(...) |
|
||||
@@ -163,6 +166,7 @@ nodes
|
||||
| TransformerTests.java:136:38:136:58 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| TransformerTests.java:141:18:141:70 | new SAXSource(...) | semmle.label | new SAXSource(...) |
|
||||
| TransformerTests.java:141:48:141:68 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| UnmarshallerTests.java:28:18:28:38 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||
| XMLReaderTests.java:16:18:16:55 | new InputSource(...) | semmle.label | new InputSource(...) |
|
||||
| XMLReaderTests.java:16:34:16:54 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XMLReaderTests.java:56:18:56:55 | new InputSource(...) | semmle.label | new InputSource(...) |
|
||||
@@ -220,6 +224,7 @@ nodes
|
||||
| SAXReaderTests.java:45:17:45:37 | getInputStream(...) | SAXReaderTests.java:45:17:45:37 | getInputStream(...) | SAXReaderTests.java:45:17:45:37 | getInputStream(...) | Unsafe parsing of XML file from $@. | SAXReaderTests.java:45:17:45:37 | getInputStream(...) | user input |
|
||||
| SAXReaderTests.java:53:17:53:37 | getInputStream(...) | SAXReaderTests.java:53:17:53:37 | getInputStream(...) | SAXReaderTests.java:53:17:53:37 | getInputStream(...) | Unsafe parsing of XML file from $@. | SAXReaderTests.java:53:17:53:37 | getInputStream(...) | user input |
|
||||
| SAXReaderTests.java:61:17:61:37 | getInputStream(...) | SAXReaderTests.java:61:17:61:37 | getInputStream(...) | SAXReaderTests.java:61:17:61:37 | getInputStream(...) | Unsafe parsing of XML file from $@. | SAXReaderTests.java:61:17:61:37 | getInputStream(...) | user input |
|
||||
| SAXSourceTests.java:20:18:20:23 | source | SAXSourceTests.java:17:62:17:82 | getInputStream(...) : InputStream | SAXSourceTests.java:20:18:20:23 | source | Unsafe parsing of XML file from $@. | SAXSourceTests.java:17:62:17:82 | getInputStream(...) | user input |
|
||||
| SchemaTests.java:12:39:12:77 | new StreamSource(...) | SchemaTests.java:12:56:12:76 | getInputStream(...) : InputStream | SchemaTests.java:12:39:12:77 | new StreamSource(...) | Unsafe parsing of XML file from $@. | SchemaTests.java:12:56:12:76 | getInputStream(...) | user input |
|
||||
| SchemaTests.java:25:39:25:77 | new StreamSource(...) | SchemaTests.java:25:56:25:76 | getInputStream(...) : InputStream | SchemaTests.java:25:39:25:77 | new StreamSource(...) | Unsafe parsing of XML file from $@. | SchemaTests.java:25:56:25:76 | getInputStream(...) | user input |
|
||||
| SchemaTests.java:31:39:31:77 | new StreamSource(...) | SchemaTests.java:31:56:31:76 | getInputStream(...) : InputStream | SchemaTests.java:31:39:31:77 | new StreamSource(...) | Unsafe parsing of XML file from $@. | SchemaTests.java:31:56:31:76 | getInputStream(...) | user input |
|
||||
@@ -267,6 +272,7 @@ nodes
|
||||
| TransformerTests.java:129:21:129:59 | new StreamSource(...) | TransformerTests.java:129:38:129:58 | getInputStream(...) : InputStream | TransformerTests.java:129:21:129:59 | new StreamSource(...) | Unsafe parsing of XML file from $@. | TransformerTests.java:129:38:129:58 | getInputStream(...) | user input |
|
||||
| TransformerTests.java:136:21:136:59 | new StreamSource(...) | TransformerTests.java:136:38:136:58 | getInputStream(...) : InputStream | TransformerTests.java:136:21:136:59 | new StreamSource(...) | Unsafe parsing of XML file from $@. | TransformerTests.java:136:38:136:58 | getInputStream(...) | user input |
|
||||
| TransformerTests.java:141:18:141:70 | new SAXSource(...) | TransformerTests.java:141:48:141:68 | getInputStream(...) : InputStream | TransformerTests.java:141:18:141:70 | new SAXSource(...) | Unsafe parsing of XML file from $@. | TransformerTests.java:141:48:141:68 | getInputStream(...) | user input |
|
||||
| UnmarshallerTests.java:28:18:28:38 | getInputStream(...) | UnmarshallerTests.java:28:18:28:38 | getInputStream(...) | UnmarshallerTests.java:28:18:28:38 | getInputStream(...) | Unsafe parsing of XML file from $@. | UnmarshallerTests.java:28:18:28:38 | getInputStream(...) | user input |
|
||||
| XMLReaderTests.java:16:18:16:55 | new InputSource(...) | XMLReaderTests.java:16:34:16:54 | getInputStream(...) : InputStream | XMLReaderTests.java:16:18:16:55 | new InputSource(...) | Unsafe parsing of XML file from $@. | XMLReaderTests.java:16:34:16:54 | getInputStream(...) | user input |
|
||||
| XMLReaderTests.java:56:18:56:55 | new InputSource(...) | XMLReaderTests.java:56:34:56:54 | getInputStream(...) : InputStream | XMLReaderTests.java:56:18:56:55 | new InputSource(...) | Unsafe parsing of XML file from $@. | XMLReaderTests.java:56:34:56:54 | getInputStream(...) | user input |
|
||||
| XMLReaderTests.java:63:18:63:55 | new InputSource(...) | XMLReaderTests.java:63:34:63:54 | getInputStream(...) : InputStream | XMLReaderTests.java:63:18:63:55 | new InputSource(...) | Unsafe parsing of XML file from $@. | XMLReaderTests.java:63:34:63:54 | getInputStream(...) | user input |
|
||||
|
||||
@@ -1 +1 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/jdom-1.1.3:${testdir}/../../../stubs/dom4j-2.1.1:${testdir}/../../../stubs/simple-xml-2.7.1
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/jdom-1.1.3:${testdir}/../../../stubs/dom4j-2.1.1:${testdir}/../../../stubs/simple-xml-2.7.1:${testdir}/../../../stubs/jaxb-api-2.3.1
|
||||
|
||||
@@ -26,6 +26,10 @@ import com.alibaba.fastjson.parser.*;
|
||||
import com.alibaba.fastjson.parser.deserializer.ParseProcess;
|
||||
|
||||
public abstract class JSON {
|
||||
public static String toJSONString(Object object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object parse(String text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
7
java/ql/test/stubs/gson-2.8.6/com/google/gson/Gson.java
Normal file
7
java/ql/test/stubs/gson-2.8.6/com/google/gson/Gson.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package com.google.gson;
|
||||
|
||||
public final class Gson {
|
||||
public String toJson(Object src) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
3
java/ql/test/stubs/java-ee-el/javax/el/ELContext.java
Normal file
3
java/ql/test/stubs/java-ee-el/javax/el/ELContext.java
Normal file
@@ -0,0 +1,3 @@
|
||||
package javax.el;
|
||||
|
||||
public class ELContext {}
|
||||
5
java/ql/test/stubs/java-ee-el/javax/el/ELManager.java
Normal file
5
java/ql/test/stubs/java-ee-el/javax/el/ELManager.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package javax.el;
|
||||
|
||||
public class ELManager {
|
||||
public static ExpressionFactory getExpressionFactory() { return null; }
|
||||
}
|
||||
8
java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
Normal file
8
java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package javax.el;
|
||||
|
||||
public class ELProcessor {
|
||||
public Object eval(String expression) { return null; }
|
||||
public Object getValue(String expression, Class<?> expectedType) { return null; }
|
||||
public void setValue(String expression, Object value) {}
|
||||
public void setVariable(String var, String expression) {}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package javax.el;
|
||||
|
||||
public class ExpressionFactory {
|
||||
public MethodExpression createMethodExpression(ELContext context, String expression, Class<?> expectedReturnType,
|
||||
Class<?>[] expectedParamTypes) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ValueExpression createValueExpression(ELContext context, String expression, Class<?> expectedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ValueExpression createValueExpression(Object instance, Class<?> expectedType) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user