Merge pull request #6103 from atorralba/atorralba/promote-insecure-javamail

Java: Promote Insecure JavaMail SSL Configuration from experimental
This commit is contained in:
Anders Schack-Mulligen
2021-10-06 09:24:11 +02:00
committed by GitHub
22 changed files with 463 additions and 192 deletions

View File

@@ -0,0 +1,27 @@
/** Provides classes and predicates to work with email */
import java
/**
* The class `javax.mail.Session` or `jakarta.mail.Session`.
*/
class MailSession extends Class {
MailSession() { this.hasQualifiedName(["javax.mail", "jakarta.mail"], "Session") }
}
/**
* The method `getInstance` of the classes `javax.mail.Session` or `jakarta.mail.Session`.
*/
class MailSessionGetInstanceMethod extends Method {
MailSessionGetInstanceMethod() {
this.getDeclaringType() instanceof MailSession and
this.getName() = "getInstance"
}
}
/**
* A subtype of the class `org.apache.commons.mail.Email`.
*/
class ApacheEmail extends Class {
ApacheEmail() { this.getASupertype*().hasQualifiedName("org.apache.commons.mail", "Email") }
}

View File

@@ -0,0 +1,74 @@
/** Provides classes and predicates to reason about email vulnerabilities. */
import java
import semmle.code.java.frameworks.Mail
private import semmle.code.java.frameworks.Properties
/**
* The insecure way to set Java properties in mail sessions.
* 1. Set the `mail.smtp.auth` property to provide the SMTP Transport with a username and password when connecting to the SMTP server or
* set the `mail.smtp.ssl.socketFactory`/`mail.smtp.ssl.socketFactory.class` property to create an SMTP SSL socket.
* 2. No `mail.smtp.ssl.checkserveridentity` property is enabled.
*/
predicate isInsecureMailPropertyConfig(Variable properties) {
exists(MethodAccess ma |
ma.getMethod() instanceof SetPropertyMethod and
ma.getQualifier() = properties.getAnAccess()
|
getStringValue(ma.getArgument(0)).matches("%.auth%") and //mail.smtp.auth
getStringValue(ma.getArgument(1)) = "true"
or
getStringValue(ma.getArgument(0)).matches("%.socketFactory%") //mail.smtp.socketFactory or mail.smtp.socketFactory.class
) and
not exists(MethodAccess ma |
ma.getMethod() instanceof SetPropertyMethod and
ma.getQualifier() = properties.getAnAccess()
|
getStringValue(ma.getArgument(0)).matches("%.ssl.checkserveridentity%") and //mail.smtp.ssl.checkserveridentity
getStringValue(ma.getArgument(1)) = "true"
)
}
/**
* Holds if `ma` enables TLS/SSL with Apache Email.
*/
predicate enablesEmailSsl(MethodAccess ma) {
ma.getMethod().hasName(["setSSLOnConnect", "setStartTLSRequired"]) and
ma.getMethod().getDeclaringType() instanceof ApacheEmail and
ma.getArgument(0).(BooleanLiteral).getBooleanValue() = true
}
/**
* Holds if a SSL certificate check is enabled on an access of `apacheEmail` with Apache Email.
*/
predicate hasSslCertificateCheck(Variable apacheEmail) {
exists(MethodAccess ma |
ma.getQualifier() = apacheEmail.getAnAccess() and
ma.getMethod().hasName("setSSLCheckServerIdentity") and
ma.getMethod().getDeclaringType() instanceof ApacheEmail and
ma.getArgument(0).(BooleanLiteral).getBooleanValue() = true
)
}
/**
* Returns the string value of `expr` if it is a `CompileTimeConstantExpr`,
* or the string value of its operands if it is an `AddExpr`.
*/
private string getStringValue(Expr expr) {
result = expr.(CompileTimeConstantExpr).getStringValue()
or
result = getStringValue(expr.(AddExpr).getAnOperand())
}
/**
* A method to set Java properties, either using the `Properties` class
* or the `Dictionary` class.
*/
private class SetPropertyMethod extends Method {
SetPropertyMethod() {
this instanceof PropertiesSetPropertyMethod
or
this.hasName("put") and
this.getDeclaringType().getASourceSupertype*().hasQualifiedName("java.util", "Dictionary")
}
}

