@@ -10,6 +10,7 @@ private import semmle.javascript.security.dataflow.TaintedPathCustomizations
10
10
private import CoreKnowledge as CoreKnowledge
11
11
private import semmle.javascript.heuristics.SyntacticHeuristics
12
12
private import semmle.javascript.filters.ClassifyFiles as ClassifyFiles
13
+ private import StandardEndpointFilters as StandardEndpointFilters
13
14
14
15
/**
15
16
* A set of characteristics that a particular endpoint might have. This set of characteristics is used to make decisions
@@ -555,3 +556,311 @@ private class InIrrelevantFileCharacteristic extends StandardEndpointFilterChara
555
556
this = "in " + category + " file"
556
557
}
557
558
}
559
+
560
+ /** An EndpointFilterCharacteristic that indicates that an endpoint is unlikely to be a NoSQL injection sink. */
561
+ abstract private class NosqlInjectionSinkEndpointFilterCharacteristic extends EndpointFilterCharacteristic {
562
+ bindingset [ this ]
563
+ NosqlInjectionSinkEndpointFilterCharacteristic ( ) { any ( ) }
564
+
565
+ override predicate getImplications (
566
+ EndpointType endpointClass , boolean isPositiveIndicator , float confidence
567
+ ) {
568
+ endpointClass instanceof NosqlInjectionSinkType and
569
+ isPositiveIndicator = false and
570
+ confidence = mediumConfidence ( )
571
+ }
572
+ }
573
+
574
+ private class DatabaseAccessCallHeuristicCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
575
+ DatabaseAccessCallHeuristicCharacteristic ( ) { this = "matches database access call heuristic" }
576
+
577
+ override predicate getEndpoints ( DataFlow:: Node n ) {
578
+ exists ( DataFlow:: CallNode call | n = call .getAnArgument ( ) |
579
+ // additional databases accesses that aren't modeled yet
580
+ call .( DataFlow:: MethodCallNode ) .getMethodName ( ) =
581
+ [ "create" , "createCollection" , "createIndexes" ]
582
+ )
583
+ }
584
+ }
585
+
586
+ private class ModeledSinkCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
587
+ ModeledSinkCharacteristic ( ) { this = "modeled sink" }
588
+
589
+ override predicate getEndpoints ( DataFlow:: Node n ) {
590
+ exists ( DataFlow:: CallNode call | n = call .getAnArgument ( ) |
591
+ // Remove modeled sinks
592
+ CoreKnowledge:: isArgumentToKnownLibrarySinkFunction ( n )
593
+ )
594
+ }
595
+ }
596
+
597
+ private class PredecessorInModeledFlowStepCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
598
+ PredecessorInModeledFlowStepCharacteristic ( ) { this = "predecessor in a modeled flow step" }
599
+
600
+ override predicate getEndpoints ( DataFlow:: Node n ) {
601
+ exists ( DataFlow:: CallNode call | n = call .getAnArgument ( ) |
602
+ // Remove common kinds of unlikely sinks
603
+ CoreKnowledge:: isKnownStepSrc ( n )
604
+ )
605
+ }
606
+ }
607
+
608
+ private class ModeledDatabaseAccessCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
609
+ ModeledDatabaseAccessCharacteristic ( ) { this = "modeled database access" }
610
+
611
+ override predicate getEndpoints ( DataFlow:: Node n ) {
612
+ exists ( DataFlow:: CallNode call | n = call .getAnArgument ( ) |
613
+ // Remove modeled database calls. Arguments to modeled calls are very likely to be modeled
614
+ // as sinks if they are true positives. Therefore arguments that are not modeled as sinks
615
+ // are unlikely to be true positives.
616
+ call instanceof DatabaseAccess
617
+ )
618
+ }
619
+ }
620
+
621
+ private class ReceiverIsHTTPRequestExpressionCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
622
+ ReceiverIsHTTPRequestExpressionCharacteristic ( ) { this = "receiver is a HTTP request expression" }
623
+
624
+ override predicate getEndpoints ( DataFlow:: Node n ) {
625
+ exists ( DataFlow:: CallNode call | n = call .getAnArgument ( ) |
626
+ // Remove calls to APIs that aren't relevant to NoSQL injection
627
+ call .getReceiver ( ) instanceof Http:: RequestNode
628
+ )
629
+ }
630
+ }
631
+
632
+ private class ReceiverIsHTTPResponseExpressionCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
633
+ ReceiverIsHTTPResponseExpressionCharacteristic ( ) {
634
+ this = "receiver is a HTTP response expression"
635
+ }
636
+
637
+ override predicate getEndpoints ( DataFlow:: Node n ) {
638
+ exists ( DataFlow:: CallNode call | n = call .getAnArgument ( ) |
639
+ // Remove calls to APIs that aren't relevant to NoSQL injection
640
+ call .getReceiver ( ) instanceof Http:: ResponseNode
641
+ )
642
+ }
643
+ }
644
+
645
+ private class NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkNosqlCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
646
+ NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkNosqlCharacteristic ( ) {
647
+ this = "not a direct argument to a likely external library call or a heuristic sink (nosql)"
648
+ }
649
+
650
+ override predicate getEndpoints ( DataFlow:: Node n ) {
651
+ // Require NoSQL injection sink candidates to be (a) direct arguments to external library calls
652
+ // or (b) heuristic sinks for NoSQL injection.
653
+ //
654
+ // ## Direct arguments to external library calls
655
+ //
656
+ // The `StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall` endpoint filter
657
+ // allows sink candidates which are within object literals or array literals, for example
658
+ // `req.sendFile(_, { path: ENDPOINT })`.
659
+ //
660
+ // However, the NoSQL injection query deals differently with these types of sinks compared to
661
+ // other security queries. Other security queries such as SQL injection tend to treat
662
+ // `ENDPOINT` as the ground truth sink, but the NoSQL injection query instead treats
663
+ // `{ path: ENDPOINT }` as the ground truth sink and defines an additional flow step to ensure
664
+ // data flows from `ENDPOINT` to the ground truth sink `{ path: ENDPOINT }`.
665
+ //
666
+ // Therefore for the NoSQL injection boosted query, we must ignore sink candidates within object
667
+ // literals or array literals, to avoid having multiple alerts for the same security
668
+ // vulnerability (one FP where the sink is `ENDPOINT` and one TP where the sink is
669
+ // `{ path: ENDPOINT }`). We accomplish this by directly testing that the sink candidate is an
670
+ // argument of a likely external library call.
671
+ //
672
+ // ## Heuristic sinks
673
+ //
674
+ // We also allow heuristic sinks in addition to direct arguments to external library calls.
675
+ // These are copied from the `HeuristicNosqlInjectionSink` class defined within
676
+ // `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
677
+ // We can't reuse the class because importing that file would cause us to treat these
678
+ // heuristic sinks as known sinks.
679
+ not n = StandardEndpointFilters:: getALikelyExternalLibraryCall ( ) .getAnArgument ( ) and
680
+ not (
681
+ isAssignedToOrConcatenatedWith ( n , "(?i)(nosql|query)" ) or
682
+ isArgTo ( n , "(?i)(query)" )
683
+ )
684
+ }
685
+ }
686
+
687
+ /** An EndpointFilterCharacteristic that indicates that an endpoint is unlikely to be a SQL injection sink. */
688
+ abstract private class SqlInjectionSinkEndpointFilterCharacteristic extends EndpointFilterCharacteristic {
689
+ bindingset [ this ]
690
+ SqlInjectionSinkEndpointFilterCharacteristic ( ) { any ( ) }
691
+
692
+ override predicate getImplications (
693
+ EndpointType endpointClass , boolean isPositiveIndicator , float confidence
694
+ ) {
695
+ endpointClass instanceof SqlInjectionSinkType and
696
+ isPositiveIndicator = false and
697
+ confidence = mediumConfidence ( )
698
+ }
699
+ }
700
+
701
+ private class PreparedSQLStatementCharacteristic extends SqlInjectionSinkEndpointFilterCharacteristic {
702
+ PreparedSQLStatementCharacteristic ( ) { this = "prepared SQL statement" }
703
+
704
+ override predicate getEndpoints ( DataFlow:: Node n ) {
705
+ exists ( DataFlow:: CallNode call | n = call .getAnArgument ( ) |
706
+ // prepared statements for SQL
707
+ any ( DataFlow:: CallNode cn | cn .getCalleeName ( ) = "prepare" )
708
+ .getAMethodCall ( "run" )
709
+ .getAnArgument ( ) = n
710
+ )
711
+ }
712
+ }
713
+
714
+ private class ArrayCreationCharacteristic extends SqlInjectionSinkEndpointFilterCharacteristic {
715
+ ArrayCreationCharacteristic ( ) { this = "array creation" }
716
+
717
+ override predicate getEndpoints ( DataFlow:: Node n ) {
718
+ exists ( DataFlow:: CallNode call | n = call .getAnArgument ( ) |
719
+ n instanceof DataFlow:: ArrayCreationNode
720
+ )
721
+ }
722
+ }
723
+
724
+ private class HTMLOrRenderingCharacteristic extends SqlInjectionSinkEndpointFilterCharacteristic {
725
+ HTMLOrRenderingCharacteristic ( ) { this = "HTML / rendering" }
726
+
727
+ override predicate getEndpoints ( DataFlow:: Node n ) {
728
+ exists ( DataFlow:: CallNode call | n = call .getAnArgument ( ) |
729
+ // UI is unrelated to SQL
730
+ call .getCalleeName ( ) .regexpMatch ( "(?i).*(render|html).*" )
731
+ )
732
+ }
733
+ }
734
+
735
+ private class NotAnArgumentToLikelyExternalLibraryCallOrHeuristicSinkCharacteristic extends SqlInjectionSinkEndpointFilterCharacteristic {
736
+ NotAnArgumentToLikelyExternalLibraryCallOrHeuristicSinkCharacteristic ( ) {
737
+ this = "not an argument to a likely external library call or a heuristic sink"
738
+ }
739
+
740
+ override predicate getEndpoints ( DataFlow:: Node n ) {
741
+ // Require SQL injection sink candidates to be (a) arguments to external library calls
742
+ // (possibly indirectly), or (b) heuristic sinks.
743
+ //
744
+ // Heuristic sinks are copied from the `HeuristicSqlInjectionSink` class defined within
745
+ // `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
746
+ // We can't reuse the class because importing that file would cause us to treat these
747
+ // heuristic sinks as known sinks.
748
+ not StandardEndpointFilters:: flowsToArgumentOfLikelyExternalLibraryCall ( n ) and
749
+ not (
750
+ isAssignedToOrConcatenatedWith ( n , "(?i)(sql|query)" ) or
751
+ isArgTo ( n , "(?i)(query)" ) or
752
+ isConcatenatedWithString ( n ,
753
+ "(?s).*(ALTER|COUNT|CREATE|DATABASE|DELETE|DISTINCT|DROP|FROM|GROUP|INSERT|INTO|LIMIT|ORDER|SELECT|TABLE|UPDATE|WHERE).*" )
754
+ )
755
+ }
756
+ }
757
+
758
+ /** An EndpointFilterCharacteristic that indicates that an endpoint is unlikely to be a tainted path injection sink. */
759
+ abstract private class TaintedPathSinkEndpointFilterCharacteristic extends EndpointFilterCharacteristic {
760
+ bindingset [ this ]
761
+ TaintedPathSinkEndpointFilterCharacteristic ( ) { any ( ) }
762
+
763
+ override predicate getImplications (
764
+ EndpointType endpointClass , boolean isPositiveIndicator , float confidence
765
+ ) {
766
+ endpointClass instanceof TaintedPathSinkType and
767
+ isPositiveIndicator = false and
768
+ confidence = mediumConfidence ( )
769
+ }
770
+ }
771
+
772
+ private class NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkTaintedPathCharacteristic extends TaintedPathSinkEndpointFilterCharacteristic {
773
+ NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkTaintedPathCharacteristic ( ) {
774
+ this =
775
+ "not a direct argument to a likely external library call or a heuristic sink (tainted path)"
776
+ }
777
+
778
+ override predicate getEndpoints ( DataFlow:: Node n ) {
779
+ // Require path injection sink candidates to be (a) arguments to external library calls
780
+ // (possibly indirectly), or (b) heuristic sinks.
781
+ //
782
+ // Heuristic sinks are mostly copied from the `HeuristicTaintedPathSink` class defined within
783
+ // `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
784
+ // We can't reuse the class because importing that file would cause us to treat these
785
+ // heuristic sinks as known sinks.
786
+ not StandardEndpointFilters:: flowsToArgumentOfLikelyExternalLibraryCall ( n ) and
787
+ not (
788
+ isAssignedToOrConcatenatedWith ( n , "(?i)(file|folder|dir|absolute)" )
789
+ or
790
+ isArgTo ( n , "(?i)(get|read)file" )
791
+ or
792
+ exists ( string pathPattern |
793
+ // paths with at least two parts, and either a trailing or leading slash
794
+ pathPattern = "(?i)([a-z0-9_.-]+/){2,}" or
795
+ pathPattern = "(?i)(/[a-z0-9_.-]+){2,}"
796
+ |
797
+ isConcatenatedWithString ( n , pathPattern )
798
+ )
799
+ or
800
+ isConcatenatedWithStrings ( ".*/" , n , "/.*" )
801
+ or
802
+ // In addition to the names from `HeuristicTaintedPathSink` in the
803
+ // `isAssignedToOrConcatenatedWith` predicate call above, we also allow the noisier "path"
804
+ // name.
805
+ isAssignedToOrConcatenatedWith ( n , "(?i)path" )
806
+ )
807
+ }
808
+ }
809
+
810
+ /** An EndpointFilterCharacteristic that indicates that an endpoint is unlikely to be an XSS sink. */
811
+ abstract private class XssSinkEndpointFilterCharacteristic extends EndpointFilterCharacteristic {
812
+ bindingset [ this ]
813
+ XssSinkEndpointFilterCharacteristic ( ) { any ( ) }
814
+
815
+ override predicate getImplications (
816
+ EndpointType endpointClass , boolean isPositiveIndicator , float confidence
817
+ ) {
818
+ endpointClass instanceof XssSinkType and
819
+ isPositiveIndicator = false and
820
+ confidence = mediumConfidence ( )
821
+ }
822
+ }
823
+
824
+ private class SetStateCallsInReactApplicationsCharacteristic extends XssSinkEndpointFilterCharacteristic {
825
+ SetStateCallsInReactApplicationsCharacteristic ( ) {
826
+ this = "setState calls ought to be safe in react applications"
827
+ }
828
+
829
+ override predicate getEndpoints ( DataFlow:: Node n ) {
830
+ exists ( DataFlow:: CallNode call | n = call .getAnArgument ( ) | call .getCalleeName ( ) = "setState" )
831
+ }
832
+ }
833
+
834
+ private class NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkXssCharacteristic extends XssSinkEndpointFilterCharacteristic {
835
+ NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkXssCharacteristic ( ) {
836
+ this = "not a direct argument to a likely external library call or a heuristic sink (XSS)"
837
+ }
838
+
839
+ override predicate getEndpoints ( DataFlow:: Node n ) {
840
+ // Require XSS sink candidates to be (a) arguments to external library calls (possibly
841
+ // indirectly), or (b) heuristic sinks.
842
+ //
843
+ // Heuristic sinks are copied from the `HeuristicDomBasedXssSink` class defined within
844
+ // `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
845
+ // We can't reuse the class because importing that file would cause us to treat these
846
+ // heuristic sinks as known sinks.
847
+ not StandardEndpointFilters:: flowsToArgumentOfLikelyExternalLibraryCall ( n ) and
848
+ not (
849
+ isAssignedToOrConcatenatedWith ( n , "(?i)(html|innerhtml)" )
850
+ or
851
+ isArgTo ( n , "(?i)(html|render)" )
852
+ or
853
+ n instanceof StringOps:: HtmlConcatenationLeaf
854
+ or
855
+ isConcatenatedWithStrings ( "(?is).*<[a-z ]+.*" , n , "(?s).*>.*" )
856
+ or
857
+ // In addition to the heuristic sinks from `HeuristicDomBasedXssSink`, explicitly allow
858
+ // property writes like `elem.innerHTML = <TAINT>` that may not be picked up as HTML
859
+ // concatenation leaves.
860
+ exists ( DataFlow:: PropWrite pw |
861
+ pw .getPropertyName ( ) .regexpMatch ( "(?i).*html*" ) and
862
+ pw .getRhs ( ) = n
863
+ )
864
+ )
865
+ }
866
+ }
0 commit comments