Merge branch 'master' into feat/JLL/jOOQ_SQL_injection

* master: (485 commits)
  C++: Remove @stmt_while from the TConditionalStmt union type.
  C++: Remove abstract classes from Stmt.qll
  Drop Map.merge as taint step
  Add the printAst.ql contextual query for C++
  Fix modelling of Stack.push
  C#: Sync identical files
  C++: Replace getResultType() with getResultIRType() in IR dataflow
  C++: Replace getResultType() with getResultIRType() in IR range analysis
  C++: Introduce isSigned() and isUnsigned() predicates on IRIntegerType to mirror IntegralType
  Add missing java import
  Add missing java import
  Mark ServletUrlRedirectSink private
  Java: model Object.clone
  Add file-level qldoc
  Optimize imports
  Join ServletUrlRedirectSink with UrlRedirectSink
  Extend UrlRedirectSink from DataFlow::Node
  Remove superfluous imports
  Java: ContainerFlow add comments
  Generalize QueryInjectionSink
  ...
This commit is contained in:
Jonathan Leitschuh
2020-07-10 14:36:56 -04:00
1225 changed files with 67179 additions and 35681 deletions

View File

@@ -1,53 +1,8 @@
/** Definitions used by the queries for database query injection. */
import semmle.code.java.Expr
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.android.SQLite
import semmle.code.java.frameworks.javaee.Persistence
import semmle.code.java.frameworks.SpringJdbc
import semmle.code.java.frameworks.MyBatis
import semmle.code.java.frameworks.Hibernate
import semmle.code.java.frameworks.jOOQ
/** A sink for database query language injection vulnerabilities. */
abstract class QueryInjectionSink extends DataFlow::ExprNode { }
/** A sink for SQL injection vulnerabilities. */
class SqlInjectionSink extends QueryInjectionSink {
SqlInjectionSink() {
this.getExpr() instanceof SqlExpr
or
exists(MethodAccess ma, Method m, int index |
ma.getMethod() = m and
ma.getArgument(index) = this.getExpr()
|
index = m.(SQLiteRunner).sqlIndex()
or
m instanceof BatchUpdateVarargsMethod
or
index = 0 and jdbcSqlMethod(m)
or
index = 0 and mybatisSqlMethod(m)
or
index = 0 and hibernateSqlMethod(m)
or
index = 0 and jOOQSqlMethod(m)
)
}
}
/** A sink for Java Persistence Query Language injection vulnerabilities. */
class PersistenceQueryInjectionSink extends QueryInjectionSink {
PersistenceQueryInjectionSink() {
// the query (first) argument to a `createQuery` or `createNativeQuery` method on `EntityManager`
exists(MethodAccess call, TypeEntityManager em | call.getArgument(0) = this.getExpr() |
call.getMethod() = em.getACreateQueryMethod() or
call.getMethod() = em.getACreateNativeQueryMethod()
// note: `createNamedQuery` is safe, as it takes only the query name,
// and named queries can only be constructed using constants as the query text
)
}
}
import semmle.code.java.security.QueryInjection
private class QueryInjectionFlowConfig extends TaintTracking::Configuration {
QueryInjectionFlowConfig() { this = "SqlInjectionLib::QueryInjectionFlowConfig" }

View File

@@ -40,7 +40,7 @@ class UncontrolledStringBuilderSourceFlowConfig extends TaintTracking::Configura
from QueryInjectionSink query, Expr uncontrolled
where
(
builtFromUncontrolledConcat(query.getExpr(), uncontrolled)
builtFromUncontrolledConcat(query.asExpr(), uncontrolled)
or
exists(StringBuilderVar sbv, UncontrolledStringBuilderSourceFlowConfig conf |
uncontrolledStringBuilderQuery(sbv, uncontrolled) and

View File

@@ -21,7 +21,7 @@ private class ShortStringLiteral extends StringLiteral {
class BrokenAlgoLiteral extends ShortStringLiteral {
BrokenAlgoLiteral() {
getValue().regexpMatch(algorithmBlacklistRegex()) and
getValue().regexpMatch(getInsecureAlgorithmRegex()) and
// Exclude German and French sentences.
not getValue().regexpMatch(".*\\p{IsLowercase} des \\p{IsLetter}.*")
}

View File

@@ -25,9 +25,9 @@ class InsecureAlgoLiteral extends ShortStringLiteral {
// Algorithm identifiers should be at least two characters.
getValue().length() > 1 and
exists(string s | s = getLiteral() |
not s.regexpMatch(algorithmWhitelistRegex()) and
not s.regexpMatch(getSecureAlgorithmRegex()) and
// Exclude results covered by another query.
not s.regexpMatch(algorithmBlacklistRegex())
not s.regexpMatch(getInsecureAlgorithmRegex())
)
}
}

View File

@@ -12,7 +12,7 @@
import java
import semmle.code.java.dataflow.FlowSources
import UrlRedirect
import semmle.code.java.security.UrlRedirect
import DataFlow::PathGraph
class UrlRedirectConfig extends TaintTracking::Configuration {

View File

@@ -12,7 +12,7 @@
import java
import semmle.code.java.dataflow.FlowSources
import UrlRedirect
import semmle.code.java.security.UrlRedirect
import DataFlow::PathGraph
class UrlRedirectLocalConfig extends TaintTracking::Configuration {

View File

@@ -0,0 +1,101 @@
public static void main(String[] args) {
{
HostnameVerifier verifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
try { //GOOD: verify the certificate
Certificate[] certs = session.getPeerCertificates();
X509Certificate x509 = (X509Certificate) certs[0];
check(new String[]{host}, x509);
return true;
} catch (SSLException e) {
return false;
}
}
};
HttpsURLConnection.setDefaultHostnameVerifier(verifier);
}
{
HostnameVerifier verifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true; // BAD: accept even if the hostname doesn't match
}
};
HttpsURLConnection.setDefaultHostnameVerifier(verifier);
}
{
X509TrustManager trustAllCertManager = new X509TrustManager() {
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType)
throws CertificateException {
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType)
throws CertificateException {
// BAD: trust any server cert
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null; //BAD: doesn't check cert issuer
}
};
}
{
X509TrustManager trustCertManager = new X509TrustManager() {
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType)
throws CertificateException {
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType)
throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType); //GOOD: validate the server cert
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0]; //GOOD: Validate the cert issuer
}
};
}
{
SSLContext sslContext = SSLContext.getInstance("TLS");
SSLEngine sslEngine = sslContext.createSSLEngine();
SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); //GOOD: Set a valid endpointIdentificationAlgorithm for SSL engine to trigger hostname verification
sslEngine.setSSLParameters(sslParameters);
}
{
SSLContext sslContext = SSLContext.getInstance("TLS");
SSLEngine sslEngine = sslContext.createSSLEngine(); //BAD: No endpointIdentificationAlgorithm set
}
{
SSLContext sslContext = SSLContext.getInstance("TLS");
final SSLSocketFactory socketFactory = sslContext.getSocketFactory();
SSLSocket socket = (SSLSocket) socketFactory.createSocket("www.example.com", 443);
SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); //GOOD: Set a valid endpointIdentificationAlgorithm for SSL socket to trigger hostname verification
socket.setSSLParameters(sslParameters);
}
{
com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
connectionFactory.useSslProtocol();
connectionFactory.enableHostnameVerification(); //GOOD: Enable hostname verification for rabbitmq ConnectionFactory
}
{
com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
connectionFactory.useSslProtocol(); //BAD: Hostname verification for rabbitmq ConnectionFactory is not enabled
}
}

View File

@@ -0,0 +1,46 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Java offers two mechanisms for SSL authentication - trust manager and hostname verifier. Trust manager validates the peer's certificate chain while hostname verification establishes that the hostname in the URL matches the hostname in the server's identification.</p>
<p>And when SSLSocket or SSLEngine is created without a valid parameter of setEndpointIdentificationAlgorithm, hostname verification is disabled by default.</p>
<p>Unsafe implementation of the interface X509TrustManager, HostnameVerifier, and SSLSocket/SSLEngine ignores all SSL certificate validation errors when establishing an HTTPS connection, thereby making the app vulnerable to man-in-the-middle attacks.</p>
<p>This query checks whether trust manager is set to trust all certificates, the hostname verifier is turned off, or setEndpointIdentificationAlgorithm is missing. The query also covers a special implementation com.rabbitmq.client.ConnectionFactory.</p>
</overview>
<recommendation>
<p>Validate SSL certificate in SSL authentication.</p>
</recommendation>
<example>
<p>The following two examples show two ways of configuring X509 trust cert manager and hostname verifier. In the 'BAD' case,
no validation is performed thus any certificate is trusted. In the 'GOOD' case, the proper validation is performed.</p>
<sample src="UnsafeCertTrust.java" />
</example>
<references>
<li>
<a href="https://cwe.mitre.org/data/definitions/273.html">CWE-273</a>
</li>
<li>
<a href="https://support.google.com/faqs/answer/6346016?hl=en">How to fix apps containing an unsafe implementation of TrustManager</a>
</li>
<li>
<a href="https://github.com/OWASP/owasp-mstg/blob/master/Document/0x05g-Testing-Network-Communication.md">Testing Endpoint Identify Verification (MSTG-NETWORK-3)</a>
</li>
<li>
<a href="https://github.com/advisories/GHSA-xvch-r4wf-h8w9">CVE-2018-17187: Apache Qpid Proton-J transport issue with hostname verification</a>
</li>
<li>
<a href="https://github.com/advisories/GHSA-46j3-r4pj-4835">CVE-2018-8034: Apache Tomcat - host name verification when using TLS with the WebSocket client</a>
</li>
<li>
<a href="https://github.com/advisories/GHSA-w4g2-9hj6-5472">CVE-2018-11087: Pivotal Spring AMQP vulnerability due to lack of hostname validation</a>
</li>
<li>
<a href="https://github.com/advisories/GHSA-m9w8-v359-9ffr">CVE-2018-11775: TLS hostname verification issue when using the Apache ActiveMQ Client</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,246 @@
/**
* @name Unsafe certificate trust and improper hostname verification
* @description Unsafe implementation of the interface X509TrustManager, HostnameVerifier, and SSLSocket/SSLEngine ignores all SSL certificate validation errors when establishing an HTTPS connection, thereby making the app vulnerable to man-in-the-middle attacks.
* @kind problem
* @id java/unsafe-cert-trust
* @tags security
* external/cwe-273
*/
import java
import semmle.code.java.security.Encryption
/**
* X509TrustManager class that blindly trusts all certificates in server SSL authentication
*/
class X509TrustAllManager extends RefType {
X509TrustAllManager() {
this.getASupertype*() instanceof X509TrustManager and
exists(Method m1 |
m1.getDeclaringType() = this and
m1.hasName("checkServerTrusted") and
m1.getBody().getNumStmt() = 0
) and
exists(Method m2, ReturnStmt rt2 |
m2.getDeclaringType() = this and
m2.hasName("getAcceptedIssuers") and
rt2.getEnclosingCallable() = m2 and
rt2.getResult() instanceof NullLiteral
)
}
}
/**
* The init method of SSLContext with the trust all manager, which is sslContext.init(..., serverTMs, ...)
*/
class X509TrustAllManagerInit extends MethodAccess {
X509TrustAllManagerInit() {
this.getMethod().hasName("init") and
this.getMethod().getDeclaringType() instanceof SSLContext and //init method of SSLContext
(
exists(ArrayInit ai |
this.getArgument(1).(ArrayCreationExpr).getInit() = ai and
ai.getInit(0).(VarAccess).getVariable().getInitializer().getType().(Class).getASupertype*()
instanceof X509TrustAllManager //Scenario of context.init(null, new TrustManager[] { TRUST_ALL_CERTIFICATES }, null);
)
or
exists(Variable v, ArrayInit ai |
this.getArgument(1).(VarAccess).getVariable() = v and
ai.getParent() = v.getAnAssignedValue() and
ai.getInit(0).getType().(Class).getASupertype*() instanceof X509TrustAllManager //Scenario of context.init(null, serverTMs, null);
)
)
}
}
/**
* HostnameVerifier class that allows a certificate whose CN (Common Name) does not match the host name in the URL
*/
class TrustAllHostnameVerifier extends RefType {
TrustAllHostnameVerifier() {
this.getASupertype*() instanceof HostnameVerifier and
exists(Method m, ReturnStmt rt |
m.getDeclaringType() = this and
m.hasName("verify") and
rt.getEnclosingCallable() = m and
rt.getResult().(BooleanLiteral).getBooleanValue() = true
)
}
}
/**
* The setDefaultHostnameVerifier method of HttpsURLConnection with the trust all configuration
*/
class TrustAllHostnameVerify extends MethodAccess {
TrustAllHostnameVerify() {
this.getMethod().hasName("setDefaultHostnameVerifier") and
this.getMethod().getDeclaringType() instanceof HttpsURLConnection and //httpsURLConnection.setDefaultHostnameVerifier method
(
exists(NestedClass nc |
nc.getASupertype*() instanceof TrustAllHostnameVerifier and
this.getArgument(0).getType() = nc //Scenario of HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {...});
)
or
exists(Variable v |
this.getArgument(0).(VarAccess).getVariable() = v and
v.getInitializer().getType() instanceof TrustAllHostnameVerifier //Scenario of HttpsURLConnection.setDefaultHostnameVerifier(verifier);
)
)
}
}
class SSLEngine extends RefType {
SSLEngine() { this.hasQualifiedName("javax.net.ssl", "SSLEngine") }
}
class Socket extends RefType {
Socket() { this.hasQualifiedName("java.net", "Socket") }
}
class SocketFactory extends RefType {
SocketFactory() { this.hasQualifiedName("javax.net", "SocketFactory") }
}
class SSLSocket extends RefType {
SSLSocket() { this.hasQualifiedName("javax.net.ssl", "SSLSocket") }
}
/**
* has setEndpointIdentificationAlgorithm set correctly
*/
predicate setEndpointIdentificationAlgorithm(MethodAccess createSSL) {
exists(
Variable sslo, MethodAccess ma, Variable sslparams //setSSLParameters with valid setEndpointIdentificationAlgorithm set
|
createSSL = sslo.getAnAssignedValue() and
ma.getQualifier() = sslo.getAnAccess() and
ma.getMethod().hasName("setSSLParameters") and
ma.getArgument(0) = sslparams.getAnAccess() and
exists(MethodAccess setepa |
setepa.getQualifier() = sslparams.getAnAccess() and
setepa.getMethod().hasName("setEndpointIdentificationAlgorithm") and
not setepa.getArgument(0) instanceof NullLiteral
)
)
}
/**
* has setEndpointIdentificationAlgorithm set correctly
*/
predicate hasEndpointIdentificationAlgorithm(Variable ssl) {
exists(
MethodAccess ma, Variable sslparams //setSSLParameters with valid setEndpointIdentificationAlgorithm set
|
ma.getQualifier() = ssl.getAnAccess() and
ma.getMethod().hasName("setSSLParameters") and
ma.getArgument(0) = sslparams.getAnAccess() and
exists(MethodAccess setepa |
setepa.getQualifier() = sslparams.getAnAccess() and
setepa.getMethod().hasName("setEndpointIdentificationAlgorithm") and
not setepa.getArgument(0) instanceof NullLiteral
)
)
}
/**
* Cast of Socket to SSLSocket
*/
predicate sslCast(MethodAccess createSSL) {
exists(Variable ssl, CastExpr ce |
ce.getExpr() = createSSL and
ce.getControlFlowNode().getASuccessor().(VariableAssign).getDestVar() = ssl and
ssl.getType() instanceof SSLSocket //With a type cast `SSLSocket socket = (SSLSocket) socketFactory.createSocket("www.example.com", 443)`
)
}
/**
* SSL object is created in a separate method call or in the same method
*/
predicate hasFlowPath(MethodAccess createSSL, Variable ssl) {
(
createSSL = ssl.getAnAssignedValue()
or
exists(CastExpr ce |
ce.getExpr() = createSSL and
ce.getControlFlowNode().getASuccessor().(VariableAssign).getDestVar() = ssl //With a type cast like SSLSocket socket = (SSLSocket) socketFactory.createSocket("www.example.com", 443);
)
)
or
exists(MethodAccess tranm |
createSSL.getEnclosingCallable() = tranm.getMethod() and
tranm.getControlFlowNode().getASuccessor().(VariableAssign).getDestVar() = ssl and
not setEndpointIdentificationAlgorithm(createSSL) //Check the scenario of invocation before used in the current method
)
}
/**
* Not have the SSLParameter set
*/
predicate hasNoEndpointIdentificationSet(MethodAccess createSSL, Variable ssl) {
//No setSSLParameters set
hasFlowPath(createSSL, ssl) and
not exists(MethodAccess ma |
ma.getQualifier() = ssl.getAnAccess() and
ma.getMethod().hasName("setSSLParameters")
)
or
//No endpointIdentificationAlgorithm set with setSSLParameters
hasFlowPath(createSSL, ssl) and
not setEndpointIdentificationAlgorithm(createSSL)
}
/**
* The setEndpointIdentificationAlgorithm method of SSLParameters with the ssl engine or socket
*/
class SSLEndpointIdentificationNotSet extends MethodAccess {
SSLEndpointIdentificationNotSet() {
(
this.getMethod().hasName("createSSLEngine") and
this.getMethod().getDeclaringType() instanceof SSLContext //createEngine method of SSLContext
or
this.getMethod().hasName("createSocket") and
this.getMethod().getDeclaringType() instanceof SocketFactory and
this.getMethod().getReturnType() instanceof Socket and
sslCast(this) //createSocket method of SocketFactory
) and
exists(Variable ssl |
hasNoEndpointIdentificationSet(this, ssl) and //Not set in itself
not exists(VariableAssign ar, Variable newSsl |
ar.getSource() = this.getCaller().getAReference() and
ar.getDestVar() = newSsl and
hasEndpointIdentificationAlgorithm(newSsl) //Not set in its caller either
)
) and
not exists(MethodAccess ma | ma.getMethod() instanceof HostnameVerifierVerify) //Reduce false positives since this method access set default hostname verifier
}
}
class RabbitMQConnectionFactory extends RefType {
RabbitMQConnectionFactory() { this.hasQualifiedName("com.rabbitmq.client", "ConnectionFactory") }
}
/**
* The com.rabbitmq.client.ConnectionFactory useSslProtocol method access without enableHostnameVerification
*/
class RabbitMQEnableHostnameVerificationNotSet extends MethodAccess {
RabbitMQEnableHostnameVerificationNotSet() {
this.getMethod().hasName("useSslProtocol") and
this.getMethod().getDeclaringType() instanceof RabbitMQConnectionFactory and
exists(Variable v |
v.getType() instanceof RabbitMQConnectionFactory and
this.getQualifier() = v.getAnAccess() and
not exists(MethodAccess ma |
ma.getMethod().hasName("enableHostnameVerification") and
ma.getQualifier() = v.getAnAccess()
)
)
}
}
from MethodAccess aa
where
aa instanceof TrustAllHostnameVerify or
aa instanceof X509TrustAllManagerInit or
aa instanceof SSLEndpointIdentificationNotSet or
aa instanceof RabbitMQEnableHostnameVerificationNotSet
select aa, "Unsafe configuration of trusted certificates"