View File

@@ -0,0 +1,35 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>JavaMail is commonly used in Java applications to send emails. There are popular third-party libraries like Apache Commons Email which are built on JavaMail and facilitate integration. Authenticated mail sessions require user credentials and mail sessions can require SSL/TLS authentication. It is a common security vulnerability that host-specific certificate data is not validated or is incorrectly validated. Failing to validate the certificate makes the SSL session susceptible to a man-in-the-middle attack.</p>
<p>This query checks whether the SSL certificate is validated when credentials are used and SSL is enabled in email communications.</p>
<p>The query has code for both plain JavaMail invocation and mailing through Apache SimpleMail to make it more comprehensive.</p>
</overview>
<recommendation>
<p>Validate SSL certificate when sensitive information is sent in email communications.</p>
</recommendation>
<example>
<p>The following two examples show two ways of configuring secure emails through JavaMail or Apache SimpleMail. In the 'BAD' case,
credentials are sent in an SSL session without certificate validation. In the 'GOOD' case, the certificate is validated.</p>
<sample src="JavaMail.java" />
<sample src="SimpleMail.java" />
</example>
<references>
<li>
Jakarta Mail:
<a href="https://eclipse-ee4j.github.io/mail/docs/SSLNOTES.txt">SSL Notes</a>.
</li>
<li>
Apache Commons:
<a href="https://commons.apache.org/proper/commons-email/userguide.html#Security">Email security</a>.
</li>
<li>
Log4j2:
<a href="https://issues.apache.org/jira/browse/LOG4J2-2819">Add support for specifying an SSL configuration for SmtpAppender (CVE-2020-9488)</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,24 @@
/**
* @name Insecure JavaMail SSL Configuration
* @description Configuring a Java application to use authenticated mail session
* over SSL without certificate validation
* makes the session susceptible to a man-in-the-middle attack.
* @kind problem
* @problem.severity warning
* @security-severity 5.9
* @precision medium
* @id java/insecure-smtp-ssl
* @tags security
* external/cwe/cwe-297
*/
import java
import semmle.code.java.security.Mail
from MethodAccess ma
where
ma.getMethod() instanceof MailSessionGetInstanceMethod and
isInsecureMailPropertyConfig(ma.getArgument(0).(VarAccess).getVariable())
or
enablesEmailSsl(ma) and not hasSslCertificateCheck(ma.getQualifier().(VarAccess).getVariable())
select ma, "Java mailing has insecure SSL configuration"

View File

@@ -1,36 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>JavaMail is commonly used in Java applications to send emails. There are popular third-party libraries like Apache Commons Email which are built on JavaMail and facilitate integration. Authenticated mail sessions require user credentials and mail sessions can require SSL/TLS authentication. It is a common security vulnerability that host-specific certificate data is not validated or is incorrectly validated. Failing to validate the certificate makes the SSL session susceptible to a man-in-the-middle attack.</p>
<p>This query checks whether SSL certificate is validated when username/password is sent in authenticator and when SSL is enabled.</p>
<p>The query has code for both plain JavaMail invocation and mailing through Apache SimpleMail to make it more comprehensive.</p>
</overview>
<recommendation>
<p>Validate SSL certificate when sensitive information is sent in email communications.</p>
</recommendation>
<example>
<p>The following two examples show two ways of configuring secure emails through JavaMail or Apache SimpleMail. In the 'BAD' case,
credentials are sent in an SSL session without certificate validation. In the 'GOOD' case, the certificate is validated.</p>
<sample src="JavaMail.java" />
<sample src="SimpleMail.java" />
</example>
<references>
<li>
<a href="https://cwe.mitre.org/data/definitions/297.html">CWE-297</a>
</li>
<li>
Log4j2:
<a href="https://issues.apache.org/jira/browse/LOG4J2-2819">Add support for specifying an SSL configuration for SmtpAppender (CVE-2020-9488)</a>
</li>
<li>
SonarSource rule:
<a href="https://rules.sonarsource.com/java/tag/owasp/RSPEC-4499">SMTP SSL connection should check server identity</a>
</li>
</references>
</qhelp>

View File

