diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bbac050588..b9f9ace36e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -sonar-commons = "2.14.0.3087" +sonar-commons = "2.15.0.3128" sonar-plugin-api = "10.13.0.2560" sonarqube = "10.7.0.96327" sonar-scanner-gradle = "5.1.0.4882" diff --git a/php-checks/src/main/java/org/sonar/php/checks/CheckList.java b/php-checks/src/main/java/org/sonar/php/checks/CheckList.java index 1c470bb03a..d72e63f699 100644 --- a/php-checks/src/main/java/org/sonar/php/checks/CheckList.java +++ b/php-checks/src/main/java/org/sonar/php/checks/CheckList.java @@ -200,6 +200,7 @@ public static List> getGeneralChecks() { HardCodedCredentialsInFunctionCallsCheck.class, HardCodedCredentialsInVariablesAndUrisCheck.class, HardCodedIpAddressCheck.class, + HardCodedSecretCheck.class, HardCodedUriCheck.class, HttpOnlyCheck.class, IdenticalOperandsInBinaryExpressionCheck.class, diff --git a/php-checks/src/main/java/org/sonar/php/checks/HardCodedIpAddressCheck.java b/php-checks/src/main/java/org/sonar/php/checks/HardCodedIpAddressCheck.java index 27d0458029..24c763a679 100644 --- a/php-checks/src/main/java/org/sonar/php/checks/HardCodedIpAddressCheck.java +++ b/php-checks/src/main/java/org/sonar/php/checks/HardCodedIpAddressCheck.java @@ -38,10 +38,10 @@ public class HardCodedIpAddressCheck extends PHPVisitorCheck { private static final Pattern LOOPBACK_IP = Pattern.compile(LOOPBACK_IPV4 + "|" + LOOPBACK_IPV6 + "|" + LOOPBACK_IPV4_MAPPED_TO_IPV6); private static final String PROTOCOL = "((\\w+:)?\\/\\/)?"; - private static final String IP_V4 = "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[1-9])(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}(?!\\d)"; + public static final String IP_V4 = "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[1-9])(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}(?!\\d)"; // @spotless:off - private static final String IP_V6 = "\\[?(" + + public static final String IP_V6 = "\\[?(" + "([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + // 1:2:3:4:5:6:7:8 "([0-9a-fA-F]{1,4}:){1,7}:|"+ // 1:: 1:2:3:4:5:6:7:: "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + // 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 diff --git a/php-checks/src/main/java/org/sonar/php/checks/HardCodedSecretCheck.java b/php-checks/src/main/java/org/sonar/php/checks/HardCodedSecretCheck.java new file mode 100644 index 0000000000..46358d7514 --- /dev/null +++ b/php-checks/src/main/java/org/sonar/php/checks/HardCodedSecretCheck.java @@ -0,0 +1,244 @@ +/* + * SonarQube PHP Plugin + * Copyright (C) 2010-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.php.checks; + +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import org.sonar.check.Rule; +import org.sonar.check.RuleProperty; +import org.sonar.php.checks.utils.CheckUtils; +import org.sonar.plugins.php.api.tree.Tree; +import org.sonar.plugins.php.api.tree.declaration.ParameterTree; +import org.sonar.plugins.php.api.tree.declaration.VariableDeclarationTree; +import org.sonar.plugins.php.api.tree.expression.ArrayPairTree; +import org.sonar.plugins.php.api.tree.expression.AssignmentExpressionTree; +import org.sonar.plugins.php.api.tree.expression.BinaryExpressionTree; +import org.sonar.plugins.php.api.tree.expression.ExpandableStringCharactersTree; +import org.sonar.plugins.php.api.tree.expression.FunctionCallTree; +import org.sonar.plugins.php.api.tree.expression.HeredocStringLiteralTree; +import org.sonar.plugins.php.api.tree.expression.LiteralTree; +import org.sonar.plugins.php.api.tree.expression.VariableIdentifierTree; +import org.sonar.plugins.php.api.visitors.PHPVisitorCheck; +import org.sonarsource.analyzer.commons.EntropyDetector; +import org.sonarsource.analyzer.commons.HumanLanguageDetector; + +import static org.sonar.php.checks.HardCodedIpAddressCheck.IP_V4; +import static org.sonar.php.checks.HardCodedIpAddressCheck.IP_V6; +import static org.sonar.php.checks.utils.CheckUtils.trimQuotes; + +@Rule(key = "S6418") +public class HardCodedSecretCheck extends PHPVisitorCheck { + private static final String DEFAULT_SECRET_WORDS = "api[_.-]?key,auth,credential,secret,token"; + private static final String DEFAULT_RANDOMNESS_SENSIBILITY = "5.0"; + private static final double LANGUAGE_SCORE_INCREMENT = 0.3; + private static final int MAX_RANDOMNESS_SENSIBILITY = 10; + private static final int MINIMUM_CREDENTIAL_LENGTH = 17; + + private static final String FIRST_ACCEPTED_CHARACTER = "[\\w.+/~$:&-]"; + private static final String FOLLOWING_ACCEPTED_CHARACTER = "[=\\w.+/~$:&-]"; + private static final Pattern SECRET_PATTERN = Pattern.compile(FIRST_ACCEPTED_CHARACTER + "(" + FOLLOWING_ACCEPTED_CHARACTER + "|\\\\\\\\" + FOLLOWING_ACCEPTED_CHARACTER + ")++"); + private static final Pattern IP_PATTERN = Pattern.compile("%s|%s".formatted(IP_V4, IP_V6)); + + @RuleProperty( + key = "secretWords", + description = "Comma separated list of words identifying potential secrets", + defaultValue = DEFAULT_SECRET_WORDS) + public String secretWords = DEFAULT_SECRET_WORDS; + + @RuleProperty( + key = "randomnessSensibility", + description = "Allows to tune the Randomness Sensibility (from 0 to 10)", + defaultValue = DEFAULT_RANDOMNESS_SENSIBILITY) + public double randomnessSensibility = Double.parseDouble(DEFAULT_RANDOMNESS_SENSIBILITY); + + private List variablePatterns; + private EntropyDetector entropyDetector; + private double maxLanguageScore; + + @Override + public void visitVariableDeclaration(VariableDeclarationTree tree) { + if (tree.initValue() instanceof LiteralTree literalTree) { + detectSecret(tree.identifier().text(), trimQuotes(literalTree.value()), literalTree); + } + if (tree.initValue() instanceof HeredocStringLiteralTree heredoc) { + for (ExpandableStringCharactersTree heredocLine : heredoc.strings()) { + detectSecret(tree.identifier().text(), heredocLine.value(), heredocLine); + } + } + super.visitVariableDeclaration(tree); + } + + @Override + public void visitFunctionCall(FunctionCallTree tree) { + var functionName = CheckUtils.getLowerCaseFunctionName(tree); + if ("define".equals(functionName)) { + visitDefineFunctionCall(tree); + } else if ("strcasecmp".equals(functionName) || "strcmp".equals(functionName)) { + visitStringCompareFunctionCall(tree); + } else if (tree.callArguments().size() == 2) { + visitTwoArgumentsFunctionCall(tree); + } + super.visitFunctionCall(tree); + } + + private void visitDefineFunctionCall(FunctionCallTree tree) { + CheckUtils.argumentValue(tree, "constant_name", 0) + .filter(constantName -> constantName.is(Tree.Kind.REGULAR_STRING_LITERAL)) + .map(LiteralTree.class::cast) + .ifPresent((LiteralTree constantName) -> CheckUtils.argumentValue(tree, "value", 1) + .filter(value -> value.is(Tree.Kind.REGULAR_STRING_LITERAL)) + .map(LiteralTree.class::cast) + .ifPresent(value -> detectSecret(trimQuotes(constantName.value()), trimQuotes(value.value()), value))); + } + + private void visitStringCompareFunctionCall(FunctionCallTree tree) { + var string1 = CheckUtils.resolvedArgumentLiteral(tree, "string1", 0); + var string2 = CheckUtils.resolvedArgumentLiteral(tree, "string2", 1); + if (string1.isPresent() && tree.callArguments().size() == 2) { + var callArg = tree.callArguments().get(1).value(); + if (callArg instanceof VariableIdentifierTree variableIdentifier) { + detectSecret(variableIdentifier.text(), string1.get().value(), string1.get()); + } + } + if (string2.isPresent() && tree.callArguments().size() == 2) { + var callArg = tree.callArguments().get(0).value(); + if (callArg instanceof VariableIdentifierTree variableIdentifier) { + detectSecret(variableIdentifier.text(), string2.get().value(), string2.get()); + } + } + } + + private void visitTwoArgumentsFunctionCall(FunctionCallTree tree) { + var firstArg = tree.callArguments().get(0).value(); + var secondArg = tree.callArguments().get(1).value(); + if (firstArg instanceof LiteralTree firstLiteralTree && secondArg instanceof LiteralTree secondLiteralTree) { + detectSecret(firstLiteralTree.value(), secondLiteralTree.value(), secondLiteralTree); + detectSecret(secondLiteralTree.value(), firstLiteralTree.value(), firstLiteralTree); + } + } + + @Override + public void visitAssignmentExpression(AssignmentExpressionTree tree) { + var variableIdentifier = tree.variable(); + if (variableIdentifier instanceof VariableIdentifierTree identifier) { + var valueTree = tree.value(); + if (valueTree instanceof LiteralTree literalTree) { + detectSecret(identifier.text(), literalTree.value(), literalTree); + } + } + super.visitAssignmentExpression(tree); + } + + @Override + public void visitParameter(ParameterTree tree) { + if (tree.initValue() instanceof LiteralTree valueTree) { + detectSecret(tree.variableIdentifier().text(), valueTree.value(), valueTree); + } + super.visitParameter(tree); + } + + @Override + public void visitArrayPair(ArrayPairTree tree) { + if (tree.key() instanceof LiteralTree keyTree && tree.value() instanceof LiteralTree valueTree) { + detectSecret(keyTree.value(), valueTree.value(), valueTree); + } + super.visitArrayPair(tree); + } + + @Override + public void visitBinaryExpression(BinaryExpressionTree tree) { + var leftOperand = tree.leftOperand(); + var rightOperand = tree.rightOperand(); + if (rightOperand instanceof LiteralTree secretValueTree && leftOperand instanceof VariableIdentifierTree variableTree) { + detectSecret(variableTree.text(), secretValueTree.value(), rightOperand); + } + if (leftOperand instanceof LiteralTree secretValueTree && rightOperand instanceof VariableIdentifierTree variableTree) { + detectSecret(variableTree.text(), secretValueTree.value(), leftOperand); + } + super.visitBinaryExpression(tree); + } + + private void detectSecret(String identifierName, String secretValue, Tree tree) { + var identifier = trimQuotes(identifierName); + var secret = trimQuotes(secretValue); + getSecretLikeName(identifier).ifPresent((String secretName) -> { + if (isSecret(secret)) { + newIssue(tree, "'%s' detected in this expression, review this potentially hard-coded secret.".formatted(secretName)); + } + }); + } + + private Optional getSecretLikeName(String identifierName) { + if (identifierName.isBlank()) { + return Optional.empty(); + } + return variableSecretPatterns() + .map(pattern -> pattern.matcher(identifierName)) + .filter(Matcher::find) + .map(matcher -> matcher.group(1)) + .findAny(); + } + + private Stream variableSecretPatterns() { + if (variablePatterns == null) { + variablePatterns = toPatterns(""); + } + return variablePatterns.stream(); + } + + private List toPatterns(String suffix) { + return Stream.of(secretWords.split(",")) + .map(String::trim) + .map(word -> Pattern.compile("(" + word + ")" + suffix, Pattern.CASE_INSENSITIVE)) + .toList(); + } + + private boolean isSecret(String literal) { + if (literal.length() < MINIMUM_CREDENTIAL_LENGTH || !SECRET_PATTERN.matcher(literal).matches()) { + return false; + } + return isRandom(literal) && isNotIpV6(literal); + } + + private boolean isRandom(String literal) { + return entropyDetector().hasEnoughEntropy(literal) && HumanLanguageDetector.humanLanguageScore(literal) < maxLanguageScore(); + } + + private static boolean isNotIpV6(String literal) { + return !IP_PATTERN.matcher(literal).matches(); + } + + private EntropyDetector entropyDetector() { + if (entropyDetector == null) { + entropyDetector = new EntropyDetector(randomnessSensibility); + } + return entropyDetector; + } + + private double maxLanguageScore() { + if (maxLanguageScore == 0.0) { + maxLanguageScore = (MAX_RANDOMNESS_SENSIBILITY - randomnessSensibility) * LANGUAGE_SCORE_INCREMENT; + } + return maxLanguageScore; + } +} diff --git a/php-checks/src/main/java/org/sonar/php/checks/utils/CheckUtils.java b/php-checks/src/main/java/org/sonar/php/checks/utils/CheckUtils.java index 0af0515571..f6b16cf4ec 100644 --- a/php-checks/src/main/java/org/sonar/php/checks/utils/CheckUtils.java +++ b/php-checks/src/main/java/org/sonar/php/checks/utils/CheckUtils.java @@ -32,11 +32,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; -import org.apache.commons.lang3.StringUtils; import org.sonar.php.symbols.ClassSymbol; import org.sonar.php.symbols.Symbols; import org.sonar.php.tree.TreeUtils; -import org.sonar.php.tree.impl.PHPTree; import org.sonar.php.tree.impl.VariableIdentifierTreeImpl; import org.sonar.php.tree.symbols.SymbolImpl; import org.sonar.php.utils.collections.MapBuilder; @@ -64,7 +62,6 @@ import org.sonar.plugins.php.api.tree.expression.ParenthesisedExpressionTree; import org.sonar.plugins.php.api.tree.expression.VariableIdentifierTree; import org.sonar.plugins.php.api.tree.lexical.SyntaxToken; -import org.sonar.plugins.php.api.tree.lexical.SyntaxTrivia; import org.sonar.plugins.php.api.tree.statement.ForStatementTree; import org.sonar.plugins.php.api.tree.statement.InlineHTMLTree; import org.sonar.plugins.php.api.visitors.PhpFile; @@ -207,21 +204,6 @@ public static String nameOf(Tree tree) { return null; } - /** - * Return whether the method is overriding a parent method or not. - * - * @param declaration METHOD_DECLARATION - * @return true if method has tag "@inheritdoc" in it's doc comment. - */ - public static boolean isOverriding(MethodDeclarationTree declaration) { - for (SyntaxTrivia comment : ((PHPTree) declaration).getFirstToken().trivias()) { - if (StringUtils.containsIgnoreCase(comment.text(), "@inheritdoc")) { - return true; - } - } - return false; - } - public static boolean isExitExpression(FunctionCallTree functionCallTree) { String callee = functionCallTree.callee().toString(); return "die".equalsIgnoreCase(callee) || "exit".equalsIgnoreCase(callee); @@ -379,8 +361,10 @@ public static Optional argument(FunctionCallTree call, String } public static Optional resolvedArgumentLiteral(FunctionCallTree call, String name, int position) { - return argumentValue(call, name, position).map(CheckUtils::assignedValue) - .filter(LiteralTree.class::isInstance).map(LiteralTree.class::cast); + return argumentValue(call, name, position) + .map(CheckUtils::assignedValue) + .filter(LiteralTree.class::isInstance) + .map(LiteralTree.class::cast); } public static Optional argumentValue(FunctionCallTree call, String name, int position) { diff --git a/php-checks/src/main/resources/org/sonar/l10n/php/rules/php/S6418.html b/php-checks/src/main/resources/org/sonar/l10n/php/rules/php/S6418.html new file mode 100644 index 0000000000..ece38fff5a --- /dev/null +++ b/php-checks/src/main/resources/org/sonar/l10n/php/rules/php/S6418.html @@ -0,0 +1,63 @@ +

Because it is easy to extract strings from an application source code or binary, secrets should not be hard-coded. This is particularly true for +applications that are distributed or that are open-source.

+

In the past, it has led to the following vulnerabilities:

+ +

Secrets should be stored outside of the source code in a configuration file or a management service for secrets.

+

This rule detects variables/fields having a name matching a list of words (secret, token, credential, auth, api[_.-]?key) being assigned a +pseudorandom hard-coded value. The pseudorandomness of the hard-coded value is based on its entropy and the probability to be human-readable. The +randomness sensibility can be adjusted if needed. Lower values will detect less random values, raising potentially more false positives.

+

Ask Yourself Whether

+
    +
  • The secret allows access to a sensitive component like a database, a file storage, an API, or a service.
  • +
  • The secret is used in a production environment.
  • +
  • Application re-distribution is required before updating the secret.
  • +
+

There would be a risk if you answered yes to any of those questions.

+

Recommended Secure Coding Practices

+
    +
  • Store the secret in a configuration file that is not pushed to the code repository.
  • +
  • Use your cloud provider’s service for managing secrets.
  • +
  • If a secret has been disclosed through the source code: revoke it and create a new one.
  • +
+

Sensitive Code Example

+
+$secret = '47828a8dd77ee1eb9dde2d5e93cb221ce8c32b37';
+MyClass->callMyService($secret);
+
+

Compliant Solution

+

Using AWS Secrets Manager:

+
+use Aws\SecretsManager\SecretsManagerClient;
+use Aws\Exception\AwsException;
+$client = new SecretsManagerClient(...);
+$secretName = 'example';
+doSomething($client, $secretName)
+function doSomething($client, $secretName) {
+    try {
+        $result = $client->getSecretValue([
+            'SecretId' => $secretName,
+        ]);
+    } catch (AwsException $e) {
+    ...
+    }
+    if (isset($result['SecretString'])) {
+        $secret = $result['SecretString'];
+    } else {
+        $secret = base64_decode($result['SecretBinary']);
+    }
+    // do something with the secret
+    MyClass->callMyService($secret);
+}
+
+

See

+ + diff --git a/php-checks/src/main/resources/org/sonar/l10n/php/rules/php/S6418.json b/php-checks/src/main/resources/org/sonar/l10n/php/rules/php/S6418.json new file mode 100644 index 0000000000..41d5aae9fc --- /dev/null +++ b/php-checks/src/main/resources/org/sonar/l10n/php/rules/php/S6418.json @@ -0,0 +1,45 @@ +{ + "title": "Hard-coded secrets are security-sensitive", + "type": "SECURITY_HOTSPOT", + "code": { + "impacts": { + "SECURITY": "HIGH" + }, + "attribute": "TRUSTWORTHY" + }, + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "30min" + }, + "tags": [ + "cwe" + ], + "defaultSeverity": "Blocker", + "ruleSpecification": "RSPEC-6418", + "sqKey": "S6418", + "scope": "Main", + "securityStandards": { + "CWE": [ + 798 + ], + "OWASP": [ + "A2" + ], + "OWASP Top 10 2021": [ + "A7" + ], + "PCI DSS 3.2": [ + "6.5.10" + ], + "PCI DSS 4.0": [ + "6.2.4" + ], + "ASVS 4.0": [ + "2.10.4", + "3.5.2", + "6.4.1" + ] + }, + "quickfix": "unknown" +} diff --git a/php-checks/src/main/resources/org/sonar/l10n/php/rules/php/Sonar_way_profile.json b/php-checks/src/main/resources/org/sonar/l10n/php/rules/php/Sonar_way_profile.json index 72298b5d6d..573019f040 100644 --- a/php-checks/src/main/resources/org/sonar/l10n/php/rules/php/Sonar_way_profile.json +++ b/php-checks/src/main/resources/org/sonar/l10n/php/rules/php/Sonar_way_profile.json @@ -176,6 +176,7 @@ "S6395", "S6396", "S6397", + "S6418", "S6437", "S6600" ] diff --git a/php-checks/src/test/java/org/sonar/php/checks/HardCodedSecretCheckTest.java b/php-checks/src/test/java/org/sonar/php/checks/HardCodedSecretCheckTest.java new file mode 100644 index 0000000000..8b54cb3745 --- /dev/null +++ b/php-checks/src/test/java/org/sonar/php/checks/HardCodedSecretCheckTest.java @@ -0,0 +1,50 @@ +/* + * SonarQube PHP Plugin + * Copyright (C) 2010-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.php.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.plugins.php.CheckVerifier; + +class HardCodedSecretCheckTest { + + @Test + void shouldVerifyDifferentASTElements() { + CheckVerifier.verify(new HardCodedSecretCheck(), "HardCodedSecretCheckAST.php"); + } + + @Test + void shouldVerifyDifferentSecrets() { + CheckVerifier.verify(new HardCodedSecretCheck(), "HardCodedSecretCheckSecrets.php"); + } + + @Test + void shouldVerifyCustomSecretWords() { + var check = new HardCodedSecretCheck(); + check.secretWords = "CUSTOM,app"; + CheckVerifier.verify(check, "HardCodedSecretCheckCustomWords.php"); + } + + @Test + void shouldVerifyCustomRandomnessSensibility() { + var check = new HardCodedSecretCheck(); + check.randomnessSensibility = 6.0; + CheckVerifier.verify(check, "HardCodedSecretCheckCustomRandomnessSensibility.php"); + } +} diff --git a/php-checks/src/test/resources/checks/HardCodedSecretCheckAST.php b/php-checks/src/test/resources/checks/HardCodedSecretCheckAST.php new file mode 100644 index 0000000000..7971ed0c04 --- /dev/null +++ b/php-checks/src/test/resources/checks/HardCodedSecretCheckAST.php @@ -0,0 +1,284 @@ + 'abcdefghijklmnopqrs', + 'secret' => 'abcdefghijklmnopqrs', // Noncompliant + "token" => "abcdefghijklmnopqrs" // Noncompliant +]; + +// Array +$array = array( + 'passed' => 'abcdefghijklmnopqrs', + 'auth' => 'abcdefghijklmnopqrs', // Noncompliant + "credential" => "abcdefghijklmnopqrs" // Noncompliant +); + +function compareStrings($auth) +{ + if ($auth == null) + { + return; + } + if ($auth == "") + { + return; + } + if ($auth == "X") + { + return; + } + if ($auth == "abcdefghijklmnopqrs") // Noncompliant + { + echo $auth; + } + if ("abcdefghijklmnopqrs" == $auth) // Noncompliant + { + echo $auth; + } + if ($auth === "abcdefghijklmnopqrs") // Noncompliant + { + echo $auth; + } + if ("abcdefghijklmnopqrs" === $auth) // Noncompliant + { + echo $auth; + } + if ($auth <=> "abcdefghijklmnopqrs") // Noncompliant + { + echo $auth; + } + if ("abcdefghijklmnopqrs" <=> $auth) // Noncompliant + { + echo $auth; + } + if (PASSED == $auth) // FN unable to resolve the value + { + echo $auth; + } + if ($auth == PASSED) // FN unable to resolve the value + { + echo $auth; + } + + // case sensitive + if (strcmp($auth, "abcdefghijklmnopqrs")) // Noncompliant + { + echo $auth; + } + if (strcmp("abcdefghijklmnopqrs", $auth)) // Noncompliant + { + echo $auth; + } + if (strcmp($auth, PASSED)) // FN unable to resolve the value + { + echo $auth; + } + if (strcmp(PASSED, $auth)) // FN unable to resolve the value + { + echo $auth; + } + if (strcmp(null, $auth)) + { + echo $auth; + } + if (strcmp("", $auth)) + { + echo $auth; + } + if (strcmp("X", $auth)) + { + echo $auth; + } + if (strcmp()) + { + echo $auth; + } + if (strcmp("abcdefghijklmnopqrs")) + { + echo $auth; + } + if (strcmp("abcdefghijklmnopqrs", $auth, "abc")) + { + echo $auth; + } + + // case insensitive + if (strcasecmp($auth, "abcdefghijklmnopqrs")) // Noncompliant + { + echo $auth; + } + if (strcasecmp("abcdefghijklmnopqrs", $auth)) // Noncompliant + { + echo $auth; + } + if (strcasecmp($auth, PASSED)) // FN unable to resolve the value + { + echo $auth; + } + if (strcasecmp(PASSED, $auth)) // FN unable to resolve the value + { + echo $auth; + } + if (strcasecmp(null, $auth)) + { + echo $auth; + } + if (strcasecmp("", $auth)) + { + echo $auth; + } + if (strcasecmp("X", $auth)) + { + echo $auth; + } + if (strcasecmp()) + { + echo $auth; + } + if (strcasecmp("abcdefghijklmnopqrs")) + { + echo $auth; + } + if (strcasecmp("abcdefghijklmnopqrs", $auth, "abc")) + { + echo $auth; + } +} + +function some2ArgsFunction($arg1, $arg2) +{ + // do nothing +} + +// When a function call has two arguments potentially containing String, we report an issue the same way we would with a variable declaration +function callSome2ArgsFunction() +{ + some2ArgsFunction("secret", "abcdefghijklmnopqrs"); // Noncompliant + some2ArgsFunction("abcdefghijklmnopqrs", "secret"); // Noncompliant + some2ArgsFunction('abcdefghijklmnopqrs', 'secret'); // Noncompliant + some2ArgsFunction("secret", PASSED); // FN unable to resolve the value + some2ArgsFunction(PASSED, "secret"); // FN unable to resolve the value + some2ArgsFunction("secret", "X"); + some2ArgsFunction("secret", ""); + some2ArgsFunction("secret", null); + some2ArgsFunction("secret", 42); + some2ArgsFunction("secret", 'auth'); + some2ArgsFunction("X", "secret"); + some2ArgsFunction("", "secret"); + some2ArgsFunction(null, "secret"); + some2ArgsFunction(42, "secret"); + some2ArgsFunction('auth', "secret"); +} + +function callSome2ArgsMethod() +{ + $box = new Box(); + $box->setProperty("secret", "abcdefghijklmnopqrs"); // Noncompliant + $box->setProperty("abcdefghijklmnopqrs", "secret"); // Noncompliant + $box->setProperty('abcdefghijklmnopqrs', 'secret'); // Noncompliant + $box->setProperty("secret", PASSED); // FN unable to resolve the value + $box->setProperty(PASSED, "secret"); // FN unable to resolve the value + $box->setProperty("secret", "X"); + $box->setProperty("secret", ""); + $box->setProperty("secret", null); + $box->setProperty("secret", 42); + $box->setProperty("secret", 'auth'); +} + +class Box +{ + function setProperty($arg1, $arg2) + { + // do nothing + } +} diff --git a/php-checks/src/test/resources/checks/HardCodedSecretCheckCustomRandomnessSensibility.php b/php-checks/src/test/resources/checks/HardCodedSecretCheckCustomRandomnessSensibility.php new file mode 100644 index 0000000000..8e3ae3d254 --- /dev/null +++ b/php-checks/src/test/resources/checks/HardCodedSecretCheckCustomRandomnessSensibility.php @@ -0,0 +1,32 @@ +