View File

@@ -0,0 +1,10 @@
public void validate(KeyStore cacerts, CertPath certPath) throws Exception {
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
PKIXParameters params = new PKIXParameters(cacerts);
params.setRevocationEnabled(false);
PKIXRevocationChecker checker = (PKIXRevocationChecker) validator.getRevocationChecker();
checker.setOcspResponder(OCSP_RESPONDER_URL);
checker.setOcspResponderCert(OCSP_RESPONDER_CERT);
params.addCertPathChecker(checker);
validator.validate(certPath, params);
}

View File

@@ -0,0 +1,5 @@
public void validate(KeyStore cacerts, CertPath chain) throws Exception {
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
PKIXParameters params = new PKIXParameters(cacerts);
validator.validate(chain, params);
}

View File

@@ -0,0 +1,63 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>Validating a certificate chain includes multiple steps. One of them is checking whether or not
certificates in the chain have been revoked. A certificate may be revoked due to multiple reasons.
One of the reasons why the certificate authority (CA) may revoke a certificate is that its private key
has been compromised. For example, the private key might have been stolen by an adversary.
In this case, the adversary may be able to impersonate the owner of the private key.
Therefore, trusting a revoked certificate may be dangerous.</p>
<p>The Java Certification Path API provides a revocation checking mechanism
that supports both CRL and OCSP.
Revocation checking happens while building and validating certificate chains.
If at least one of the certificates is revoked, then an exception is thrown.
This mechanism is enabled by default. However, it may be disabled
by passing <code>false</code> to the <code>PKIXParameters.setRevocationEnabled()</code> method.
If an application doesn't set a custom <code>PKIXRevocationChecker</code>
via <code>PKIXParameters.addCertPathChecker()</code>
or <code>PKIXParameters.setCertPathCheckers()</code> methods,
then revocation checking is not going to happen.</p>
</overview>
<recommendation>
<p>An application should not disable the default revocationg checking mechanism
unless it provides a custom revocation checker.</p>
</recommendation>
<example>
<p>The following example turns off revocation checking for validating a certificate chain.
That should be avoided.</p>
<sample src="NoRevocationChecking.java" />
<p>The next example uses the default revocation checking mechanism.</p>
<sample src="DefaultRevocationChecking.java" />
<p>The third example turns off the default revocation mechanism. However, it registers another
revocation checker that uses OCSP to obtain revocation status of certificates.</p>
<sample src="CustomRevocationChecking.java" />
</example>
<references>
<li>
Wikipedia:
<a href="https://en.wikipedia.org/wiki/Public_key_certificate">Public key certificate</a>
</li>
<li>
Java SE Documentation:
<a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html">Java PKI Programmer's Guide</a>
</li>
<li>
Java SE API Specification:
<a href="https://docs.oracle.com/javase/8/docs/api/java/security/cert/CertPathValidator.html">CertPathValidator</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Disabled ceritificate revocation checking
* @description Using revoked certificates is dangerous.
* Therefore, revocation status of certificates in a chain should be checked.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/disabled-certificate-revocation-checking
* @tags security
* external/cwe/cwe-299
*/
import java
import RevocationCheckingLib
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink, DisabledRevocationCheckingConfig config
where config.hasFlowPath(source, sink)
select source.getNode(), source, sink, "Revocation checking is disabled $@.", source.getNode(),
"here"

View File

@@ -0,0 +1,6 @@
public void validateUnsafe(KeyStore cacerts, CertPath chain) throws Exception {
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
PKIXParameters params = new PKIXParameters(cacerts);
params.setRevocationEnabled(false);
validator.validate(chain, params);
}

View File

@@ -0,0 +1,60 @@
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow
/**
* A taint-tracking configuration for disabling revocation checking.
*/
class DisabledRevocationCheckingConfig extends TaintTracking::Configuration {
DisabledRevocationCheckingConfig() { this = "DisabledRevocationCheckingConfig" }
override predicate isSource(DataFlow::Node source) {
exists(BooleanLiteral b | b.getBooleanValue() = false | source.asExpr() = b)
}
override predicate isSink(DataFlow::Node sink) { sink instanceof SetRevocationEnabledSink }
}
/**
* A sink that disables revocation checking,
* i.e. calling `PKIXParameters.setRevocationEnabled(false)`
* without setting a custom revocation checker in `PKIXParameters`.
*/
class SetRevocationEnabledSink extends DataFlow::ExprNode {
SetRevocationEnabledSink() {
exists(MethodAccess setRevocationEnabledCall |
setRevocationEnabledCall.getMethod() instanceof SetRevocationEnabledMethod and
setRevocationEnabledCall.getArgument(0) = getExpr() and
not exists(MethodAccess ma, Method m | m = ma.getMethod() |
(m instanceof AddCertPathCheckerMethod or m instanceof SetCertPathCheckersMethod) and
ma.getQualifier().(VarAccess).getVariable() =
setRevocationEnabledCall.getQualifier().(VarAccess).getVariable()
)
)
}
}
class SetRevocationEnabledMethod extends Method {
SetRevocationEnabledMethod() {
getDeclaringType() instanceof PKIXParameters and
hasName("setRevocationEnabled")
}
}
class AddCertPathCheckerMethod extends Method {
AddCertPathCheckerMethod() {
getDeclaringType() instanceof PKIXParameters and
hasName("addCertPathChecker")
}
}
class SetCertPathCheckersMethod extends Method {
SetCertPathCheckersMethod() {
getDeclaringType() instanceof PKIXParameters and
hasName("setCertPathCheckers")
}
}
class PKIXParameters extends RefType {
PKIXParameters() { hasQualifiedName("java.security.cert", "PKIXParameters") }
}

View File