@@ -1,107 +0,0 @@
/**
* @name Insecure JavaMail SSL Configuration
* @description Java application configured to use authenticated mail session
* over SSL does not validate the SSL certificate to properly
* ensure that it is actually associated with that host.
* @kind problem
* @problem.severity warning
* @precision medium
* @id java/insecure-smtp-ssl
* @tags security
* external/cwe/cwe-297
*/
import java
/**
* The method to set Java properties
*/
class SetPropertyMethod extends Method {
SetPropertyMethod() {
this.hasName("setProperty") and
this.getDeclaringType().hasQualifiedName("java.util", "Properties")
or
this.hasName("put") and
this.getDeclaringType().getASourceSupertype*().hasQualifiedName("java.util", "Dictionary")
}
}
/**
* The insecure way to set Java properties in mail sessions.
* 1. Set the mail.smtp.auth property to provide the SMTP Transport with a username and password when connecting to the SMTP server or
* set the mail.smtp.ssl.socketFactory/mail.smtp.ssl.socketFactory.class property to create an SMTP SSL socket.
* 2. No mail.smtp.ssl.checkserveridentity property is enabled.
*/
predicate isInsecureMailPropertyConfig(VarAccess propertiesVarAccess) {
exists(MethodAccess ma |
ma.getMethod() instanceof SetPropertyMethod and
ma.getQualifier() = propertiesVarAccess.getVariable().getAnAccess() and
(
getStringValue(ma.getArgument(0)).matches("%.auth%") and //mail.smtp.auth
getStringValue(ma.getArgument(1)) = "true"
or
getStringValue(ma.getArgument(0)).matches("%.socketFactory%") //mail.smtp.socketFactory or mail.smtp.socketFactory.class
)
) and
not exists(MethodAccess ma |
ma.getMethod() instanceof SetPropertyMethod and
ma.getQualifier() = propertiesVarAccess.getVariable().getAnAccess() and
(
getStringValue(ma.getArgument(0)).matches("%.ssl.checkserveridentity%") and //mail.smtp.ssl.checkserveridentity
getStringValue(ma.getArgument(1)) = "true"
)
)
}
/**
* Helper method to get string value of an argument
*/
string getStringValue(Expr expr) {
result = expr.(CompileTimeConstantExpr).getStringValue()
or
result = getStringValue(expr.(AddExpr).getLeftOperand())
or
result = getStringValue(expr.(AddExpr).getRightOperand())
}
/**
* The JavaMail session class `javax.mail.Session`
*/
class MailSession extends RefType {
MailSession() { this.hasQualifiedName("javax.mail", "Session") }
}
/**
* The class of Apache SimpleMail
*/
class SimpleMail extends RefType {
SimpleMail() { this.hasQualifiedName("org.apache.commons.mail", "SimpleEmail") }
}
/**
* Has TLS/SSL enabled with SimpleMail
*/
predicate enableTLSWithSimpleMail(MethodAccess ma) {
ma.getMethod().hasName("setSSLOnConnect") and
ma.getArgument(0).(BooleanLiteral).getBooleanValue() = true
}
/**
* Has no certificate check
*/
predicate hasNoCertCheckWithSimpleMail(VarAccess va) {
not exists(MethodAccess ma |
ma.getQualifier() = va.getVariable().getAnAccess() and
ma.getMethod().hasName("setSSLCheckServerIdentity") and
ma.getArgument(0).(BooleanLiteral).getBooleanValue() = true
)
}
from MethodAccess ma
where
ma.getMethod().getDeclaringType() instanceof MailSession and
ma.getMethod().getName() = "getInstance" and
isInsecureMailPropertyConfig(ma.getArgument(0))
or
enableTLSWithSimpleMail(ma) and hasNoCertCheckWithSimpleMail(ma.getQualifier())
select ma, "Java mailing has insecure SSL configuration"

View File

@@ -1,2 +0,0 @@
| InsecureJavaMail.java:29:27:29:72 | getInstance(...) | Java mailing has insecure SSL configuration |
| InsecureJavaMail.java:37:3:37:29 | setSSLOnConnect(...) | Java mailing has insecure SSL configuration |

View File

@@ -1,45 +0,0 @@
import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import org.apache.commons.mail.DefaultAuthenticator;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.SimpleEmail;
import java.util.Properties;
class InsecureJavaMail {
public void testJavaMail() {
final Properties properties = new Properties();
properties.put("mail.transport.protocol", "protocol");
properties.put("mail.smtp.host", "hostname");
properties.put("mail.smtp.socketFactory.class", "classname");
final javax.mail.Authenticator authenticator = new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("username", "password");
}
};
if (null != authenticator) {
properties.put("mail.smtp.auth", "true");
// properties.put("mail.smtp.ssl.checkserveridentity", "true");
}
final Session session = Session.getInstance(properties, authenticator);
}
public void testSimpleMail() throws Exception {
Email email = new SimpleEmail();
email.setHostName("config.hostName");
email.setSmtpPort(25);
email.setAuthenticator(new DefaultAuthenticator("config.username", "config.password"));
email.setSSLOnConnect(true);
// email.setSSLCheckServerIdentity(true);
email.setFrom("fromAddress");
email.setSubject("subject");
email.setMsg("body");
email.addTo("toAddress");
email.send();
}
}

View File

@@ -1 +0,0 @@
experimental/Security/CWE/CWE-297/InsecureJavaMail.ql

View File

@@ -1 +0,0 @@
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/apache-commons-email-1.6.0:${testdir}/../../../../stubs/javamail-api-1.6.2

View File

@@ -0,0 +1,42 @@
import java.util.Properties;
import jakarta.mail.Authenticator;
import jakarta.mail.PasswordAuthentication;
import jakarta.mail.Session;
class InsecureJakartaMailTest {
public void testJavaMail() {
final Properties properties = new Properties();
properties.put("mail.transport.protocol", "protocol");
properties.put("mail.smtp.host", "hostname");
properties.put("mail.smtp.socketFactory.class", "classname");
final jakarta.mail.Authenticator authenticator = new jakarta.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("username", "password");
}
};
if (null != authenticator) {
properties.put("mail.smtp.auth", "true");
}
final Session session = Session.getInstance(properties, authenticator); // $hasInsecureJavaMail
}
public void testSecureJavaMail() {
final Properties properties = new Properties();
properties.put("mail.transport.protocol", "protocol");
properties.put("mail.smtp.host", "hostname");
properties.put("mail.smtp.socketFactory.class", "classname");
final jakarta.mail.Authenticator authenticator = new jakarta.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("username", "password");
}
};
if (null != authenticator) {
properties.put("mail.smtp.auth", "true");
properties.put("mail.smtp.ssl.checkserveridentity", "true");
}
final Session session = Session.getInstance(properties, authenticator); // Safe
}
}

View File

@@ -0,0 +1,44 @@
import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
class InsecureJavaMailTest {
public void testJavaMail() {
final Properties properties = new Properties();
properties.put("mail.transport.protocol", "protocol");
properties.put("mail.smtp.host", "hostname");
properties.put("mail.smtp.socketFactory.class", "classname");
final javax.mail.Authenticator authenticator = new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("username", "password");
}
};
if (null != authenticator) {
properties.put("mail.smtp.auth", "true");
}
final Session session = Session.getInstance(properties, authenticator); // $hasInsecureJavaMail
}
public void testSecureJavaMail() {
final Properties properties = new Properties();
properties.put("mail.transport.protocol", "protocol");
properties.put("mail.smtp.host", "hostname");
properties.put("mail.smtp.socketFactory.class", "classname");
final javax.mail.Authenticator authenticator = new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("username", "password");
}
};
if (null != authenticator) {
properties.put("mail.smtp.auth", "true");
properties.put("mail.smtp.ssl.checkserveridentity", "true");
}
final Session session = Session.getInstance(properties, authenticator); // Safe
}
}

View File

@@ -0,0 +1,24 @@
import java
import semmle.code.java.security.Mail
import TestUtilities.InlineExpectationsTest
class InsecureJavaMailTest extends InlineExpectationsTest {
InsecureJavaMailTest() { this = "HasInsecureJavaMailTest" }
override string getARelevantTag() { result = "hasInsecureJavaMail" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasInsecureJavaMail" and
exists(MethodAccess ma |
ma.getLocation() = location and
element = ma.toString() and
value = ""
|
ma.getMethod() instanceof MailSessionGetInstanceMethod and
isInsecureMailPropertyConfig(ma.getArgument(0).(VarAccess).getVariable())
or
enablesEmailSsl(ma) and
not hasSslCertificateCheck(ma.getQualifier().(VarAccess).getVariable())
)
}
}

