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

This commit is contained in:
haby0
2021-04-19 20:05:49 +08:00
378 changed files with 10416 additions and 3945 deletions

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -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. */

View File

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

View File

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

View File

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

View File

@@ -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. */

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
| pom.xml:29:9:32:22 | dependency | Insecure configuration of Spring Boot Actuator exposes sensitive endpoints. |

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-352/JsonpInjection.ql

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/servlet-api-2.4

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
package com.google.gson;
public final class Gson {
public String toJson(Object src) {
return null;
}
}

View File

@@ -0,0 +1,3 @@
package javax.el;
public class ELContext {}

View File

@@ -0,0 +1,5 @@
package javax.el;
public class ELManager {
public static ExpressionFactory getExpressionFactory() { return null; }
}

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

View File

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