@@ -61,7 +61,7 @@ class SslParametersSetProtocolsSink extends DataFlow::ExprNode {
}
/**
* A sink that sets protocol versions fro `SSLSocket`, `SSLServerSocket` and `SSLEngine`,
* A sink that sets protocol versions for `SSLSocket`, `SSLServerSocket`, and `SSLEngine`,
* i.e. `socket.setEnabledProtocols(versions)` or `engine.setEnabledProtocols(versions)`.
*/
class SetEnabledProtocolsSink extends DataFlow::ExprNode {

View File

@@ -0,0 +1,17 @@
import ognl.Ognl;
import ognl.OgnlException;
public void evaluate(HttpServletRequest request, Object root) throws OgnlException {
String expression = request.getParameter("expression");
// BAD: User provided expression is evaluated
Ognl.getValue(expression, root);
// GOOD: The name is validated and expression is evaluated in sandbox
System.setProperty("ognl.security.manager", ""); // Or add -Dognl.security.manager to JVM args
if (isValid(expression)) {
Ognl.getValue(expression, root);
} else {
// Reject the request
}
}

View File

@@ -0,0 +1,33 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Object-Graph Navigation Language (OGNL) is an open-source Expression Language (EL) for Java. Due
to its ability to create or change executable code, OGNL is capable of introducing critical
security flaws to any application that uses it. Evaluation of unvalidated expressions can let
attacker to modify Java objects' properties or execute arbitrary code.</p>
</overview>
<recommendation>
<p>The general recommendation is to not evaluate untrusted ONGL expressions. If user provided OGNL
expressions must be evaluated, do this in sandbox (add `-Dognl.security.manager` to JVM arguments)
and validate the expressions before evaluation.</p>
</recommendation>
<example>
<p>In the following examples, the code accepts an OGNL expression from the user and evaluates it.
</p>
<p>In the first example, the user provided OGNL expression is parsed and evaluated.</p>
<p>The second example validates the expression and evaluates it inside the sandbox.</p>
<sample src="OgnlInjection.java" />
</example>
<references>
<li><a href="https://github.com/jkuhnert/ognl/">OGNL library</a>.</li>
<li>Struts security: <a href="https://struts.apache.org/security/#proactively-protect-from-ognl-expression-injections-attacks-if-easily-applicable">Proactively protect from OGNL Expression Injections attacks</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,22 @@
/**
* @name OGNL Expression Language statement with user-controlled input
* @description Evaluation of OGNL Expression Language statement with user-controlled input can
* lead to execution of arbitrary code.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/ognl-injection
* @tags security
* external/cwe/cwe-917
*/
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow
import DataFlow::PathGraph
import OgnlInjectionLib
from DataFlow::PathNode source, DataFlow::PathNode sink, OgnlInjectionFlowConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "OGNL expression might include input from $@.",
source.getNode(), "this user input"

View File

@@ -0,0 +1,109 @@
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow
import DataFlow::PathGraph
/**
* A taint-tracking configuration for unvalidated user input that is used in OGNL EL evaluation.
*/
class OgnlInjectionFlowConfig extends TaintTracking::Configuration {
OgnlInjectionFlowConfig() { this = "OgnlInjectionFlowConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof OgnlInjectionSink }
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
parseCompileExpressionStep(node1, node2)
}
}
/** The class `org.apache.commons.ognl.Ognl` or `ognl.Ognl`. */
class TypeOgnl extends Class {
TypeOgnl() {
this.hasQualifiedName("org.apache.commons.ognl", "Ognl") or
this.hasQualifiedName("ognl", "Ognl")
}
}
/** The interface `org.apache.commons.ognl.Node` or `ognl.Node`. */
class TypeNode extends Interface {
TypeNode() {
this.hasQualifiedName("org.apache.commons.ognl", "Node") or
this.hasQualifiedName("ognl", "Node")
}
}
/** The class `com.opensymphony.xwork2.ognl.OgnlUtil`. */
class TypeOgnlUtil extends Class {
TypeOgnlUtil() { this.hasQualifiedName("com.opensymphony.xwork2.ognl", "OgnlUtil") }
}
/**
* OGNL sink for OGNL injection vulnerabilities, i.e. 1st argument to `getValue` or `setValue`
* method from `Ognl` or `getValue` or `setValue` method from `Node`.
*/
predicate ognlSinkMethod(Method m, int index) {
(
m.getDeclaringType() instanceof TypeOgnl
or
m.getDeclaringType().getAnAncestor*() instanceof TypeNode
) and
(
m.hasName("getValue") or
m.hasName("setValue")
) and
index = 0
}
/**
* Struts sink for OGNL injection vulnerabilities, i.e. 1st argument to `getValue`, `setValue` or
* `callMethod` method from `OgnlUtil`.
*/
predicate strutsSinkMethod(Method m, int index) {
m.getDeclaringType() instanceof TypeOgnlUtil and
(
m.hasName("getValue") or
m.hasName("setValue") or
m.hasName("callMethod")
) and
index = 0
}
/** Holds if parameter at index `index` in method `m` is OGNL injection sink. */
predicate ognlInjectionSinkMethod(Method m, int index) {
ognlSinkMethod(m, index) or
strutsSinkMethod(m, index)
}
/** A data flow sink for unvalidated user input that is used in OGNL EL evaluation. */
class OgnlInjectionSink extends DataFlow::ExprNode {
OgnlInjectionSink() {
exists(MethodAccess ma, Method m, int index |
ma.getMethod() = m and
(ma.getArgument(index) = this.getExpr() or ma.getQualifier() = this.getExpr()) and
ognlInjectionSinkMethod(m, index)
)
}
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and `Object` or `Node`,
* i.e. `Ognl.parseExpression(tainted)` or `Ognl.compileExpression(tainted)`.
*/
predicate parseCompileExpressionStep(ExprNode n1, ExprNode n2) {
exists(MethodAccess ma, Method m, int index |
n1.asExpr() = ma.getArgument(index) and
n2.asExpr() = ma and
ma.getMethod() = m and
m.getDeclaringType() instanceof TypeOgnl
|
m.hasName("parseExpression") and index = 0
or
m.hasName("compileExpression") and index = 2
)
}

View File

@@ -90,16 +90,16 @@ class Top extends @top {
/** A location maps language elements to positions in source files. */
class Location extends @location {
/** Gets the line number where this location starts. */
/** Gets the 1-based line number (inclusive) where this location starts. */
int getStartLine() { locations_default(this, _, result, _, _, _) }
/** Gets the column number where this location starts. */
/** Gets the 1-based column number (inclusive) where this location starts. */
int getStartColumn() { locations_default(this, _, _, result, _, _) }
/** Gets the line number where this location ends. */
/** Gets the 1-based line number (inclusive) where this location ends. */
int getEndLine() { locations_default(this, _, _, _, result, _) }
/** Gets the column number where this location ends. */
/** Gets the 1-based column number (inclusive) where this location ends. */
int getEndColumn() { locations_default(this, _, _, _, _, result) }
/**

View File

@@ -60,6 +60,12 @@ class Expr extends ExprParent, @expr {
/** Gets the statement containing this expression, if any. */
Stmt getEnclosingStmt() { statementEnclosingExpr(this, result) }
/**
* Gets a statement that directly or transitively contains this expression, if any.
* This is equivalent to `this.getEnclosingStmt().getEnclosingStmt*()`.
*/
Stmt getAnEnclosingStmt() { result = this.getEnclosingStmt().getEnclosingStmt*() }
/** Gets a child of this expression. */
Expr getAChildExpr() { exprs(result, _, _, this, _) }
@@ -1237,7 +1243,7 @@ class VariableAssign extends VariableUpdate {
}
/**
* Gets the source of this assignment, if any.
* Gets the source (right-hand side) of this assignment, if any.
*
* An initialization in a `CatchClause` or `EnhancedForStmt` is implicit and
* does not have a source.

View File

@@ -79,7 +79,7 @@ abstract class JavadocElement extends @javadocElement, Top {
abstract string getText();
}
/** A Javadoc tag. */
/** A Javadoc block tag. This does not include inline tags. */
class JavadocTag extends JavadocElement, JavadocParent, @javadocTag {
/** Gets the name of this Javadoc tag. */
string getTagName() { javadocTag(this, result, _, _) }

View File

@@ -16,7 +16,9 @@ import semmle.code.java.frameworks.android.XmlParsing
import semmle.code.java.frameworks.android.WebView
import semmle.code.java.frameworks.JaxWS
import semmle.code.java.frameworks.android.Intent
import semmle.code.java.frameworks.SpringWeb
import semmle.code.java.frameworks.spring.SpringWeb
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.frameworks.spring.SpringWebClient
import semmle.code.java.frameworks.Guice
import semmle.code.java.frameworks.struts.StrutsActions
import semmle.code.java.frameworks.Thrift
@@ -118,7 +120,7 @@ private class SpringMultipartFileSource extends RemoteFlowSource {
private class SpringServletInputParameterSource extends RemoteFlowSource {
SpringServletInputParameterSource() {
this.asParameter().getAnAnnotation() instanceof SpringServletInputAnnotation
this.asParameter() = any(SpringRequestMappingParameter srmp | srmp.isTaintedInput())
}
override string getSourceType() { result = "Spring servlet input parameter" }
@@ -215,6 +217,8 @@ private class RemoteTaintedMethod extends Method {
this instanceof HttpServletRequestGetRequestURIMethod or
this instanceof HttpServletRequestGetRequestURLMethod or
this instanceof HttpServletRequestGetRemoteUserMethod or
this instanceof SpringWebRequestGetMethod or
this instanceof SpringRestTemplateResponseEntityMethod or
this instanceof ServletRequestGetBodyMethod or
this instanceof CookieGetValueMethod or
this instanceof CookieGetNameMethod or
@@ -232,6 +236,22 @@ private class RemoteTaintedMethod extends Method {
}
}
private class SpringWebRequestGetMethod extends Method {
SpringWebRequestGetMethod() {
exists(SpringWebRequest swr | this = swr.getAMethod() |
this.hasName("getDescription") or
this.hasName("getHeader") or
this.hasName("getHeaderNames") or
this.hasName("getHeaderValues") or
this.hasName("getParameter") or
this.hasName("getParameterMap") or
this.hasName("getParameterNames") or
this.hasName("getParameterValues")
// TODO consider getRemoteUser
)
}
}
private class EnvTaintedMethod extends Method {
EnvTaintedMethod() {
this instanceof MethodSystemGetenv or

View File

@@ -89,45 +89,95 @@ class ContainerType extends RefType {
}
private predicate taintPreservingQualifierToMethod(Method m) {
// java.util.Map.Entry
m.getDeclaringType() instanceof EntryType and
m.hasName("getValue")
m.hasName(["getValue", "setValue"])
or
// java.util.Iterable
m.getDeclaringType() instanceof IterableType and
m.hasName("iterator")
m.hasName(["iterator", "spliterator"])
or
// java.util.Iterator
m.getDeclaringType() instanceof IteratorType and
m.hasName("next")
or
// java.util.ListIterator
m.getDeclaringType() instanceof IteratorType and
m.hasName("previous")
or
// java.util.Enumeration
m.getDeclaringType() instanceof EnumerationType and
m.hasName("nextElement")
m.hasName(["asIterator", "nextElement"])
or
m.(MapMethod).hasName("entrySet")
// java.util.Map
m
.(MapMethod)
.hasName(["computeIfAbsent", "entrySet", "get", "getOrDefault", "put", "putIfAbsent",
"remove", "replace", "values"])
or
m.(MapMethod).hasName("get")
// java.util.Collection
m.(CollectionMethod).hasName(["parallelStream", "stream", "toArray"])
or
m.(MapMethod).hasName("remove")
or
m.(MapMethod).hasName("values")
or
m.(CollectionMethod).hasName("toArray")
or
m.(CollectionMethod).hasName("get")
// java.util.List
m.(CollectionMethod).hasName(["get", "listIterator", "set", "subList"])
or
m.(CollectionMethod).hasName("remove") and m.getParameterType(0).(PrimitiveType).hasName("int")
or
// java.util.Vector
m.(CollectionMethod).hasName(["elementAt", "elements", "firstElement", "lastElement"])
or
// java.util.Stack
m.(CollectionMethod).hasName(["peek", "pop"])
or
// java.util.Queue
m.(CollectionMethod).hasName(["element", "poll"])
or
m.(CollectionMethod).hasName("remove") and m.getNumberOfParameters() = 0
or
m.(CollectionMethod).hasName("subList")
// java.util.Deque
m
.(CollectionMethod)
.hasName(["getFirst", "getLast", "peekFirst", "peekLast", "pollFirst", "pollLast",
"removeFirst", "removeLast"])
or
m.(CollectionMethod).hasName("firstElement")
// java.util.concurrent.BlockingQueue
// covered by Queue: poll(long, TimeUnit)
m.(CollectionMethod).hasName("take")
or
m.(CollectionMethod).hasName("lastElement")
// java.util.concurrent.BlockingDeque
// covered by Deque: pollFirst(long, TimeUnit), pollLast(long, TimeUnit)
m.(CollectionMethod).hasName(["takeFirst", "takeLast"])
or
m.(CollectionMethod).hasName("poll")
// java.util.SortedSet
m.(CollectionMethod).hasName(["first", "headSet", "last", "subSet", "tailSet"])
or
m.(CollectionMethod).hasName("peek")
// java.util.NavigableSet
// covered by Deque: pollFirst(), pollLast()
// covered by SortedSet: headSet(E, boolean), subSet(E, boolean, E, boolean) and tailSet(E, boolean)
m
.(CollectionMethod)
.hasName(["ceiling", "descendingIterator", "descendingSet", "floor", "higher", "lower"])
or
m.(CollectionMethod).hasName("element")
// java.util.SortedMap
m.(MapMethod).hasName(["headMap", "subMap", "tailMap"])
or
// java.util.NavigableMap
// covered by SortedMap: headMap(K, boolean), subMap(K, boolean, K, boolean), tailMap(K, boolean)
m
.(MapMethod)
.hasName(["ceilingEntry", "descendingMap", "firstEntry", "floorEntry", "higherEntry",
"lastEntry", "lowerEntry", "pollFirstEntry", "pollLastEntry"])
or
// java.util.Dictionary
m
.getDeclaringType()
.getSourceDeclaration()
.getASourceSupertype*()
.hasQualifiedName("java.util", "Dictionary") and
m.hasName(["elements", "get", "put", "remove"])
or
// java.util.concurrent.ConcurrentHashMap
m.(MapMethod).hasName(["elements", "search", "searchEntries", "searchValues"])
}
private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
@@ -135,28 +185,165 @@ private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
tracked = sink.getQualifier()
}
private predicate qualifierToArgumentStep(Expr tracked, RValue sink) {
exists(MethodAccess ma |
ma.getMethod().(CollectionMethod).hasName("toArray") and
private predicate qualifierToArgumentStep(Expr tracked, Expr sink) {
exists(MethodAccess ma, CollectionMethod method |
method = ma.getMethod() and
(
// java.util.Vector
method.hasName("copyInto")
or
// java.util.concurrent.BlockingQueue
method.hasName("drainTo")
or
// java.util.Collection
method.hasName("toArray") and method.getParameter(0).getType() instanceof Array
) and
tracked = ma.getQualifier() and
sink = ma.getArgument(1)
sink = ma.getArgument(0)
)
}
private predicate taintPreservingArgumentToQualifier(Method method, int arg) {
method.(MapMethod).hasName("put") and arg = 1
// java.util.Map.Entry
method.getDeclaringType() instanceof EntryType and
method.hasName("setValue") and
arg = 0
or
// java.util.Map
method.(MapMethod).hasName(["merge", "put", "putIfAbsent"]) and arg = 1
or
method.(MapMethod).hasName("replace") and arg = method.getNumberOfParameters() - 1
or
method.(MapMethod).hasName("putAll") and arg = 0
or
method.(CollectionMethod).hasName("add") and arg = method.getNumberOfParameters() - 1
// java.util.ListIterator
method.getDeclaringType() instanceof IteratorType and
method.hasName(["add", "set"]) and
arg = 0
or
method.(CollectionMethod).hasName("addAll") and arg = method.getNumberOfParameters() - 1
or
method.(CollectionMethod).hasName("addElement") and arg = 0
// java.util.Collection
method.(CollectionMethod).hasName(["add", "addAll"]) and
// Refer to the last parameter to also cover List::add(int, E) and List::addAll(int, Collection)
arg = method.getNumberOfParameters() - 1
or
// java.util.List
// covered by Collection: add(int, E), addAll(int, Collection<? extends E>)
method.(CollectionMethod).hasName("set") and arg = 1
or
// java.util.Vector
method.(CollectionMethod).hasName(["addElement", "insertElementAt", "setElementAt"]) and arg = 0
or
// java.util.Stack
method.(CollectionMethod).hasName("push") and arg = 0
or
// java.util.Queue
method.(CollectionMethod).hasName("offer") and arg = 0
or
// java.util.Deque
// covered by Stack: push(E)
method.(CollectionMethod).hasName(["addFirst", "addLast", "offerFirst", "offerLast"]) and arg = 0
or
// java.util.concurrent.BlockingQueue
// covered by Queue: offer(E, long, TimeUnit)
method.(CollectionMethod).hasName("put") and arg = 0
or
// java.util.concurrent.TransferQueue
method.(CollectionMethod).hasName(["transfer", "tryTransfer"]) and arg = 0
or
// java.util.concurrent.BlockingDeque
// covered by Deque: offerFirst(E, long, TimeUnit), offerLast(E, long, TimeUnit)
method.(CollectionMethod).hasName(["putFirst", "putLast"]) and arg = 0
or
//java.util.Dictionary
method
.getDeclaringType()
.getSourceDeclaration()
.getASourceSupertype*()
.hasQualifiedName("java.util", "Dictionary") and
method.hasName("put") and
arg = 1
}
/**
* Holds if `method` is a library method that returns tainted data if its
* `arg`th argument is tainted.
*/
private predicate taintPreservingArgumentToMethod(Method method, int arg) {
// java.util.Stack
method.(CollectionMethod).hasName("push") and arg = 0
or
method.getDeclaringType().hasQualifiedName("java.util", "Collections") and
(
method
.hasName(["checkedCollection", "checkedList", "checkedMap", "checkedNavigableMap",
"checkedNavigableSet", "checkedSet", "checkedSortedMap", "checkedSortedSet",
"enumeration", "list", "max", "min", "singleton", "singletonList",
"synchronizedCollection", "synchronizedList", "synchronizedMap",
"synchronizedNavigableMap", "synchronizedNavigableSet", "synchronizedSet",
"synchronizedSortedMap", "synchronizedSortedSet", "unmodifiableCollection",
"unmodifiableList", "unmodifiableMap", "unmodifiableNavigableMap",
"unmodifiableNavigableSet", "unmodifiableSet", "unmodifiableSortedMap",
"unmodifiableSortedSet"]) and
arg = 0
or
method.hasName(["nCopies", "singletonMap"]) and arg = 1
)
or
method
.getDeclaringType()
.getSourceDeclaration()
.hasQualifiedName("java.util", ["List", "Map", "Set"]) and
method.hasName("copyOf") and
arg = 0
or
method.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", "Map") and
(
method.hasName("of") and
arg = any(int i | i in [1 .. 10] | 2 * i - 1)
or
method.hasName("entry") and
arg = 1
)
or
method.getDeclaringType().hasQualifiedName("java.util", "Arrays") and
(
method.hasName(["copyOf", "copyOfRange", "spliterator", "stream"]) and
arg = 0
)
}
/**
* Holds if `method` is a library method that returns tainted data if any
* of its arguments are tainted.
*/
private predicate taintPreservingArgumentToMethod(Method method) {
method.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", ["Set", "List"]) and
method.hasName("of")
or
method.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", "Map") and
method.hasName("ofEntries")
}
/**
* Holds if `method` is a library method that writes tainted data to the
* `output`th argument if the `input`th argument is tainted.
*/
private predicate taintPreservingArgToArg(Method method, int input, int output) {
method.getDeclaringType().hasQualifiedName("java.util", "Collections") and
(
method.hasName(["copy", "fill"]) and
input = 1 and
output = 0
or
method.hasName("replaceAll") and input = 2 and output = 0
)
or
method.getDeclaringType().hasQualifiedName("java.util", "Arrays") and
(
method.hasName("fill") and
output = 0 and
input = method.getNumberOfParameters() - 1
)
}
private predicate argToQualifierStep(Expr tracked, Expr sink) {
@@ -168,13 +355,55 @@ private predicate argToQualifierStep(Expr tracked, Expr sink) {
)
}
/** Access to a method that passes taint from an argument. */
private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
exists(Method m |
m = sink.getMethod() and
(
exists(int i |
taintPreservingArgumentToMethod(m, i) and
tracked = sink.getArgument(i)
)
or
m.getDeclaringType().hasQualifiedName("java.util", "Arrays") and
m.hasName("asList") and
tracked = sink.getAnArgument()
)
)
or
taintPreservingArgumentToMethod(sink.getMethod()) and
tracked = sink.getAnArgument()
}
/**
* Holds if `tracked` and `sink` are arguments to a method that transfers taint
* between arguments.
*/
private predicate argToArgStep(Expr tracked, Expr sink) {
exists(MethodAccess ma, Method method, int input, int output |
ma.getMethod() = method and
ma.getArgument(input) = tracked and
ma.getArgument(output) = sink and
(
taintPreservingArgToArg(method, input, output)
or
method.getDeclaringType().hasQualifiedName("java.util", "Collections") and
method.hasName("addAll") and
input >= 1 and
output = 0
)
)
}
/**
* Holds if the step from `n1` to `n2` is either extracting a value from a
* container, inserting a value into a container, or transforming one container
* to another. This is restricted to cases where `n2` is the returned value of
* a call.
*/
predicate containerReturnValueStep(Expr n1, Expr n2) { qualifierToMethodStep(n1, n2) }
predicate containerReturnValueStep(Expr n1, Expr n2) {
qualifierToMethodStep(n1, n2) or argToMethodStep(n1, n2)
}
/**
* Holds if the step from `n1` to `n2` is either extracting a value from a
@@ -183,7 +412,8 @@ predicate containerReturnValueStep(Expr n1, Expr n2) { qualifierToMethodStep(n1,
*/
predicate containerUpdateStep(Expr n1, Expr n2) {
qualifierToArgumentStep(n1, n2) or
argToQualifierStep(n1, n2)
argToQualifierStep(n1, n2) or
argToArgStep(n1, n2)
}
/**

View File

@@ -2,14 +2,13 @@ private import java
private import DataFlowPrivate
import semmle.code.java.dispatch.VirtualDispatch
cached
private module DispatchImpl {
/**
* 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
* qualifier is the `i`th parameter of the enclosing callable `c`.
*/
private predicate benefitsFromCallContext(MethodAccess ma, Callable c, int i) {
private predicate mayBenefitFromCallContext(MethodAccess ma, Callable c, int i) {
exists(Parameter p |
2 <= strictcount(viableImpl(ma)) and
ma.getQualifier().(VarAccess).getVariable() = p and
@@ -28,7 +27,7 @@ private module DispatchImpl {
pragma[nomagic]
private predicate relevantContext(Call ctx, int i) {
exists(Callable c |
benefitsFromCallContext(_, c, i) and
mayBenefitFromCallContext(_, c, i) and
c = viableCallable(ctx)
)
}
@@ -53,14 +52,23 @@ private module DispatchImpl {
)
}
/**
* 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
* qualifier is a parameter of the enclosing callable `c`.
*/
predicate mayBenefitFromCallContext(MethodAccess ma, Callable c) {
mayBenefitFromCallContext(ma, c, _)
}
/**
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
* restricted to those `ma`s for which a context might make a difference.
*/
private Method viableImplInCallContext(MethodAccess ma, Call ctx) {
Method viableImplInCallContext(MethodAccess ma, Call ctx) {
result = viableImpl(ma) and
exists(int i, Callable c, Method def, RefType t, boolean exact |
benefitsFromCallContext(ma, c, i) and
mayBenefitFromCallContext(ma, c, i) and
c = viableCallable(ctx) and
contextArgHasType(ctx, i, t, exact) and
ma.getMethod() = def
@@ -136,57 +144,6 @@ private module DispatchImpl {
)
)
}
/**
* Holds if the call context `ctx` reduces the set of viable dispatch
* targets of `ma` in `c`.
*/
cached
predicate reducedViableImplInCallContext(MethodAccess ma, Callable c, Call ctx) {
exists(int tgts, int ctxtgts |
benefitsFromCallContext(ma, c, _) and
c = viableCallable(ctx) and
ctxtgts = count(viableImplInCallContext(ma, ctx)) and
tgts = strictcount(viableImpl(ma)) and
ctxtgts < tgts
)
}
/**
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
* restricted to those `ma`s for which the context makes a difference.
*/
cached
Method prunedViableImplInCallContext(MethodAccess ma, Call ctx) {
result = viableImplInCallContext(ma, ctx) and
reducedViableImplInCallContext(ma, _, ctx)
}
/**
* Holds if flow returning from `m` to `ma` might return further and if
* this path restricts the set of call sites that can be returned to.
*/
cached
predicate reducedViableImplInReturn(Method m, MethodAccess ma) {
exists(int tgts, int ctxtgts |
benefitsFromCallContext(ma, _, _) and
m = viableImpl(ma) and
ctxtgts = count(Call ctx | m = viableImplInCallContext(ma, ctx)) and
tgts = strictcount(Call ctx | viableCallable(ctx) = ma.getEnclosingCallable()) and
ctxtgts < tgts
)
}
/**
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
* restricted to those `ma`s and results for which the return flow from the
* result to `ma` restricts the possible context `ctx`.
*/
cached
Method prunedViableImplInCallContextReverse(MethodAccess ma, Call ctx) {
result = viableImplInCallContext(ma, ctx) and
reducedViableImplInReturn(result, ma)
}
}
import DispatchImpl

View File

@@ -1124,11 +1124,11 @@ private module LocalFlowBigStep {
(
localFlowStepNodeCand1(node1, node2, config) and
preservesValue = true and
t = getErasedNodeTypeBound(node1)
t = getNodeType(node1)
or
additionalLocalFlowStepNodeCand2(node1, node2, config) and
preservesValue = false and
t = getErasedNodeTypeBound(node2)
t = getNodeType(node2)
) and
node1 != node2 and
cc.relevantFor(node1.getEnclosingCallable()) and
@@ -1147,7 +1147,7 @@ private module LocalFlowBigStep {
additionalLocalFlowStepNodeCand2(mid, node2, config) and
not mid instanceof FlowCheckNode and
preservesValue = false and
t = getErasedNodeTypeBound(node2) and
t = getNodeType(node2) and
nodeCand2(node2, unbind(config))
)
)
@@ -1202,9 +1202,7 @@ private predicate flowCandFwd(
) {
flowCandFwd0(node, fromArg, argApf, apf, config) and
not apf.isClearedAt(node) and
if node instanceof CastingNode
then compatibleTypes(getErasedNodeTypeBound(node), apf.getType())
else any()
if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any()
}
pragma[nomagic]
@@ -1216,7 +1214,7 @@ private predicate flowCandFwd0(
config.isSource(node) and
fromArg = false and
argApf = TAccessPathFrontNone() and
apf = TFrontNil(getErasedNodeTypeBound(node))
apf = TFrontNil(getNodeType(node))
or
exists(Node mid |
flowCandFwd(mid, fromArg, argApf, apf, config) and
@@ -1242,7 +1240,7 @@ private predicate flowCandFwd0(
additionalJumpStep(mid, node, config) and
fromArg = false and
argApf = TAccessPathFrontNone() and
apf = TFrontNil(getErasedNodeTypeBound(node))
apf = TFrontNil(getNodeType(node))
)
or
// store
@@ -1672,7 +1670,7 @@ private predicate flowFwd0(
config.isSource(node) and
fromArg = false and
argAp = TAccessPathNone() and
ap = TNil(getErasedNodeTypeBound(node)) and
ap = TNil(getNodeType(node)) and
apf = ap.(AccessPathNil).getFront()
or
flowCand(node, _, _, _, unbind(config)) and
@@ -1700,7 +1698,7 @@ private predicate flowFwd0(
additionalJumpStep(mid, node, config) and
fromArg = false and
argAp = TAccessPathNone() and
ap = TNil(getErasedNodeTypeBound(node)) and
ap = TNil(getNodeType(node)) and
apf = ap.(AccessPathNil).getFront()
)
)
@@ -2077,7 +2075,7 @@ private newtype TPathNode =
config.isSource(node) and
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
ap = TNil(getErasedNodeTypeBound(node))
ap = TNil(getNodeType(node))
or
// ... or a step from an existing PathNode to another node.
exists(PathNodeMid mid |
@@ -2304,7 +2302,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
mid.getAp() instanceof AccessPathNil and
ap = TNil(getErasedNodeTypeBound(node))
ap = TNil(getNodeType(node))
or
exists(TypedContent tc | pathStoreStep(mid, node, pop(tc, ap), tc, cc)) and
sc = mid.getSummaryCtx()
@@ -2646,7 +2644,7 @@ private module FlowExploration {
cc instanceof CallContextAny and
sc1 = TSummaryCtx1None() and
sc2 = TSummaryCtx2None() and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
not fullBarrier(node, config) and
exists(config.explorationLimit())
or
@@ -2663,7 +2661,7 @@ private module FlowExploration {
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
if node instanceof CastingNode
then compatibleTypes(getErasedNodeTypeBound(node), ap.getType())
then compatibleTypes(getNodeType(node), ap.getType())
else any()
)
}
@@ -2776,7 +2774,7 @@ private module FlowExploration {
sc1 = mid.getSummaryCtx1() and
sc2 = mid.getSummaryCtx2() and
mid.getAp() instanceof PartialAccessPathNil and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
config = mid.getConfiguration()
)
or
@@ -2792,7 +2790,7 @@ private module FlowExploration {
sc1 = TSummaryCtx1None() and
sc2 = TSummaryCtx2None() and
mid.getAp() instanceof PartialAccessPathNil and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
config = mid.getConfiguration()
or
partialPathStoreStep(mid, _, _, node, ap) and
@@ -2806,7 +2804,7 @@ private module FlowExploration {
sc1 = mid.getSummaryCtx1() and
sc2 = mid.getSummaryCtx2() and
apConsFwd(ap, tc, ap0, config) and
compatibleTypes(ap.getType(), getErasedNodeTypeBound(node))
compatibleTypes(ap.getType(), getNodeType(node))
)
or
partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config)

View File

@@ -1124,11 +1124,11 @@ private module LocalFlowBigStep {
(
localFlowStepNodeCand1(node1, node2, config) and
preservesValue = true and
t = getErasedNodeTypeBound(node1)
t = getNodeType(node1)
or
additionalLocalFlowStepNodeCand2(node1, node2, config) and
preservesValue = false and
t = getErasedNodeTypeBound(node2)
t = getNodeType(node2)
) and
node1 != node2 and
cc.relevantFor(node1.getEnclosingCallable()) and
@@ -1147,7 +1147,7 @@ private module LocalFlowBigStep {
additionalLocalFlowStepNodeCand2(mid, node2, config) and
not mid instanceof FlowCheckNode and
preservesValue = false and
t = getErasedNodeTypeBound(node2) and
t = getNodeType(node2) and
nodeCand2(node2, unbind(config))
)
)
@@ -1202,9 +1202,7 @@ private predicate flowCandFwd(
) {
flowCandFwd0(node, fromArg, argApf, apf, config) and
not apf.isClearedAt(node) and
if node instanceof CastingNode
then compatibleTypes(getErasedNodeTypeBound(node), apf.getType())
else any()
if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any()
}
pragma[nomagic]
@@ -1216,7 +1214,7 @@ private predicate flowCandFwd0(
config.isSource(node) and
fromArg = false and
argApf = TAccessPathFrontNone() and
apf = TFrontNil(getErasedNodeTypeBound(node))
apf = TFrontNil(getNodeType(node))
or
exists(Node mid |
flowCandFwd(mid, fromArg, argApf, apf, config) and
@@ -1242,7 +1240,7 @@ private predicate flowCandFwd0(
additionalJumpStep(mid, node, config) and
fromArg = false and
argApf = TAccessPathFrontNone() and
apf = TFrontNil(getErasedNodeTypeBound(node))
apf = TFrontNil(getNodeType(node))
)
or
// store
@@ -1672,7 +1670,7 @@ private predicate flowFwd0(
config.isSource(node) and
fromArg = false and
argAp = TAccessPathNone() and
ap = TNil(getErasedNodeTypeBound(node)) and
ap = TNil(getNodeType(node)) and
apf = ap.(AccessPathNil).getFront()
or
flowCand(node, _, _, _, unbind(config)) and
@@ -1700,7 +1698,7 @@ private predicate flowFwd0(
additionalJumpStep(mid, node, config) and
fromArg = false and
argAp = TAccessPathNone() and
ap = TNil(getErasedNodeTypeBound(node)) and
ap = TNil(getNodeType(node)) and
apf = ap.(AccessPathNil).getFront()
)
)
@@ -2077,7 +2075,7 @@ private newtype TPathNode =
config.isSource(node) and
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
ap = TNil(getErasedNodeTypeBound(node))
ap = TNil(getNodeType(node))
or
// ... or a step from an existing PathNode to another node.
exists(PathNodeMid mid |
@@ -2304,7 +2302,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
mid.getAp() instanceof AccessPathNil and
ap = TNil(getErasedNodeTypeBound(node))
ap = TNil(getNodeType(node))
or
exists(TypedContent tc | pathStoreStep(mid, node, pop(tc, ap), tc, cc)) and
sc = mid.getSummaryCtx()
@@ -2646,7 +2644,7 @@ private module FlowExploration {
cc instanceof CallContextAny and
sc1 = TSummaryCtx1None() and
sc2 = TSummaryCtx2None() and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
not fullBarrier(node, config) and
exists(config.explorationLimit())
or
@@ -2663,7 +2661,7 @@ private module FlowExploration {
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
if node instanceof CastingNode
then compatibleTypes(getErasedNodeTypeBound(node), ap.getType())
then compatibleTypes(getNodeType(node), ap.getType())
else any()
)
}
@@ -2776,7 +2774,7 @@ private module FlowExploration {
sc1 = mid.getSummaryCtx1() and
sc2 = mid.getSummaryCtx2() and
mid.getAp() instanceof PartialAccessPathNil and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
config = mid.getConfiguration()
)
or
@@ -2792,7 +2790,7 @@ private module FlowExploration {
sc1 = TSummaryCtx1None() and
sc2 = TSummaryCtx2None() and
mid.getAp() instanceof PartialAccessPathNil and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
config = mid.getConfiguration()
or
partialPathStoreStep(mid, _, _, node, ap) and
@@ -2806,7 +2804,7 @@ private module FlowExploration {
sc1 = mid.getSummaryCtx1() and
sc2 = mid.getSummaryCtx2() and
apConsFwd(ap, tc, ap0, config) and
compatibleTypes(ap.getType(), getErasedNodeTypeBound(node))
compatibleTypes(ap.getType(), getNodeType(node))
)
or
partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config)

View File

@@ -1124,11 +1124,11 @@ private module LocalFlowBigStep {
(
localFlowStepNodeCand1(node1, node2, config) and
preservesValue = true and
t = getErasedNodeTypeBound(node1)
t = getNodeType(node1)
or
additionalLocalFlowStepNodeCand2(node1, node2, config) and
preservesValue = false and
t = getErasedNodeTypeBound(node2)
t = getNodeType(node2)
) and
node1 != node2 and
cc.relevantFor(node1.getEnclosingCallable()) and
@@ -1147,7 +1147,7 @@ private module LocalFlowBigStep {
additionalLocalFlowStepNodeCand2(mid, node2, config) and
not mid instanceof FlowCheckNode and
preservesValue = false and
t = getErasedNodeTypeBound(node2) and
t = getNodeType(node2) and
nodeCand2(node2, unbind(config))
)
)
@@ -1202,9 +1202,7 @@ private predicate flowCandFwd(
) {
flowCandFwd0(node, fromArg, argApf, apf, config) and
not apf.isClearedAt(node) and
if node instanceof CastingNode
then compatibleTypes(getErasedNodeTypeBound(node), apf.getType())
else any()
if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any()
}
pragma[nomagic]
@@ -1216,7 +1214,7 @@ private predicate flowCandFwd0(
config.isSource(node) and
fromArg = false and
argApf = TAccessPathFrontNone() and
apf = TFrontNil(getErasedNodeTypeBound(node))
apf = TFrontNil(getNodeType(node))
or
exists(Node mid |
flowCandFwd(mid, fromArg, argApf, apf, config) and
@@ -1242,7 +1240,7 @@ private predicate flowCandFwd0(
additionalJumpStep(mid, node, config) and
fromArg = false and
argApf = TAccessPathFrontNone() and
apf = TFrontNil(getErasedNodeTypeBound(node))
apf = TFrontNil(getNodeType(node))
)
or
// store
@@ -1672,7 +1670,7 @@ private predicate flowFwd0(
config.isSource(node) and
fromArg = false and
argAp = TAccessPathNone() and
ap = TNil(getErasedNodeTypeBound(node)) and
ap = TNil(getNodeType(node)) and
apf = ap.(AccessPathNil).getFront()
or
flowCand(node, _, _, _, unbind(config)) and
@@ -1700,7 +1698,7 @@ private predicate flowFwd0(
additionalJumpStep(mid, node, config) and
fromArg = false and
argAp = TAccessPathNone() and
ap = TNil(getErasedNodeTypeBound(node)) and
ap = TNil(getNodeType(node)) and
apf = ap.(AccessPathNil).getFront()
)
)
@@ -2077,7 +2075,7 @@ private newtype TPathNode =
config.isSource(node) and
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
ap = TNil(getErasedNodeTypeBound(node))
ap = TNil(getNodeType(node))
or
// ... or a step from an existing PathNode to another node.
exists(PathNodeMid mid |
@@ -2304,7 +2302,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
mid.getAp() instanceof AccessPathNil and
ap = TNil(getErasedNodeTypeBound(node))
ap = TNil(getNodeType(node))
or
exists(TypedContent tc | pathStoreStep(mid, node, pop(tc, ap), tc, cc)) and
sc = mid.getSummaryCtx()
@@ -2646,7 +2644,7 @@ private module FlowExploration {
cc instanceof CallContextAny and
sc1 = TSummaryCtx1None() and
sc2 = TSummaryCtx2None() and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
not fullBarrier(node, config) and
exists(config.explorationLimit())
or
@@ -2663,7 +2661,7 @@ private module FlowExploration {
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
if node instanceof CastingNode
then compatibleTypes(getErasedNodeTypeBound(node), ap.getType())
then compatibleTypes(getNodeType(node), ap.getType())
else any()
)
}
@@ -2776,7 +2774,7 @@ private module FlowExploration {
sc1 = mid.getSummaryCtx1() and
sc2 = mid.getSummaryCtx2() and
mid.getAp() instanceof PartialAccessPathNil and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
config = mid.getConfiguration()
)
or
@@ -2792,7 +2790,7 @@ private module FlowExploration {
sc1 = TSummaryCtx1None() and
sc2 = TSummaryCtx2None() and
mid.getAp() instanceof PartialAccessPathNil and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
config = mid.getConfiguration()
or
partialPathStoreStep(mid, _, _, node, ap) and
@@ -2806,7 +2804,7 @@ private module FlowExploration {
sc1 = mid.getSummaryCtx1() and
sc2 = mid.getSummaryCtx2() and
apConsFwd(ap, tc, ap0, config) and
compatibleTypes(ap.getType(), getErasedNodeTypeBound(node))
compatibleTypes(ap.getType(), getNodeType(node))
)
or
partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config)

View File

@@ -1124,11 +1124,11 @@ private module LocalFlowBigStep {
(
localFlowStepNodeCand1(node1, node2, config) and
preservesValue = true and
t = getErasedNodeTypeBound(node1)
t = getNodeType(node1)
or
additionalLocalFlowStepNodeCand2(node1, node2, config) and
preservesValue = false and
t = getErasedNodeTypeBound(node2)
t = getNodeType(node2)
) and
node1 != node2 and
cc.relevantFor(node1.getEnclosingCallable()) and
@@ -1147,7 +1147,7 @@ private module LocalFlowBigStep {
additionalLocalFlowStepNodeCand2(mid, node2, config) and
not mid instanceof FlowCheckNode and
preservesValue = false and
t = getErasedNodeTypeBound(node2) and
t = getNodeType(node2) and
nodeCand2(node2, unbind(config))
)
)
@@ -1202,9 +1202,7 @@ private predicate flowCandFwd(
) {
flowCandFwd0(node, fromArg, argApf, apf, config) and
not apf.isClearedAt(node) and
if node instanceof CastingNode
then compatibleTypes(getErasedNodeTypeBound(node), apf.getType())
else any()
if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any()
}
pragma[nomagic]
@@ -1216,7 +1214,7 @@ private predicate flowCandFwd0(
config.isSource(node) and
fromArg = false and
argApf = TAccessPathFrontNone() and
apf = TFrontNil(getErasedNodeTypeBound(node))
apf = TFrontNil(getNodeType(node))
or
exists(Node mid |
flowCandFwd(mid, fromArg, argApf, apf, config) and
@@ -1242,7 +1240,7 @@ private predicate flowCandFwd0(
additionalJumpStep(mid, node, config) and
fromArg = false and
argApf = TAccessPathFrontNone() and
apf = TFrontNil(getErasedNodeTypeBound(node))
apf = TFrontNil(getNodeType(node))
)
or
// store
@@ -1672,7 +1670,7 @@ private predicate flowFwd0(
config.isSource(node) and
fromArg = false and
argAp = TAccessPathNone() and
ap = TNil(getErasedNodeTypeBound(node)) and
ap = TNil(getNodeType(node)) and
apf = ap.(AccessPathNil).getFront()
or
flowCand(node, _, _, _, unbind(config)) and
@@ -1700,7 +1698,7 @@ private predicate flowFwd0(
additionalJumpStep(mid, node, config) and
fromArg = false and
argAp = TAccessPathNone() and
ap = TNil(getErasedNodeTypeBound(node)) and
ap = TNil(getNodeType(node)) and
apf = ap.(AccessPathNil).getFront()
)
)
@@ -2077,7 +2075,7 @@ private newtype TPathNode =
config.isSource(node) and
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
ap = TNil(getErasedNodeTypeBound(node))
ap = TNil(getNodeType(node))
or
// ... or a step from an existing PathNode to another node.
exists(PathNodeMid mid |
@@ -2304,7 +2302,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
mid.getAp() instanceof AccessPathNil and
ap = TNil(getErasedNodeTypeBound(node))
ap = TNil(getNodeType(node))
or
exists(TypedContent tc | pathStoreStep(mid, node, pop(tc, ap), tc, cc)) and
sc = mid.getSummaryCtx()
@@ -2646,7 +2644,7 @@ private module FlowExploration {
cc instanceof CallContextAny and
sc1 = TSummaryCtx1None() and
sc2 = TSummaryCtx2None() and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
not fullBarrier(node, config) and
exists(config.explorationLimit())
or
@@ -2663,7 +2661,7 @@ private module FlowExploration {
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
if node instanceof CastingNode
then compatibleTypes(getErasedNodeTypeBound(node), ap.getType())
then compatibleTypes(getNodeType(node), ap.getType())
else any()
)
}
@@ -2776,7 +2774,7 @@ private module FlowExploration {
sc1 = mid.getSummaryCtx1() and
sc2 = mid.getSummaryCtx2() and
mid.getAp() instanceof PartialAccessPathNil and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
config = mid.getConfiguration()
)
or
@@ -2792,7 +2790,7 @@ private module FlowExploration {
sc1 = TSummaryCtx1None() and
sc2 = TSummaryCtx2None() and
mid.getAp() instanceof PartialAccessPathNil and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
config = mid.getConfiguration()
or
partialPathStoreStep(mid, _, _, node, ap) and
@@ -2806,7 +2804,7 @@ private module FlowExploration {
sc1 = mid.getSummaryCtx1() and
sc2 = mid.getSummaryCtx2() and
apConsFwd(ap, tc, ap0, config) and
compatibleTypes(ap.getType(), getErasedNodeTypeBound(node))
compatibleTypes(ap.getType(), getNodeType(node))
)
or
partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config)

View File

@@ -1124,11 +1124,11 @@ private module LocalFlowBigStep {
(
localFlowStepNodeCand1(node1, node2, config) and
preservesValue = true and
t = getErasedNodeTypeBound(node1)
t = getNodeType(node1)
or
additionalLocalFlowStepNodeCand2(node1, node2, config) and
preservesValue = false and
t = getErasedNodeTypeBound(node2)
t = getNodeType(node2)
) and
node1 != node2 and
cc.relevantFor(node1.getEnclosingCallable()) and
@@ -1147,7 +1147,7 @@ private module LocalFlowBigStep {
additionalLocalFlowStepNodeCand2(mid, node2, config) and
not mid instanceof FlowCheckNode and
preservesValue = false and
t = getErasedNodeTypeBound(node2) and
t = getNodeType(node2) and
nodeCand2(node2, unbind(config))
)
)
@@ -1202,9 +1202,7 @@ private predicate flowCandFwd(
) {
flowCandFwd0(node, fromArg, argApf, apf, config) and
not apf.isClearedAt(node) and
if node instanceof CastingNode
then compatibleTypes(getErasedNodeTypeBound(node), apf.getType())
else any()
if node instanceof CastingNode then compatibleTypes(getNodeType(node), apf.getType()) else any()
}
pragma[nomagic]
@@ -1216,7 +1214,7 @@ private predicate flowCandFwd0(
config.isSource(node) and
fromArg = false and
argApf = TAccessPathFrontNone() and
apf = TFrontNil(getErasedNodeTypeBound(node))
apf = TFrontNil(getNodeType(node))
or
exists(Node mid |
flowCandFwd(mid, fromArg, argApf, apf, config) and
@@ -1242,7 +1240,7 @@ private predicate flowCandFwd0(
additionalJumpStep(mid, node, config) and
fromArg = false and
argApf = TAccessPathFrontNone() and
apf = TFrontNil(getErasedNodeTypeBound(node))
apf = TFrontNil(getNodeType(node))
)
or
// store
@@ -1672,7 +1670,7 @@ private predicate flowFwd0(
config.isSource(node) and
fromArg = false and
argAp = TAccessPathNone() and
ap = TNil(getErasedNodeTypeBound(node)) and
ap = TNil(getNodeType(node)) and
apf = ap.(AccessPathNil).getFront()
or
flowCand(node, _, _, _, unbind(config)) and
@@ -1700,7 +1698,7 @@ private predicate flowFwd0(
additionalJumpStep(mid, node, config) and
fromArg = false and
argAp = TAccessPathNone() and
ap = TNil(getErasedNodeTypeBound(node)) and
ap = TNil(getNodeType(node)) and
apf = ap.(AccessPathNil).getFront()
)
)
@@ -2077,7 +2075,7 @@ private newtype TPathNode =
config.isSource(node) and
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
ap = TNil(getErasedNodeTypeBound(node))
ap = TNil(getNodeType(node))
or
// ... or a step from an existing PathNode to another node.
exists(PathNodeMid mid |
@@ -2304,7 +2302,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
mid.getAp() instanceof AccessPathNil and
ap = TNil(getErasedNodeTypeBound(node))
ap = TNil(getNodeType(node))
or
exists(TypedContent tc | pathStoreStep(mid, node, pop(tc, ap), tc, cc)) and
sc = mid.getSummaryCtx()
@@ -2646,7 +2644,7 @@ private module FlowExploration {
cc instanceof CallContextAny and
sc1 = TSummaryCtx1None() and
sc2 = TSummaryCtx2None() and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
not fullBarrier(node, config) and
exists(config.explorationLimit())
or
@@ -2663,7 +2661,7 @@ private module FlowExploration {
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
if node instanceof CastingNode
then compatibleTypes(getErasedNodeTypeBound(node), ap.getType())
then compatibleTypes(getNodeType(node), ap.getType())
else any()
)
}
@@ -2776,7 +2774,7 @@ private module FlowExploration {
sc1 = mid.getSummaryCtx1() and
sc2 = mid.getSummaryCtx2() and
mid.getAp() instanceof PartialAccessPathNil and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
config = mid.getConfiguration()
)
or
@@ -2792,7 +2790,7 @@ private module FlowExploration {
sc1 = TSummaryCtx1None() and
sc2 = TSummaryCtx2None() and
mid.getAp() instanceof PartialAccessPathNil and
ap = TPartialNil(getErasedNodeTypeBound(node)) and
ap = TPartialNil(getNodeType(node)) and
config = mid.getConfiguration()
or
partialPathStoreStep(mid, _, _, node, ap) and
@@ -2806,7 +2804,7 @@ private module FlowExploration {
sc1 = mid.getSummaryCtx1() and
sc2 = mid.getSummaryCtx2() and
apConsFwd(ap, tc, ap0, config) and
compatibleTypes(ap.getType(), getErasedNodeTypeBound(node))
compatibleTypes(ap.getType(), getNodeType(node))
)
or
partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config)

View File

@@ -22,7 +22,7 @@ private module Cached {
exists(int i |
viableParam(call, i, p) and
arg.argumentOf(call, i) and
compatibleTypes(getErasedNodeTypeBound(arg), getErasedNodeTypeBound(p))
compatibleTypes(getNodeType(arg), getNodeType(p))
)
}
@@ -147,54 +147,6 @@ private module Cached {
}
}
private module LocalFlowBigStep {
private predicate localFlowEntry(Node n) {
Cand::cand(_, n) and
(
n instanceof ParameterNode or
n instanceof OutNode or
readStep(_, _, n) or
n instanceof CastNode
)
}
private predicate localFlowExit(Node n) {
Cand::cand(_, n) and
(
n instanceof ArgumentNode
or
n instanceof ReturnNode
or
readStep(n, _, _)
or
n instanceof CastNode
or
n =
any(PostUpdateNode pun | Cand::parameterValueFlowsToPreUpdateCand(_, pun))
.getPreUpdateNode()
)
}
pragma[nomagic]
private predicate localFlowStepPlus(Node node1, Node node2) {
localFlowEntry(node1) and
simpleLocalFlowStep(node1, node2) and
node1 != node2
or
exists(Node mid |
localFlowStepPlus(node1, mid) and
simpleLocalFlowStep(mid, node2) and
not mid instanceof CastNode
)
}
pragma[nomagic]
predicate localFlowBigStep(Node node1, Node node2) {
localFlowStepPlus(node1, node2) and
localFlowExit(node2)
}
}
/**
* The final flow-through calculation:
*
@@ -218,10 +170,10 @@ private module Cached {
then
// normal flow through
read = TReadStepTypesNone() and
compatibleTypes(getErasedNodeTypeBound(p), getErasedNodeTypeBound(node))
compatibleTypes(getNodeType(p), getNodeType(node))
or
// getter
compatibleTypes(read.getContentType(), getErasedNodeTypeBound(node))
compatibleTypes(read.getContentType(), getNodeType(node))
else any()
}
@@ -234,7 +186,7 @@ private module Cached {
// local flow
exists(Node mid |
parameterValueFlow(p, mid, read) and
LocalFlowBigStep::localFlowBigStep(mid, node)
simpleLocalFlowStep(mid, node)
)
or
// read
@@ -243,19 +195,26 @@ private module Cached {
readStepWithTypes(mid, read.getContainerType(), read.getContent(), node,
read.getContentType()) and
Cand::parameterValueFlowReturnCand(p, _, true) and
compatibleTypes(getErasedNodeTypeBound(p), read.getContainerType())
compatibleTypes(getNodeType(p), read.getContainerType())
)
or
parameterValueFlow0_0(TReadStepTypesNone(), p, node, read)
}
pragma[nomagic]
private predicate parameterValueFlow0_0(
ReadStepTypesOption mustBeNone, ParameterNode p, Node node, ReadStepTypesOption read
) {
// flow through: no prior read
exists(ArgumentNode arg |
parameterValueFlowArg(p, arg, TReadStepTypesNone()) and
parameterValueFlowArg(p, arg, mustBeNone) and
argumentValueFlowsThrough(arg, read, node)
)
or
// flow through: no read inside method
exists(ArgumentNode arg |
parameterValueFlowArg(p, arg, read) and
argumentValueFlowsThrough(arg, TReadStepTypesNone(), node)
argumentValueFlowsThrough(arg, mustBeNone, node)
)
}
@@ -292,11 +251,11 @@ private module Cached {
|
// normal flow through
read = TReadStepTypesNone() and
compatibleTypes(getErasedNodeTypeBound(arg), getErasedNodeTypeBound(out))
compatibleTypes(getNodeType(arg), getNodeType(out))
or
// getter
compatibleTypes(getErasedNodeTypeBound(arg), read.getContainerType()) and
compatibleTypes(read.getContentType(), getErasedNodeTypeBound(out))
compatibleTypes(getNodeType(arg), read.getContainerType()) and
compatibleTypes(read.getContentType(), getNodeType(out))
)
}
@@ -330,6 +289,67 @@ private module Cached {
import Final
}
import FlowThrough
cached
private module DispatchWithCallContext {
/**
* Holds if the call context `ctx` reduces the set of viable run-time
* dispatch targets of call `call` in `c`.
*/
cached
predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) {
exists(int tgts, int ctxtgts |
mayBenefitFromCallContext(call, c) and
c = viableCallable(ctx) and
ctxtgts = count(viableImplInCallContext(call, ctx)) and
tgts = strictcount(viableCallable(call)) and
ctxtgts < tgts
)
}
/**
* Gets a viable run-time dispatch target for the call `call` in the
* context `ctx`. This is restricted to those calls for which a context
* makes a difference.
*/
cached
DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
result = viableImplInCallContext(call, ctx) and
reducedViableImplInCallContext(call, _, ctx)
}
/**
* Holds if flow returning from callable `c` to call `call` might return
* further and if this path restricts the set of call sites that can be
* returned to.
*/
cached
predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) {
exists(int tgts, int ctxtgts |
mayBenefitFromCallContext(call, _) and
c = viableCallable(call) and
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContext(call, ctx)) and
tgts = strictcount(DataFlowCall ctx | viableCallable(ctx) = call.getEnclosingCallable()) and
ctxtgts < tgts
)
}
/**
* Gets a viable run-time dispatch target for the call `call` in the
* context `ctx`. This is restricted to those calls and results for which
* the return flow from the result to `call` restricts the possible context
* `ctx`.
*/
cached
DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) {
result = viableImplInCallContext(call, ctx) and
reducedViableImplInReturn(result, call)
}
}
import DispatchWithCallContext
/**
* Holds if `p` can flow to the pre-update node associated with post-update
* node `n`, in the same callable, using only value-preserving steps.
@@ -344,8 +364,8 @@ private module Cached {
) {
storeStep(node1, c, node2) and
readStep(_, c, _) and
contentType = getErasedNodeTypeBound(node1) and
containerType = getErasedNodeTypeBound(node2)
contentType = getNodeType(node1) and
containerType = getNodeType(node2)
or
exists(Node n1, Node n2 |
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
@@ -354,8 +374,8 @@ private module Cached {
argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1)
or
readStep(n2, c, n1) and
contentType = getErasedNodeTypeBound(n1) and
containerType = getErasedNodeTypeBound(n2)
contentType = getNodeType(n1) and
containerType = getNodeType(n2)
)
}
@@ -371,8 +391,6 @@ private module Cached {
store(node1, tc.getContent(), node2, contentType, tc.getContainerType())
}
import FlowThrough
/**
* Holds if the call context `call` either improves virtual dispatch in
* `callable` or if it allows us to prune unreachable nodes in `callable`.
@@ -448,8 +466,8 @@ private predicate readStepWithTypes(
Node n1, DataFlowType container, Content c, Node n2, DataFlowType content
) {
readStep(n1, c, n2) and
container = getErasedNodeTypeBound(n1) and
content = getErasedNodeTypeBound(n2)
container = getNodeType(n1) and
content = getNodeType(n2)
}
private newtype TReadStepTypesOption =
@@ -712,9 +730,6 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
result = viableCallable(call) and cc instanceof CallContextReturn
}
pragma[noinline]
DataFlowType getErasedNodeTypeBound(Node n) { result = getErasedRepr(n.getTypeBound()) }
predicate read = readStep/3;
/** An optional Boolean value. */

View File

@@ -37,21 +37,12 @@ module Consistency {
)
}
query predicate uniqueTypeBound(Node n, string msg) {
query predicate uniqueType(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
c = count(n.getTypeBound()) and
c = count(getNodeType(n)) and
c != 1 and
msg = "Node should have one type bound but has " + c + "."
)
}
query predicate uniqueTypeRepr(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
c = count(getErasedRepr(n.getTypeBound())) and
c != 1 and
msg = "Node should have one type representation but has " + c + "."
msg = "Node should have one type but has " + c + "."
)
}
@@ -104,7 +95,7 @@ module Consistency {
msg = "Local flow step does not preserve enclosing callable."
}
private DataFlowType typeRepr() { result = getErasedRepr(any(Node n).getTypeBound()) }
private DataFlowType typeRepr() { result = getNodeType(_) }
query predicate compatibleTypesReflexive(DataFlowType t, string msg) {
t = typeRepr() and

View File

@@ -209,7 +209,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.
*/
DataFlowType getErasedRepr(Type t) {
private DataFlowType getErasedRepr(Type t) {
exists(Type e | e = t.getErasure() |
if e instanceof NumericOrCharType
then result.(BoxedType).getPrimitiveType().getName() = "double"
@@ -222,6 +222,9 @@ DataFlowType getErasedRepr(Type t) {
t instanceof NullType and result instanceof TypeObject
}
pragma[noinline]
DataFlowType getNodeType(Node n) { result = getErasedRepr(n.getTypeBound()) }
/** Gets a string representation of a type returned by `getErasedRepr`. */
string ppReprType(Type t) {
if t.(BoxedType).getPrimitiveType().getName() = "double"

View File

@@ -400,6 +400,19 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
node2.asExpr().(ChooseExpr).getAResultExpr() = node1.asExpr()
or
node2.asExpr().(AssignExpr).getSource() = node1.asExpr()
or
exists(MethodAccess ma, Method m |
ma = node2.asExpr() and
m = ma.getMethod() and
m.getDeclaringType().hasQualifiedName("java.util", "Objects") and
(
m.hasName(["requireNonNull", "requireNonNullElseGet"]) and node1.asExpr() = ma.getArgument(0)
or
m.hasName("requireNonNullElse") and node1.asExpr() = ma.getAnArgument()
or
m.hasName("toString") and node1.asExpr() = ma.getArgument(1)
)
)
}
/**

View File

@@ -8,6 +8,8 @@ private import semmle.code.java.security.Validation
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.Guice
private import semmle.code.java.frameworks.Protobuf
private import semmle.code.java.frameworks.spring.SpringController
private import semmle.code.java.frameworks.spring.SpringHttp
private import semmle.code.java.Maps
private import semmle.code.java.dataflow.internal.ContainerFlow
private import semmle.code.java.frameworks.jackson.JacksonSerializability
@@ -252,6 +254,22 @@ private predicate constructorStep(Expr tracked, ConstructorCall sink) {
or
// a custom InputStream that wraps a tainted data source is tainted
inputStreamWrapper(sink.getConstructor(), argi)
or
// A SpringHttpEntity is a wrapper around a body and some headers
// Track flow through iff body is a String
exists(SpringHttpEntity she |
sink.getConstructor() = she.getAConstructor() and
argi = 0 and
tracked.getType() instanceof TypeString
)
or
// A SpringRequestEntity is a wrapper around a body and some headers
// Track flow through iff body is a String
exists(SpringResponseEntity sre |
sink.getConstructor() = sre.getAConstructor() and
argi = 0 and
tracked.getType() instanceof TypeString
)
)
}
@@ -292,6 +310,8 @@ private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
* Methods that return tainted data when called on tainted data.
*/
private predicate taintPreservingQualifierToMethod(Method m) {
m instanceof CloneMethod
or
m.getDeclaringType() instanceof TypeString and
(
m.getName() = "concat" or
@@ -358,6 +378,21 @@ private predicate taintPreservingQualifierToMethod(Method m) {
m = any(GuiceProvider gp).getAnOverridingGetMethod()
or
m = any(ProtobufMessageLite p).getAGetterMethod()
or
m instanceof GetterMethod and m.getDeclaringType() instanceof SpringUntrustedDataType
or
m.getDeclaringType() instanceof SpringHttpEntity and
m.getName().regexpMatch("getBody|getHeaders")
or
exists(SpringHttpHeaders headers | m = headers.getAMethod() |
m.getReturnType() instanceof TypeString
or
exists(ParameterizedType stringlist |
m.getReturnType().(RefType).getASupertype*() = stringlist and
stringlist.getSourceDeclaration().hasQualifiedName("java.util", "List") and
stringlist.getTypeArgument(0) instanceof TypeString
)
)
}
private class StringReplaceMethod extends Method {
@@ -383,9 +418,9 @@ private predicate unsafeEscape(MethodAccess ma) {
/** Access to a method that passes taint from an argument. */
private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
exists(Method m, int i |
m = sink.(MethodAccess).getMethod() and
m = sink.getMethod() and
taintPreservingArgumentToMethod(m, i) and
tracked = sink.(MethodAccess).getArgument(i)
tracked = sink.getArgument(i)
)
or
exists(MethodAccess ma |
@@ -393,6 +428,22 @@ private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
tracked = ma.getAnArgument() and
sink = ma
)
or
exists(Method springResponseEntityOfOk |
sink.getMethod() = springResponseEntityOfOk and
springResponseEntityOfOk.getDeclaringType() instanceof SpringResponseEntity and
springResponseEntityOfOk.getName().regexpMatch("ok|of") and
tracked = sink.getArgument(0) and
tracked.getType() instanceof TypeString
)
or
exists(Method springResponseEntityBody |
sink.getMethod() = springResponseEntityBody and
springResponseEntityBody.getDeclaringType() instanceof SpringResponseEntityBodyBuilder and
springResponseEntityBody.getName().regexpMatch("body") and
tracked = sink.getArgument(0) and
tracked.getType() instanceof TypeString
)
}
/**

View File

@@ -1,19 +1,3 @@
import java
/** A Spring framework annotation indicating remote user input from servlets. */
class SpringServletInputAnnotation extends Annotation {
SpringServletInputAnnotation() {
exists(AnnotationType a |
a = this.getType() and
a.getPackage().getName() = "org.springframework.web.bind.annotation"
|
a.hasName("MatrixVariable") or
a.hasName("RequestParam") or
a.hasName("RequestHeader") or
a.hasName("CookieValue") or
a.hasName("RequestPart") or
a.hasName("PathVariable") or
a.hasName("RequestBody")
)
}
}
import spring.SpringController
import spring.SpringWeb

View File

@@ -1,7 +1,10 @@
import java
import semmle.code.java.Maps
import SpringWeb
import SpringWebClient
/**
* An annotation type that identifies Spring components.
* An annotation type that identifies Spring controllers.
*/
class SpringControllerAnnotation extends AnnotationType {
SpringControllerAnnotation() {
@@ -13,6 +16,15 @@ class SpringControllerAnnotation extends AnnotationType {
}
}
/**
* An annotation type that identifies Spring rest controllers.
*
* Rest controllers are the same as controllers, but imply the `@ResponseBody` annotation.
*/
class SpringRestControllerAnnotation extends SpringControllerAnnotation {
SpringRestControllerAnnotation() { hasName("RestController") }
}
/**
* A class annotated, directly or indirectly, as a Spring `Controller`.
*/
@@ -20,6 +32,13 @@ class SpringController extends Class {
SpringController() { getAnAnnotation().getType() instanceof SpringControllerAnnotation }
}
/**
* A class annotated, directly or indirectly, as a Spring `RestController`.
*/
class SpringRestController extends SpringController {
SpringRestController() { getAnAnnotation().getType() instanceof SpringRestControllerAnnotation }
}
/**
* A method on a Spring controller which is accessed by the Spring MVC framework.
*/
@@ -38,7 +57,7 @@ class SpringModelAttributeMethod extends SpringControllerMethod {
// not declared with @Inherited.
exists(Method superMethod |
this.overrides*(superMethod) and
superMethod.hasAnnotation("org.springframework.web.bind.annotation", "ModelAttribute")
superMethod.getAnAnnotation() instanceof SpringModelAttributeAnnotation
)
}
}
@@ -58,19 +77,180 @@ class SpringInitBinderMethod extends SpringControllerMethod {
}
}
/**
* An `AnnotationType` that is used to indicate a `RequestMapping`.
*/
class SpringRequestMappingAnnotationType extends AnnotationType {
SpringRequestMappingAnnotationType() {
// `@RequestMapping` used directly as an annotation.
hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping")
or
// `@RequestMapping` can be used as a meta-annotation on other annotation types, e.g. GetMapping, PostMapping etc.
getAnAnnotation().getType() instanceof SpringRequestMappingAnnotationType
}
}
/**
* An `AnnotationType` that is used to indicate a `ResponseBody`.
*/
class SpringResponseBodyAnnotationType extends AnnotationType {
SpringResponseBodyAnnotationType() {
// `@ResponseBody` used directly as an annotation.
hasQualifiedName("org.springframework.web.bind.annotation", "ResponseBody")
}
}
/**
* A method on a Spring controller that is executed in response to a web request.
*/
class SpringRequestMappingMethod extends SpringControllerMethod {
Annotation requestMappingAnnotation;
SpringRequestMappingMethod() {
// Any method that declares the @RequestMapping annotation, or overrides a method that declares
// the annotation. We have to do this explicit check because the @RequestMapping annotation is
// not declared with @Inherited.
exists(Method superMethod |
this.overrides*(superMethod) and
superMethod.hasAnnotation("org.springframework.web.bind.annotation", "RequestMapping")
requestMappingAnnotation = superMethod.getAnAnnotation() and
requestMappingAnnotation.getType() instanceof SpringRequestMappingAnnotationType
)
}
/** Gets a request mapping parameter. */
SpringRequestMappingParameter getARequestParameter() { result = getAParameter() }
/** Gets the "produces" @RequestMapping annotation value, if present. */
string getProduces() {
result =
requestMappingAnnotation.getValue("produces").(CompileTimeConstantExpr).getStringValue()
}
/** Holds if this is considered an `@ResponseBody` method. */
predicate isResponseBody() {
getAnAnnotation().getType() instanceof SpringResponseBodyAnnotationType or
getDeclaringType().getAnAnnotation().getType() instanceof SpringResponseBodyAnnotationType or
getDeclaringType() instanceof SpringRestController
}
}
/** A Spring framework annotation indicating remote user input from servlets. */
class SpringServletInputAnnotation extends Annotation {
SpringServletInputAnnotation() {
exists(AnnotationType a |
a = this.getType() and
a.getPackage().getName() = "org.springframework.web.bind.annotation"
|
a.hasName("MatrixVariable") or
a.hasName("RequestParam") or
a.hasName("RequestHeader") or
a.hasName("CookieValue") or
a.hasName("RequestPart") or
a.hasName("PathVariable") or
a.hasName("RequestBody")
)
}
}
/** An annotation of the type `org.springframework.web.bind.annotation.ModelAttribute`. */
class SpringModelAttributeAnnotation extends Annotation {
SpringModelAttributeAnnotation() {
getType().hasQualifiedName("org.springframework.web.bind.annotation", "ModelAttribute")
}
}
/** A parameter of a `SpringRequestMappingMethod`. */
class SpringRequestMappingParameter extends Parameter {
SpringRequestMappingParameter() { getCallable() instanceof SpringRequestMappingMethod }
/** Holds if the parameter should not be consider a direct source of taint. */
predicate isNotDirectlyTaintedInput() {
getType().(RefType).getAnAncestor() instanceof SpringWebRequest or
getType().(RefType).getAnAncestor() instanceof SpringNativeWebRequest or
getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet", "ServletRequest") or
getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet", "ServletResponse") or
getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet.http", "HttpSession") or
getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet.http", "PushBuilder") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.security", "Principal") or
getType().(RefType).getAnAncestor().hasQualifiedName("org.springframework.http", "HttpMethod") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.util", "Locale") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.util", "TimeZone") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.time", "ZoneId") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "OutputStream") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "Writer") or
getType()
.(RefType)
.getAnAncestor()
.hasQualifiedName("org.springframework.web.servlet.mvc.support", "RedirectAttributes") or
// Also covers BindingResult. Note, you can access the field value through this interface, which should be considered tainted
getType().(RefType).getAnAncestor().hasQualifiedName("org.springframework.validation", "Errors") or
getType()
.(RefType)
.getAnAncestor()
.hasQualifiedName("org.springframework.web.bind.support", "SessionStatus") or
getType()
.(RefType)
.getAnAncestor()
.hasQualifiedName("org.springframework.web.util", "UriComponentsBuilder") or
getType()
.(RefType)
.getAnAncestor()
.hasQualifiedName("org.springframework.data.domain", "Pageable") or
this instanceof SpringModel
}
private predicate isExplicitlyTaintedInput() {
// InputStream or Reader parameters allow access to the body of a request
getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "InputStream") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "Reader") or
// The SpringServletInputAnnotations allow access to the URI, request parameters, cookie values and the body of the request
this.getAnAnnotation() instanceof SpringServletInputAnnotation or
// HttpEntity is like @RequestBody, but with a wrapper including the headers
// TODO model unwrapping aspects
getType().(RefType).getASourceSupertype*() instanceof SpringHttpEntity or
this
.getAnAnnotation()
.getType()
.hasQualifiedName("org.springframework.web.bind.annotation", "RequestAttribute") or
this
.getAnAnnotation()
.getType()
.hasQualifiedName("org.springframework.web.bind.annotation", "SessionAttribute")
}
private predicate isImplicitRequestParam() {
// Any parameter which is not explicitly handled, is consider to be an `@RequestParam`, if
// it is a simple bean property
not isNotDirectlyTaintedInput() and
not isExplicitlyTaintedInput() and
(
getType() instanceof PrimitiveType or
getType() instanceof TypeString
)
}
private predicate isImplicitModelAttribute() {
// Any parameter which is not explicitly handled, is consider to be an `@ModelAttribute`, if
// it is not an implicit request param
not isNotDirectlyTaintedInput() and
not isExplicitlyTaintedInput() and
not isImplicitRequestParam()
}
/** Holds if this is an explicit or implicit `@ModelAttribute` parameter. */
predicate isModelAttribute() {
isImplicitModelAttribute() or
getAnAnnotation() instanceof SpringModelAttributeAnnotation
}
/** Holds if the input is tainted. */
predicate isTaintedInput() {
isExplicitlyTaintedInput()
or
// Any parameter which is not explicitly identified, is consider to be an `@RequestParam`, if
// it is a simple bean property) or a @ModelAttribute if not
not isNotDirectlyTaintedInput()
}
}
/**
@@ -90,7 +270,7 @@ abstract class SpringModel extends Parameter {
* A `java.util.Map` can be accepted as the model parameter for a Spring `RequestMapping` method.
*/
class SpringModelPlainMap extends SpringModel {
SpringModelPlainMap() { getType().(RefType).hasQualifiedName("java.util", "Map") }
SpringModelPlainMap() { getType() instanceof MapType }
override RefType getATypeInModel() {
exists(MethodAccess methodCall |
@@ -133,3 +313,39 @@ class SpringModelResponseType extends RefType {
exists(SpringModel model | usesType(model.getATypeInModel(), this))
}
}
/** Strips wrapper types. */
private RefType stripType(Type t) {
result = t or
result = stripType(t.(Array).getComponentType()) or
result = stripType(t.(ParameterizedType).getATypeArgument())
}
/**
* A user data type that may be populated from an HTTP request.
*
* This includes types directly referred to as either `@ModelAttribute` or `@RequestBody` parameters,
* or types that are referred to by those types.
*/
class SpringUntrustedDataType extends RefType {
SpringUntrustedDataType() {
exists(SpringRequestMappingParameter p |
p.isModelAttribute()
or
p.getAnAnnotation().(SpringServletInputAnnotation).getType().hasName("RequestBody")
|
this.fromSource() and
this = stripType(p.getType())
)
or
exists(SpringRestTemplateResponseEntityMethod rm |
this = stripType(rm.getAReference().getType().(ParameterizedType).getTypeArgument(0)) and
this.fromSource()
)
or
exists(SpringUntrustedDataType mt |
this = stripType(mt.getAField().getType()) and
this.fromSource()
)
}
}

View File

@@ -0,0 +1,40 @@
/**
* Provides classes for working with Spring classes and interfaces from
* `org.springframework.http`.
*/
import java
/** The class `org.springframework.http.HttpEntity` or an instantiation of it. */
class SpringHttpEntity extends Class {
SpringHttpEntity() {
this.getSourceDeclaration().hasQualifiedName("org.springframework.http", "HttpEntity")
}
}
/** The class `org.springframework.http.RequestEntity` or an instantiation of it. */
class SpringRequestEntity extends Class {
SpringRequestEntity() {
this.getSourceDeclaration().hasQualifiedName("org.springframework.http", "RequestEntity")
}
}
/** The class `org.springframework.http.ResponseEntity` or an instantiation of it. */
class SpringResponseEntity extends Class {
SpringResponseEntity() {
this.getSourceDeclaration().hasQualifiedName("org.springframework.http", "ResponseEntity")
}
}
/** The nested class `BodyBuilder` in `org.springframework.http.ResponseEntity`. */
class SpringResponseEntityBodyBuilder extends Interface {
SpringResponseEntityBodyBuilder() {
this.getSourceDeclaration().getEnclosingType() instanceof SpringResponseEntity and
this.hasName("BodyBuilder")
}
}
/** The class `org.springframework.http.HttpHeaders`. */
class SpringHttpHeaders extends Class {
SpringHttpHeaders() { this.hasQualifiedName("org.springframework.http", "HttpHeaders") }
}

View File

@@ -0,0 +1,19 @@
/**
* Provides classes for working with Spring web requests.
*/
import java
/** An interface for web requests in the Spring framework. */
class SpringWebRequest extends Class {
SpringWebRequest() {
this.hasQualifiedName("org.springframework.web.context.request", "WebRequest")
}
}
/** An interface for web requests in the Spring framework. */
class SpringNativeWebRequest extends Class {
SpringNativeWebRequest() {
this.hasQualifiedName("org.springframework.web.context.request", "NativeWebRequest")
}
}

View File

@@ -0,0 +1,29 @@
/**
* Provides classes for working with Spring web clients.
*/
import java
import SpringHttp
/** The class `org.springframework.web.client.RestTemplate`. */
class SpringRestTemplate extends Class {
SpringRestTemplate() { this.hasQualifiedName("org.springframework.web.client", "RestTemplate") }
}
/**
* A method declared in `org.springframework.web.client.RestTemplate` that
* returns a `SpringResponseEntity`.
*/
class SpringRestTemplateResponseEntityMethod extends Method {
SpringRestTemplateResponseEntityMethod() {
this.getDeclaringType() instanceof SpringRestTemplate and
this.getReturnType() instanceof SpringResponseEntity
}
}
/** The interface `org.springframework.web.reactive.function.client.WebClient`. */
class SpringWebClient extends Interface {
SpringWebClient() {
this.hasQualifiedName("org.springframework.web.reactive.function.client", "WebClient")
}
}

View File

@@ -1,3 +1,7 @@
/**
* Provides predicates and classes relating to encryption in Java.
*/
import java
class SSLClass extends RefType {
@@ -85,8 +89,10 @@ private string algorithmRegex(string algorithmString) {
"((^|.*[A-Z]{2}|.*[^a-zA-Z])(" + algorithmString.toLowerCase() + ")([^a-z].*|$))"
}
/** Gets a blacklist of algorithms that are known to be insecure. */
private string algorithmBlacklist() {
/**
* Gets the name of an algorithm that is known to be insecure.
*/
string getAnInsecureAlgorithmName() {
result = "DES" or
result = "RC2" or
result = "RC4" or
@@ -94,32 +100,40 @@ private string algorithmBlacklist() {
result = "ARCFOUR" // a variant of RC4
}
// These are only bad if they're being used for encryption.
private string hashAlgorithmBlacklist() {
/**
* Gets the name of a hash algorithm that is insecure if it is being used for
* encryption.
*/
string getAnInsecureHashAlgorithmName() {
result = "SHA1" or
result = "MD5"
}
private string rankedAlgorithmBlacklist(int i) {
private string rankedInsecureAlgorithm(int i) {
// In this case we know these are being used for encryption, so we want to match
// weak hash algorithms too.
result = rank[i](string s | s = algorithmBlacklist() or s = hashAlgorithmBlacklist())
}
private string algorithmBlacklistString(int i) {
i = 1 and result = rankedAlgorithmBlacklist(i)
or
result = rankedAlgorithmBlacklist(i) + "|" + algorithmBlacklistString(i - 1)
}
/** Gets a regex for matching strings that look like they contain a blacklisted algorithm. */
string algorithmBlacklistRegex() {
result =
algorithmRegex(algorithmBlacklistString(max(int i | exists(rankedAlgorithmBlacklist(i)))))
rank[i](string s | s = getAnInsecureAlgorithmName() or s = getAnInsecureHashAlgorithmName())
}
/** Gets a whitelist of algorithms that are known to be secure. */
private string algorithmWhitelist() {
private string insecureAlgorithmString(int i) {
i = 1 and result = rankedInsecureAlgorithm(i)
or
result = rankedInsecureAlgorithm(i) + "|" + insecureAlgorithmString(i - 1)
}
/**
* Gets the regular expression used for matching strings that look like they
* contain an algorithm that is known to be insecure.
*/
string getInsecureAlgorithmRegex() {
result = algorithmRegex(insecureAlgorithmString(max(int i | exists(rankedInsecureAlgorithm(i)))))
}
/**
* Gets the name of an algorithm that is known to be secure.
*/
string getASecureAlgorithmName() {
result = "RSA" or
result = "SHA256" or
result = "SHA512" or
@@ -130,20 +144,50 @@ private string algorithmWhitelist() {
result = "ECIES"
}
private string rankedAlgorithmWhitelist(int i) { result = rank[i](algorithmWhitelist()) }
private string rankedSecureAlgorithm(int i) { result = rank[i](getASecureAlgorithmName()) }
private string algorithmWhitelistString(int i) {
i = 1 and result = rankedAlgorithmWhitelist(i)
private string secureAlgorithmString(int i) {
i = 1 and result = rankedSecureAlgorithm(i)
or
result = rankedAlgorithmWhitelist(i) + "|" + algorithmWhitelistString(i - 1)
result = rankedSecureAlgorithm(i) + "|" + secureAlgorithmString(i - 1)
}
/** Gets a regex for matching strings that look like they contain a whitelisted algorithm. */
string algorithmWhitelistRegex() {
result =
algorithmRegex(algorithmWhitelistString(max(int i | exists(rankedAlgorithmWhitelist(i)))))
/**
* Gets a regular expression for matching strings that look like they
* contain an algorithm that is known to be secure.
*/
string getSecureAlgorithmRegex() {
result = algorithmRegex(secureAlgorithmString(max(int i | exists(rankedSecureAlgorithm(i)))))
}
/**
* DEPRECATED: Terminology has been updated. Use `getAnInsecureAlgorithmName()`
* instead.
*/
deprecated string algorithmBlacklist() { result = getAnInsecureAlgorithmName() }
/**
* DEPRECATED: Terminology has been updated. Use
* `getAnInsecureHashAlgorithmName()` instead.
*/
deprecated string hashAlgorithmBlacklist() { result = getAnInsecureHashAlgorithmName() }
/**
* DEPRECATED: Terminology has been updated. Use `getInsecureAlgorithmRegex()` instead.
*/
deprecated string algorithmBlacklistRegex() { result = getInsecureAlgorithmRegex() }
/**
* DEPRECATED: Terminology has been updated. Use `getASecureAlgorithmName()`
* instead.
*/
deprecated string algorithmWhitelist() { result = getASecureAlgorithmName() }
/**
* DEPRECATED: Terminology has been updated. Use `getSecureAlgorithmRegex()` instead.
*/
deprecated string algorithmWhitelistRegex() { result = getSecureAlgorithmRegex() }
/**
* Any use of a cryptographic element that specifies an encryption
* algorithm. For example, methods returning ciphers, decryption methods,

View File

@@ -9,9 +9,9 @@ private predicate fileRead(VarAccess fileAccess, Expr fileReadingExpr) {
cie = fileReadingExpr and
cie.getArgument(0) = fileAccess
|
cie.getConstructedType().hasQualifiedName("java.io", "RandomAccessFile") or
cie.getConstructedType().hasQualifiedName("java.io", "FileReader") or
cie.getConstructedType().hasQualifiedName("java.io", "FileInputStream")
cie
.getConstructedType()
.hasQualifiedName("java.io", ["RandomAccessFile", "FileReader", "FileInputStream"])
)
or
exists(MethodAccess ma, Method filesMethod |
@@ -22,13 +22,9 @@ private predicate fileRead(VarAccess fileAccess, Expr fileReadingExpr) {
// represented by the first argument.
filesMethod.getDeclaringType().hasQualifiedName("java.nio.file", "Files") and
fileAccess = ma.getArgument(0) and
(
filesMethod.hasName("readAllBytes") or
filesMethod.hasName("readAllLines") or
filesMethod.hasName("newBufferedReader") or
filesMethod.hasName("newInputReader") or
filesMethod.hasName("newByteChannel")
)
filesMethod
.hasName(["readAllBytes", "readAllLines", "readString", "lines", "newBufferedReader",
"newInputStream", "newByteChannel"])
)
)
or

View File

@@ -0,0 +1,51 @@
/** Provides classes to reason about database query language injection vulnerabilities. */
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.frameworks.Jdbc
import semmle.code.java.frameworks.jOOQ
import semmle.code.java.frameworks.android.SQLite
import semmle.code.java.frameworks.javaee.Persistence
import semmle.code.java.frameworks.SpringJdbc
import semmle.code.java.frameworks.MyBatis
import semmle.code.java.frameworks.Hibernate
/** A sink for database query language injection vulnerabilities. */
abstract class QueryInjectionSink extends DataFlow::Node { }
/** A sink for SQL injection vulnerabilities. */
private class SqlInjectionSink extends QueryInjectionSink {
SqlInjectionSink() {
this.asExpr() instanceof SqlExpr
or
exists(MethodAccess ma, Method m, int index |
ma.getMethod() = m and
ma.getArgument(index) = this.asExpr()
|
index = m.(SQLiteRunner).sqlIndex()
or
m instanceof BatchUpdateVarargsMethod
or
index = 0 and jdbcSqlMethod(m)
or
index = 0 and mybatisSqlMethod(m)
or
index = 0 and hibernateSqlMethod(m)
or
index = 0 and jOOQSqlMethod(m)
)
}
}
/** A sink for Java Persistence Query Language injection vulnerabilities. */
private class PersistenceQueryInjectionSink extends QueryInjectionSink {
PersistenceQueryInjectionSink() {
// the query (first) argument to a `createQuery` or `createNativeQuery` method on `EntityManager`
exists(MethodAccess call, TypeEntityManager em | call.getArgument(0) = this.asExpr() |
call.getMethod() = em.getACreateQueryMethod() or
call.getMethod() = em.getACreateNativeQueryMethod()
// note: `createNamedQuery` is safe, as it takes only the query name,
// and named queries can only be constructed using constants as the query text
)
}
}

View File

@@ -1,12 +1,15 @@
import java
import semmle.code.java.frameworks.Servlets
import semmle.code.java.dataflow.DataFlow
/** Provides classes to reason about URL redirect attacks. */
/**
* A URL redirection sink.
*/
class UrlRedirectSink extends DataFlow::ExprNode {
UrlRedirectSink() {
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.frameworks.Servlets
/** A URL redirection sink */
abstract class UrlRedirectSink extends DataFlow::Node { }
/** A Servlet URL redirection sink. */
private class ServletUrlRedirectSink extends UrlRedirectSink {
ServletUrlRedirectSink() {
exists(MethodAccess ma |
ma.getMethod() instanceof HttpServletResponseSendRedirectMethod and
this.asExpr() = ma.getArgument(0)

View File

@@ -1,6 +1,8 @@
import java
import semmle.code.java.frameworks.Servlets
import semmle.code.java.frameworks.android.WebView
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.frameworks.spring.SpringHttp
import semmle.code.java.dataflow.TaintTracking
/*
@@ -30,6 +32,48 @@ class XssSink extends DataFlow::ExprNode {
m.getAReference().getArgument(1) = this.getExpr() and m.getName() = "loadDataWithBaseURL"
)
)
or
exists(SpringRequestMappingMethod requestMappingMethod, ReturnStmt rs |
requestMappingMethod = rs.getEnclosingCallable() and
this.asExpr() = rs.getResult() and
(
not exists(requestMappingMethod.getProduces()) or
requestMappingMethod.getProduces().matches("text/%")
)
|
// If a Spring request mapping method is either annotated with @ResponseBody (or equivalent),
// or returns a HttpEntity or sub-type, then the return value of the method is converted into
// a HTTP reponse using a HttpMessageConverter implementation. The implementation is chosen
// based on the return type of the method, and the Accept header of the request.
//
// By default, the only message converter which produces a response which is vulnerable to
// XSS is the StringHttpMessageConverter, which "Accept"s all text/* content types, including
// text/html. Therefore, if a browser request includes "text/html" in the "Accept" header,
// any String returned will be converted into a text/html response.
requestMappingMethod.isResponseBody() and
requestMappingMethod.getReturnType() instanceof TypeString
or
exists(Type returnType |
// A return type of HttpEntity<T> or ResponseEntity<T> represents an HTTP response with both
// a body and a set of headers. The body is subject to the same HttpMessageConverter
// process as above.
returnType = requestMappingMethod.getReturnType() and
(
returnType instanceof SpringHttpEntity
or
returnType instanceof SpringResponseEntity
)
|
// The type argument, representing the type of the body, is type String
returnType.(ParameterizedClass).getTypeArgument(0) instanceof TypeString
or
// Return type is a Raw class, which means no static type information on the body. In this
// case we will still treat this as an XSS sink, but rely on our taint flow steps for
// HttpEntity/ResponseEntity to only pass taint into those instances if the body type was
// String.
returnType instanceof RawClass
)
)
}
}