View File

@@ -0,0 +1,62 @@
import org.apache.commons.mail.DefaultAuthenticator;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.SimpleEmail;
public class InsecureSimpleEmailTest {
public void test() throws Exception {
// with setSSLOnConnect
{
Email email = new SimpleEmail();
email.setHostName("config.hostName");
email.setSmtpPort(25);
email.setAuthenticator(new DefaultAuthenticator("config.username", "config.password"));
email.setSSLOnConnect(true); // $hasInsecureJavaMail
email.setFrom("fromAddress");
email.setSubject("subject");
email.setMsg("body");
email.addTo("toAddress");
email.send();
}
// with setStartTLSRequired
{
Email email = new SimpleEmail();
email.setHostName("config.hostName");
email.setSmtpPort(25);
email.setAuthenticator(new DefaultAuthenticator("config.username", "config.password"));
email.setStartTLSRequired(true); // $hasInsecureJavaMail
email.setFrom("fromAddress");
email.setSubject("subject");
email.setMsg("body");
email.addTo("toAddress");
email.send();
}
// safe with setSSLOnConnect
{
Email email = new SimpleEmail();
email.setHostName("config.hostName");
email.setSmtpPort(25);
email.setAuthenticator(new DefaultAuthenticator("config.username", "config.password"));
email.setSSLOnConnect(true); // Safe
email.setSSLCheckServerIdentity(true);
email.setFrom("fromAddress");
email.setSubject("subject");
email.setMsg("body");
email.addTo("toAddress");
email.send();
}
// safe with setStartTLSRequired
{
Email email = new SimpleEmail();
email.setHostName("config.hostName");
email.setSmtpPort(25);
email.setAuthenticator(new DefaultAuthenticator("config.username", "config.password"));
email.setStartTLSRequired(true); // Safe
email.setSSLCheckServerIdentity(true);
email.setFrom("fromAddress");
email.setSubject("subject");
email.setMsg("body");
email.addTo("toAddress");
email.send();
}
}
}

View File

@@ -0,0 +1 @@
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/apache-commons-email-1.6.0:${testdir}/../../../stubs/javamail-api-1.6.2:${testdir}/../../../stubs/jakarta-mail-2.0.1

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
public abstract class Authenticator {
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License v. 2.0, which is available at http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary Licenses when the
* conditions for such availability set forth in the Eclipse Public License v. 2.0 are satisfied:
* GNU General Public License, version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
public final class PasswordAuthentication {
public PasswordAuthentication(String userName, String password) {}
public String getUserName() {
return null;
}
public String getPassword() {
return null;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License v. 2.0, which is available at http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary Licenses when the
* conditions for such availability set forth in the Eclipse Public License v. 2.0 are satisfied:
* GNU General Public License, version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import java.lang.reflect.*;
import java.io.*;
import java.net.*;
import java.security.*;
import java.util.Properties;
public final class Session {
public static Session getInstance(Properties props, Authenticator authenticator) {
return null;
}
public static Session getInstance(Properties props) {
return null;
}
public static synchronized Session getDefaultInstance(Properties props,
Authenticator authenticator) {
return null;
}
public static Session getDefaultInstance(Properties props) {
return null;
}
public synchronized void setDebug(boolean debug) {}
public synchronized boolean getDebug() {
return false;
}
public synchronized void setDebugOut(PrintStream out) {}
public synchronized PrintStream getDebugOut() {
return null;
}
public synchronized Provider[] getProviders() {
return null;
}
public synchronized Provider getProvider(String protocol) throws NoSuchProviderException {
return null;
}
public synchronized void setProvider(Provider provider) throws NoSuchProviderException {}
public PasswordAuthentication requestPasswordAuthentication(InetAddress addr, int port,
String protocol, String prompt, String defaultUserName) {
return null;
}
public Properties getProperties() {
return null;
}
public String getProperty(String name) {
return null;
}
public synchronized void addProvider(Provider provider) {}
public synchronized void setProtocolForAddress(String addresstype, String protocol) {}
}