Merge pull request #5538 from luchua-bc/java/credentials-in-properties

Java: CWE-555 Query to detect plaintext credentials in Java properties files
This commit is contained in:
Chris Smowton
2021-04-12 15:22:21 +01:00
committed by GitHub
8 changed files with 229 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Credentials management issues occur when credentials are stored in plaintext in
an application's properties file. Common credentials include but are not limited
to LDAP, mail, database, proxy account, and so on. Storing plaintext credentials
in a properties file allows anyone who can read the file access to the protected
resource. Good credentials management guidelines require that credentials never
be stored in plaintext.
</p>
</overview>
<recommendation>
<p>
Credentials stored in properties files should be encrypted and recycled regularly.
In a Java EE deployment scenario, utilities provided by application servers like
keystores and password vaults can be used to encrypt and manage credentials.
</p>
</recommendation>
<example>
<p>
In the first example, the credentials for the LDAP and datasource properties are stored
in cleartext in the properties file.
</p>
<p>
In the second example, the credentials for the LDAP and datasource properties are stored
in an encrypted format.
</p>
<sample src="configuration.properties" />
</example>
<references>
<li>
OWASP:
<a href="https://owasp.org/www-community/vulnerabilities/Password_Plaintext_Storage">Password Plaintext Storage</a>
</li>
<li>
Medium (Rajeev Shukla):
<a href="https://medium.com/@mail2rajeevshukla/hiding-encrypting-database-password-in-the-application-properties-34d59fe104eb">Encrypting database password in the application.properties file</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,34 @@
/**
* @name Cleartext Credentials in Properties File
* @description Finds cleartext credentials in Java properties files.
* @kind problem
* @id java/credentials-in-properties
* @tags security
* external/cwe/cwe-555
* external/cwe/cwe-256
* external/cwe/cwe-260
*/
/*
* Note this query requires properties files to be indexed before it can produce results.
* If creating your own database with the CodeQL CLI, you should run
* `codeql database index-files --language=properties ...`
* If using lgtm.com, you should add `properties_files: true` to the index block of your
* lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction#customizing-index)
*/
import java
import experimental.semmle.code.java.frameworks.CredentialsInPropertiesFile
/**
* Holds if the credentials are in a non-production properties file indicated by:
* a) in a non-production directory
* b) with a non-production file name
*/
predicate isNonProdCredentials(CredentialsConfig cc) {
cc.getFile().getAbsolutePath().matches(["%dev%", "%test%", "%sample%"])
}
from CredentialsConfig cc
where not isNonProdCredentials(cc)
select cc, cc.getConfigDesc()

View File

@@ -0,0 +1,26 @@
#***************************** LDAP Credentials *****************************************#
ldap.ldapHost = ldap.example.com
ldap.ldapPort = 636
ldap.loginDN = cn=Directory Manager
#### BAD: LDAP credentials are stored in cleartext ####
ldap.password = mysecpass
#### GOOD: LDAP credentials are stored in the encrypted format ####
ldap.password = eFRZ3Cqo5zDJWMYLiaEupw==
ldap.domain1 = example
ldap.domain2 = com
ldap.url= ldaps://ldap.example.com:636/dc=example,dc=com
#*************************** MS SQL Database Connection **********************************#
datasource1.driverClassName = com.microsoft.sqlserver.jdbc.SQLServerDriver
datasource1.url = jdbc:sqlserver://ms.example.com\\exampledb:1433;
datasource1.username = sa
#### BAD: Datasource credentials are stored in cleartext ####
datasource1.password = Passw0rd@123
#### GOOD: Datasource credentials are stored in the encrypted format ####
datasource1.password = VvOgflYS1EUzJdVNDoBcnA==

View File

@@ -0,0 +1,63 @@
/**
* Provides classes for analyzing properties files.
*/
import java
import semmle.code.configfiles.ConfigFiles
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.Properties
private string possibleSecretName() {
result =
[
"%password%", "%passwd%", "%account%", "%accnt%", "%credential%", "%token%", "%secret%",
"%access%key%"
]
}
private string possibleEncryptedSecretName() { result = ["%hashed%", "%encrypted%", "%crypt%"] }
/** Holds if the value is not cleartext credentials. */
bindingset[value]
predicate isNotCleartextCredentials(string value) {
value = "" // Empty string
or
value.length() < 7 // Typical credentials are no less than 6 characters
or
value.matches("% %") // Sentences containing spaces
or
value.regexpMatch(".*[^a-zA-Z\\d]{3,}.*") // Contain repeated non-alphanumeric characters such as a fake password pass**** or ????
or
value.matches("@%") // Starts with the "@" sign
or
value.regexpMatch("\\$\\{.*\\}") // Variable placeholder ${credentials}
or
value.matches("%=") // A basic check of encrypted credentials ending with padding characters
or
value.matches("ENC(%)") // Encrypted value
or
// Could be a message property for UI display or fake passwords, e.g. login.password_expired=Your current password has expired.
value.toLowerCase().matches(possibleSecretName())
}
/** A configuration property that appears to contain a cleartext secret. */
class CredentialsConfig extends ConfigPair {
CredentialsConfig() {
this.getNameElement().getName().trim().toLowerCase().matches(possibleSecretName()) and
not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName()) and
not isNotCleartextCredentials(this.getValueElement().getValue().trim())
}
/** Gets the whitespace-trimmed name of this property. */
string getName() { result = this.getNameElement().getName().trim() }
/** Gets the whitespace-trimmed value of this property. */
string getValue() { result = this.getValueElement().getValue().trim() }
/** Returns a description of this vulnerability. */
string getConfigDesc() {
result =
"Plaintext credentials " + this.getName() + " have cleartext value " + this.getValue() +
" in properties file"
}
}

View File

@@ -0,0 +1,5 @@
| configuration.properties:6:1:6:25 | ldap.password=mysecpass | Plaintext credentials ldap.password have cleartext value mysecpass in properties file |
| configuration.properties:18:1:18:35 | datasource1.password=Passw0rd@123 | Plaintext credentials datasource1.password have cleartext value Passw0rd@123 in properties file |
| configuration.properties:25:1:25:31 | mail.password=MysecPWxWa@1993 | Plaintext credentials mail.password have cleartext value MysecPWxWa@1993 in properties file |
| configuration.properties:33:1:33:50 | com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA | Plaintext credentials com.example.aws.s3.access_key have cleartext value AKMAMQPBYMCD6YSAYCBA in properties file |
| configuration.properties:34:1:34:70 | com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k | Plaintext credentials com.example.aws.s3.secret_key have cleartext value 8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k in properties file |

View File

@@ -0,0 +1,10 @@
/*
* Note this is similar to src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
* except we do not filter out test files.
*/
import java
import experimental.semmle.code.java.frameworks.CredentialsInPropertiesFile
from CredentialsConfig cc
select cc, cc.getConfigDesc()

View File

@@ -0,0 +1,37 @@
#***************************** LDAP Credentials *****************************************#
ldap.ldapHost = ldap.example.com
ldap.ldapPort = 636
ldap.loginDN = cn=Directory Manager
#### BAD: LDAP credentials are stored in cleartext ####
ldap.password = mysecpass
#### GOOD: LDAP credentials are stored in the encrypted format ####
ldap.password = eFRZ3Cqo5zDJWMYLiaEupw==
ldap.domain1 = example
ldap.domain2 = com
ldap.url= ldaps://ldap.example.com:636/dc=example,dc=com
#*************************** MS SQL Database Connection **********************************#
datasource1.driverClassName = com.microsoft.sqlserver.jdbc.SQLServerDriver
datasource1.url = jdbc:sqlserver://ms.example.com\\exampledb:1433;
datasource1.username = sa
#### BAD: Datasource credentials are stored in cleartext ####
datasource1.password = Passw0rd@123
#### GOOD: Datasource credentials are stored in the encrypted format ####
datasource1.password = VvOgflYS1EUzJdVNDoBcnA==
#*************************** Mail Connection **********************************#
mail.username = test@example.com
#### BAD: Mail credentials are stored in cleartext ####
mail.password = MysecPWxWa@1993
#### GOOD: Mail credentials are stored in the encrypted format ####
mail.password = M*********@1993
#*************************** AWS S3 Connection **********************************#
com.example.aws.s3.bucket_name=com-bucket-1
com.example.aws.s3.directory_name=com-directory-1
#### BAD: Access keys are stored in properties file in cleartext ####
com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA
com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k
#### GOOD: Access keys are not stored in properties file ####
com.example.aws.s3.access_key=${ENV:AWS_ACCESS_KEY_ID}
com.example.aws.s3.secret_key=${ENV:AWS_SECRET_ACCESS_KEY}

View File

@@ -0,0 +1,9 @@
# GOOD: UI display messages; not credentials
prompt.username=Username
prompt.password=Password
forgot_password.error=Please enter a valid email address.
reset_password.error=Passwords must match and not be empty.
login.password_expired=Your current password has expired. Please reset your password.
login.login_failure=Unable to verify username or password. Please try again.