mirror of
https://github.com/github/codeql.git
synced 2026-04-27 09:45:15 +02:00
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:
@@ -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" }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}.*")
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
246
java/ql/src/experimental/Security/CWE/CWE-273/UnsafeCertTrust.ql
Normal file
246
java/ql/src/experimental/Security/CWE/CWE-273/UnsafeCertTrust.ql
Normal 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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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") }
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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, _, _) }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") }
|
||||
}
|
||||
19
java/ql/src/semmle/code/java/frameworks/spring/SpringWeb.qll
Normal file
19
java/ql/src/semmle/code/java/frameworks/spring/SpringWeb.qll
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
51
java/ql/src/semmle/code/java/security/QueryInjection.qll
Normal file
51
java/ql/src/semmle/code/java/security/QueryInjection.qll
Normal 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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user