diff --git a/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/checks/security/HardcodedCredentialsCheck.java b/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/checks/security/HardcodedCredentialsCheck.java index ae2ad6f19..5dc463057 100644 --- a/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/checks/security/HardcodedCredentialsCheck.java +++ b/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/checks/security/HardcodedCredentialsCheck.java @@ -47,6 +47,9 @@ public class HardcodedCredentialsCheck extends SimpleXPathBasedCheck { private static final XPathExpression WEB_CONFIG_CREDENTIALS_PATH = XPathBuilder .forExpression("/configuration/system.web/authentication[@mode=\"Forms\"]/forms/credentials[@passwordFormat=\"Clear\"]/user/@password[string-length(.) > 0]").build(); + private static final XPathExpression WEB_CONFIG_APP_SETTINGS_ADD_PATH = + XPathBuilder.forExpression("//appSettings/add").build(); + private static final Pattern VALID_CREDENTIAL_VALUES = Pattern.compile("[\\{$#]\\{"); private static final Pattern VALID_WEB_CONFIG_CREDENTIAL_VALUES = Pattern.compile("^__.*__$"); @@ -80,6 +83,9 @@ public void scanFile(XmlFile file) { evaluateAsList(WEB_CONFIG_CREDENTIALS_PATH, file.getDocument()).stream() .filter(passwordAttrNode -> !isValidWebConfigCredential(passwordAttrNode.getNodeValue())) .forEach(this::reportIssue); + evaluateAsList(WEB_CONFIG_APP_SETTINGS_ADD_PATH, file.getDocument()).stream() + .filter(this::isAddWithPassword) + .forEach(node -> reportIssue(node, "Review the hard-coded credential, which may be sensitive.")); } else { checkElements(file.getDocument()); checkSpecialCases(file); @@ -152,6 +158,18 @@ private static boolean isValidWebConfigCredential(String candidate) { return isValidCredential(candidate) || VALID_WEB_CONFIG_CREDENTIAL_VALUES.matcher(candidate).matches(); } + /** Detects nodes with 'key="password"' and 'value' attributes. */ + private boolean isAddWithPassword(Node node) { + NamedNodeMap attributes = node.getAttributes(); + Optional keyValueLowerCase = + Optional.ofNullable(attributes.getNamedItem("key")) + .map(Node::getNodeValue) + .map(String::toLowerCase); + + boolean keyIsCredentialWord = keyValueLowerCase.map(credentialWordsSet()::contains).orElse(false); + return keyIsCredentialWord && attributes.getNamedItem(VALUE) != null; + } + private void checkSpecialCases(XmlFile file) { specialCases.forEach(specialCase -> specialCase.accept(file)); } diff --git a/sonar-xml-plugin/src/test/java/org/sonar/plugins/xml/checks/security/HardcodedCredentialsCheckTest.java b/sonar-xml-plugin/src/test/java/org/sonar/plugins/xml/checks/security/HardcodedCredentialsCheckTest.java index b7bb4e138..322ee743d 100644 --- a/sonar-xml-plugin/src/test/java/org/sonar/plugins/xml/checks/security/HardcodedCredentialsCheckTest.java +++ b/sonar-xml-plugin/src/test/java/org/sonar/plugins/xml/checks/security/HardcodedCredentialsCheckTest.java @@ -75,4 +75,9 @@ void web_application() { SonarXmlCheckVerifier.verifyIssues(Paths.get("web-application", "web.config").toString(), CHECK); SonarXmlCheckVerifier.verifyIssues(Paths.get("web-application", "Machine.config").toString(), CHECK); } + + @Test + void web_application_app_settings() { + SonarXmlCheckVerifier.verifyIssues(Paths.get("app-settings", "web.config").toString(), CHECK); + } } diff --git a/sonar-xml-plugin/src/test/resources/checks/HardcodedCredentialsCheck/app-settings/web.config b/sonar-xml-plugin/src/test/resources/checks/HardcodedCredentialsCheck/app-settings/web.config new file mode 100644 index 000000000..d251a35d9 --- /dev/null +++ b/sonar-xml-plugin/src/test/resources/checks/HardcodedCredentialsCheck/app-settings/web.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + +