1616 */
1717package org .sonar .python .checks ;
1818
19- import java .util .ArrayList ;
2019import java .util .Arrays ;
20+ import java .util .Collection ;
2121import java .util .Collections ;
2222import java .util .Comparator ;
2323import java .util .HashSet ;
3838import org .sonar .plugins .python .api .tree .CallExpression ;
3939import org .sonar .plugins .python .api .tree .DictionaryLiteral ;
4040import org .sonar .plugins .python .api .tree .Expression ;
41+ import org .sonar .plugins .python .api .tree .ExpressionList ;
4142import org .sonar .plugins .python .api .tree .HasSymbol ;
4243import org .sonar .plugins .python .api .tree .KeyValuePair ;
4344import org .sonar .plugins .python .api .tree .Name ;
@@ -393,28 +394,36 @@ private static boolean isFalsyRange(CallExpression callExpr, String fqn) {
393394
394395 private static void standardSslCheckForAssignmentStatement (SubscriptionContext subscriptionContext ) {
395396 AssignmentStatement asgnStmt = (AssignmentStatement ) subscriptionContext .syntaxNode ();
397+ TreeUtils .toOptionalInstanceOf (CallExpression .class , asgnStmt .assignedValue ())
398+ .flatMap (VerifiedSslTlsCertificateCheck ::isVulnerableMethodCall )
399+ .ifPresent (vulnTok -> checkVulnerableMethodCallAssignment (subscriptionContext , asgnStmt , vulnTok ));
400+ }
396401
397- Optional <VulnerabilityAndProblematicToken > vulnTokOpt = isVulnerableMethodCall (asgnStmt .assignedValue ());
398- vulnTokOpt .ifPresent (vulnTok -> asgnStmt
399- .lhsExpressions ()
402+ private static void checkVulnerableMethodCallAssignment (SubscriptionContext subscriptionContext , AssignmentStatement asgnStmt , VulnerabilityAndProblematicToken vulnTok ) {
403+ asgnStmt .lhsExpressions ()
400404 .stream ()
401- .flatMap (it -> it .expressions ().stream ())
405+ .map (ExpressionList ::expressions )
406+ .flatMap (Collection ::stream )
402407 .findFirst ()
403- .filter (Name .class ::isInstance )
404- .map (expr -> ((Name ) expr ).symbol ())
405- .ifPresent (symb -> {
406- for (Usage u : selectRelevantModifyingUsages (symb .usages (), vulnTok .token .line ())) {
408+ .flatMap (TreeUtils ::getSymbolFromTree )
409+ .map (Symbol ::usages )
410+ .map (usages -> selectRelevantModifyingUsages (usages , vulnTok .token .line ()))
411+ .filter (usages -> usages .stream ().noneMatch (VerifiedSslTlsCertificateCheck ::isCheckHostnameAttributeSetToTrue ))
412+ .ifPresent (usages -> {
413+ for (Usage u : usages ) {
407414 searchForVerifyModeOverride (u ).ifPresent (vulnTok ::overrideBy );
408415 }
409416 if (vulnTok .isVulnerable ) {
410417 subscriptionContext .addIssue (vulnTok .token , MESSAGE );
411418 }
412- })) ;
419+ });
413420 }
414421
415422 private static void standardSslCheckForRegularArgument (SubscriptionContext subscriptionContext ) {
416- var argument = (RegularArgument ) subscriptionContext .syntaxNode ();
417- isVulnerableMethodCall (argument .expression ())
423+ TreeUtils .toOptionalInstanceOf (RegularArgument .class , subscriptionContext .syntaxNode ())
424+ .map (RegularArgument ::expression )
425+ .flatMap (TreeUtils .toOptionalInstanceOfMapper (CallExpression .class ))
426+ .flatMap (VerifiedSslTlsCertificateCheck ::isVulnerableMethodCall )
418427 .ifPresent (vulnTok -> subscriptionContext .addIssue (vulnTok .token , MESSAGE ));
419428 }
420429
@@ -442,24 +451,45 @@ private static int findNextAssignmentLine(List<Usage> usages, int firstAssignmen
442451 * should suffice here.
443452 */
444453 private static List <Usage > selectRelevantModifyingUsages (List <Usage > usages , int firstAssignmentLine ) {
445- int nextAssignmentLine = findNextAssignmentLine (usages , firstAssignmentLine );
446- ArrayList <Usage > result = new ArrayList <Usage >();
447- usages .stream ().filter (u -> {
448- int line = u .tree ().firstToken ().line ();
449- return !u .isBindingUsage () && line > firstAssignmentLine && line < nextAssignmentLine ;
450- }).forEach (u -> result .add (u ));
451- result .sort (Comparator .comparing (u -> u .tree ().firstToken ().line ()));
452- return result ;
454+ var nextAssignmentLine = findNextAssignmentLine (usages , firstAssignmentLine );
455+ return usages .stream ()
456+ .filter (Predicate .not (Usage ::isBindingUsage ))
457+ .filter (u -> {
458+ var line = u .tree ().firstToken ().line ();
459+ return line > firstAssignmentLine && line < nextAssignmentLine ;
460+ })
461+ .sorted (Comparator .comparing (u -> u .tree ().firstToken ().line ()))
462+ .toList ();
453463 }
454464
455465 /**
456466 * Map from FQNs of sensitive context factories to the boolean that determines whether default settings are dangerous.
457467 */
458- private static final Map <String , Boolean > VULNERABLE_CONTEXT_FACTORIES = Map .of (
459- "ssl._create_unverified_context" , true ,
460- "ssl._create_stdlib_context" , true ,
461- "ssl.create_default_context" , false ,
462- "ssl._create_default_https_context" , false );
468+ private static final Map <String , Predicate <CallExpression >> VULNERABLE_CONTEXT_FACTORIES = Map .of (
469+ "ssl._create_unverified_context" , c -> true ,
470+ "ssl._create_stdlib_context" , c -> true ,
471+ "ssl.create_default_context" , c -> false ,
472+ "ssl._create_default_https_context" , c -> false ,
473+ "ssl.SSLContext" , VerifiedSslTlsCertificateCheck ::isVulnerableSslContextInstantiation
474+ );
475+
476+ private static final Set <String > VULNERABLE_SSL_PROTOCOLS = Set .of (
477+ "ssl.PROTOCOL_SSLv23" ,
478+ "ssl.PROTOCOL_TLS" ,
479+ "ssl.PROTOCOL_SSLv3" ,
480+ "ssl.PROTOCOL_TLSv1" ,
481+ "ssl.PROTOCOL_TLSv1_1" ,
482+ "ssl.PROTOCOL_TLSv1_2"
483+ );
484+
485+ private static boolean isVulnerableSslContextInstantiation (CallExpression sslConstructorCallExpression ) {
486+ return TreeUtils .nthArgumentOrKeywordOptional (0 , "protocol" , sslConstructorCallExpression .arguments ())
487+ .map (RegularArgument ::expression )
488+ .flatMap (TreeUtils ::getSymbolFromTree )
489+ .map (Symbol ::fullyQualifiedName )
490+ .filter (VULNERABLE_SSL_PROTOCOLS ::contains )
491+ .isPresent ();
492+ }
463493
464494 /**
465495 * Pair and a mutable cell for combining all updates to <code>verify_mode</code>.
@@ -490,21 +520,15 @@ void overrideBy(VulnerabilityAndProblematicToken overridingAssignment) {
490520 * if found, returns the token of the callee, together with the boolean that indicates whether the default settings
491521 * are dangerous.
492522 */
493- private static Optional <VulnerabilityAndProblematicToken > isVulnerableMethodCall (Expression expr ) {
494- if (expr instanceof CallExpression callExpression ) {
495- Symbol calleeSymbol = callExpression .calleeSymbol ();
496- if (calleeSymbol != null ) {
497- String fqn = calleeSymbol .fullyQualifiedName ();
498- if (fqn != null && VULNERABLE_CONTEXT_FACTORIES .containsKey (fqn )) {
499- boolean isVulnerable = VULNERABLE_CONTEXT_FACTORIES .get (fqn );
500- return Optional .of (new VulnerabilityAndProblematicToken (
501- isVulnerable ,
502- callExpression .callee ().lastToken (),
503- true ));
504- }
505- }
506- }
507- return Optional .empty ();
523+ private static Optional <VulnerabilityAndProblematicToken > isVulnerableMethodCall (CallExpression callExpression ) {
524+ return Optional .ofNullable (callExpression .calleeSymbol ())
525+ .map (Symbol ::fullyQualifiedName )
526+ .map (VULNERABLE_CONTEXT_FACTORIES ::get )
527+ .map (predicate -> predicate .test (callExpression ))
528+ .map (isVulnerable -> new VulnerabilityAndProblematicToken (
529+ isVulnerable ,
530+ callExpression .callee ().lastToken (),
531+ true ));
508532 }
509533
510534 private static Optional <VulnerabilityAndProblematicToken > searchForVerifyModeOverride (Usage u ) {
@@ -529,4 +553,19 @@ private static Optional<VulnerabilityAndProblematicToken> searchForVerifyModeOve
529553 }
530554 return Optional .empty ();
531555 }
556+
557+ private static boolean isCheckHostnameAttributeSetToTrue (Usage u ) {
558+ return Optional .of (u )
559+ .filter (Predicate .not (Usage ::isBindingUsage ))
560+ .map (Usage ::tree )
561+ .map (Tree ::parent )
562+ .flatMap (TreeUtils .toOptionalInstanceOfMapper (QualifiedExpression .class ))
563+ .filter (qe -> "check_hostname" .equals (qe .name ().name ()))
564+ .map (QualifiedExpression ::parent )
565+ .map (Tree ::parent )
566+ .flatMap (TreeUtils .toOptionalInstanceOfMapper (AssignmentStatement .class ))
567+ .map (AssignmentStatement ::assignedValue )
568+ .filter (Expressions ::isTruthy )
569+ .isPresent ();
570+ }
532571}
0 commit comments