All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.python.checks.CipherBlockChainingCheck Maven / Gradle / Ivy

There is a newer version: 4.23.0.17664
Show newest version
/*
 * SonarQube Python Plugin
 * Copyright (C) 2011-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 Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * 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 Sonar Source-Available License for more details.
 *
 * You should have received a copy of the Sonar Source-Available License
 * along with this program; if not, see https://sonarsource.com/license/ssal/
 */
package org.sonar.python.checks;

import java.util.HashSet;
import java.util.Set;
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.SubscriptionContext;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.symbols.Usage;
import org.sonar.plugins.python.api.tree.AssignmentStatement;
import org.sonar.plugins.python.api.tree.CallExpression;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.HasSymbol;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.QualifiedExpression;
import org.sonar.plugins.python.api.tree.RegularArgument;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.checks.utils.Expressions;
import org.sonar.python.tree.TreeUtils;

import static java.util.Arrays.asList;

@Rule(key = "S3329")
public class CipherBlockChainingCheck extends PythonSubscriptionCheck {

  private static final HashSet PYCRYPTO_SENSITIVE_FQNS = new HashSet<>();
  private static final String CRYPTOGRAPHY_SENSITIVE_FQN = "cryptography.hazmat.primitives.ciphers.Cipher";
  private static final String MESSAGE = "Use a dynamically-generated, random IV.";

  static {
    for (String libraryName : asList("Cryptodome", "Crypto")) {
      for (String vulnerableMethodName : asList("AES", "ARC2", "Blowfish", "CAST", "DES", "DES3")) {
        PYCRYPTO_SENSITIVE_FQNS.add(String.format("%s.Cipher.%s.new", libraryName, vulnerableMethodName));
      }
    }
  }

  @Override
  public void initialize(Context context) {
    context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, CipherBlockChainingCheck::checkCallExpression);
  }

  private static void checkCallExpression(SubscriptionContext ctx) {
    CallExpression callExpression = (CallExpression) ctx.syntaxNode();
    Symbol calleeSymbol = callExpression.calleeSymbol();
    if (calleeSymbol == null || calleeSymbol.fullyQualifiedName() == null) {
      return;
    }
    if (PYCRYPTO_SENSITIVE_FQNS.contains(calleeSymbol.fullyQualifiedName())) {
      checkPyCryptoCall(callExpression, ctx);
    }
    if (CRYPTOGRAPHY_SENSITIVE_FQN.equals(calleeSymbol.fullyQualifiedName())) {
      checkCrypographyCall(callExpression, ctx);
    }
  }

  private static void checkCrypographyCall(CallExpression callExpression, SubscriptionContext ctx) {
    RegularArgument modeArgument = TreeUtils.nthArgumentOrKeyword(1, "mode", callExpression.arguments());
    if (modeArgument == null) {
      return;
    }
    Expression modeArgumentExpression = modeArgument.expression();
    if (!modeArgumentExpression.is(Tree.Kind.CALL_EXPR)) {
      return;
    }
    CallExpression modeCallExpression = (CallExpression) modeArgumentExpression;
    Symbol calleeSymbol = modeCallExpression.calleeSymbol();
    if (calleeSymbol == null || !"CBC".equals(calleeSymbol.name())) {
      return;
    }
    RegularArgument initializationVector = TreeUtils.nthArgumentOrKeyword(0, "initialization_vector", modeCallExpression.arguments());
    if (initializationVector == null || !isStaticInitializationVector(initializationVector.expression(), new HashSet<>())) {
      return;
    }
    AssignmentStatement assignmentStatement = (AssignmentStatement) TreeUtils.firstAncestorOfKind(callExpression, Tree.Kind.ASSIGNMENT_STMT);
    if (assignmentStatement == null || assignmentStatement.assignedValue() != callExpression) {
      return;
    }
    checkAssignmentStatement(assignmentStatement, callExpression, "encryptor", ctx);
  }

  private static void checkPyCryptoCall(CallExpression callExpression, SubscriptionContext ctx) {
    RegularArgument modeArgument = TreeUtils.nthArgumentOrKeyword(1, "mode", callExpression.arguments());
    if (modeArgument == null) {
      return;
    }
    Expression modeArgumentExpression = modeArgument.expression();
    if (!(modeArgumentExpression instanceof HasSymbol)) {
      return;
    }
    Symbol symbol = ((HasSymbol) modeArgumentExpression).symbol();
    if (symbol == null || !"MODE_CBC".equals(symbol.name())) {
      return;
    }
    RegularArgument ivArgument = TreeUtils.nthArgumentOrKeyword(2, "iv", callExpression.arguments());
    if (ivArgument == null || !isStaticInitializationVector(ivArgument.expression(), new HashSet<>())) {
      return;
    }
    AssignmentStatement assignmentStatement = (AssignmentStatement) TreeUtils.firstAncestorOfKind(callExpression, Tree.Kind.ASSIGNMENT_STMT);
    if (assignmentStatement == null || assignmentStatement.assignedValue() != callExpression) {
      return;
    }
    checkAssignmentStatement(assignmentStatement, callExpression, "encrypt", ctx);
  }

  private static void checkAssignmentStatement(AssignmentStatement assignmentStatement, CallExpression callExpression, String suspiciousCallee, SubscriptionContext ctx) {
    assignmentStatement.lhsExpressions().stream()
      .filter(exprList -> exprList.expressions().size() == 1)
      .flatMap(exprList -> exprList.expressions().stream())
      .filter(expression -> expression.is(Tree.Kind.NAME))
      .forEach(name -> {
        Symbol symbol = ((Name) name).symbol();
        if (symbol == null) {
          return;
        }
        symbol.usages().stream()
          .map(Usage::tree)
          .filter(t -> isWithinCallTo(t, suspiciousCallee))
          .findFirst()
          .ifPresent(t -> ctx.addIssue(t, MESSAGE).secondary(callExpression, null));
      });
  }

  private static boolean isWithinCallTo(Tree tree, String calleeName) {
    CallExpression callExpression = (CallExpression) TreeUtils.firstAncestorOfKind(tree, Tree.Kind.CALL_EXPR);
    if (callExpression == null) {
      return false;
    }
    return callExpression.callee().is(Tree.Kind.QUALIFIED_EXPR) && ((QualifiedExpression) callExpression.callee()).name().name().equals(calleeName);
  }

  private static boolean isStaticInitializationVector(Expression expression, Set checkedExpressions) {
    if (checkedExpressions.contains(expression)) {
      return false;
    }
    checkedExpressions.add(expression);
    if (expression.is(Tree.Kind.CALL_EXPR) || TreeUtils.hasDescendant(expression, tree -> tree.is(Tree.Kind.CALL_EXPR))) {
      return false;
    }
    if (expression.is(Tree.Kind.NAME)) {
      Expression singleAssignedValue = Expressions.singleAssignedValue((Name) expression);
      if (singleAssignedValue == null) {
        return false;
      }
      return isStaticInitializationVector(singleAssignedValue, checkedExpressions);
    }
    return true;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy