@@ -284,7 +284,8 @@ class MessageBodyReaderRead extends Method {
284
284
}
285
285
286
286
private string getContentTypeString ( Expr e ) {
287
- result = e .( CompileTimeConstantExpr ) .getStringValue ( )
287
+ result = e .( CompileTimeConstantExpr ) .getStringValue ( ) and
288
+ result != ""
288
289
or
289
290
exists ( Field jaxMediaType |
290
291
// Accesses to static fields on `MediaType` class do not have constant strings in the database
@@ -327,8 +328,9 @@ private class JaxRSXssSink extends XssSink {
327
328
|
328
329
not exists ( resourceMethod .getProducesAnnotation ( ) )
329
330
or
330
- getContentTypeString ( resourceMethod .getProducesAnnotation ( ) .getADeclaredContentTypeExpr ( ) ) =
331
- "text/plain"
331
+ isXssVulnerableContentType ( getContentTypeString ( resourceMethod
332
+ .getProducesAnnotation ( )
333
+ .getADeclaredContentTypeExpr ( ) ) )
332
334
)
333
335
}
334
336
}
@@ -805,3 +807,150 @@ private class JaxRsUrlOpenSink extends SinkModelCsv {
805
807
]
806
808
}
807
809
}
810
+
811
+ private predicate isXssVulnerableContentTypeExpr ( Expr e ) {
812
+ isXssVulnerableContentType ( getContentTypeString ( e ) )
813
+ }
814
+
815
+ private predicate isXssSafeContentTypeExpr ( Expr e ) { isXssSafeContentType ( getContentTypeString ( e ) ) }
816
+
817
+ /**
818
+ * Gets a builder expression or related type that is configured to use the given `contentType`.
819
+ *
820
+ * This could be an instance of `Response.ResponseBuilder`, `Variant`, `Variant.VariantListBuilder` or
821
+ * a `List<Variant>`.
822
+ *
823
+ * This routine is used to search forwards for response entities set after the content-type is configured.
824
+ * It does not need to consider cases where the entity is set in the same call, or the entity has already
825
+ * been set: these are handled by simple sanitization below.
826
+ */
827
+ private DataFlow:: Node getABuilderWithExplicitContentType ( Expr contentType ) {
828
+ // Base case: ResponseBuilder.type(contentType)
829
+ result .asExpr ( ) =
830
+ any ( MethodAccess ma |
831
+ ma .getCallee ( ) .hasQualifiedName ( getAJaxRsPackage ( "core" ) , "Response$ResponseBuilder" , "type" ) and
832
+ contentType = ma .getArgument ( 0 )
833
+ )
834
+ or
835
+ // Base case: new Variant(contentType, ...)
836
+ result .asExpr ( ) =
837
+ any ( ClassInstanceExpr cie |
838
+ cie .getConstructedType ( ) .hasQualifiedName ( getAJaxRsPackage ( "core" ) , "Variant" ) and
839
+ contentType = cie .getArgument ( 0 )
840
+ )
841
+ or
842
+ // Base case: Variant[.VariantListBuilder].mediaTypes(...)
843
+ result .asExpr ( ) =
844
+ any ( MethodAccess ma |
845
+ ma .getCallee ( )
846
+ .hasQualifiedName ( getAJaxRsPackage ( "core" ) , [ "Variant" , "Variant$VariantListBuilder" ] ,
847
+ "mediaTypes" ) and
848
+ contentType = ma .getAnArgument ( )
849
+ )
850
+ or
851
+ // Recursive case: propagate through variant list building:
852
+ result .asExpr ( ) =
853
+ any ( MethodAccess ma |
854
+ (
855
+ ma .getType ( )
856
+ .( RefType )
857
+ .hasQualifiedName ( getAJaxRsPackage ( "core" ) , "Variant$VariantListBuilder" )
858
+ or
859
+ ma .getMethod ( )
860
+ .hasQualifiedName ( getAJaxRsPackage ( "core" ) , "Variant$VariantListBuilder" , "build" )
861
+ ) and
862
+ [ ma .getAnArgument ( ) , ma .getQualifier ( ) ] =
863
+ getABuilderWithExplicitContentType ( contentType ) .asExpr ( )
864
+ )
865
+ or
866
+ // Recursive case: propagate through a List.get operation
867
+ result .asExpr ( ) =
868
+ any ( MethodAccess ma |
869
+ ma .getMethod ( ) .hasQualifiedName ( "java.util" , "List<Variant>" , "get" ) and
870
+ ma .getQualifier ( ) = getABuilderWithExplicitContentType ( contentType ) .asExpr ( )
871
+ )
872
+ or
873
+ // Recursive case: propagate through Response.ResponseBuilder operations, including the `variant(...)` operation.
874
+ result .asExpr ( ) =
875
+ any ( MethodAccess ma |
876
+ ma .getType ( ) .( RefType ) .hasQualifiedName ( getAJaxRsPackage ( "core" ) , "Response$ResponseBuilder" ) and
877
+ [ ma .getQualifier ( ) , ma .getArgument ( 0 ) ] =
878
+ getABuilderWithExplicitContentType ( contentType ) .asExpr ( )
879
+ )
880
+ or
881
+ // Recursive case: ordinary local dataflow
882
+ DataFlow:: localFlow ( getABuilderWithExplicitContentType ( contentType ) , result )
883
+ }
884
+
885
+ private DataFlow:: Node getASanitizedBuilder ( ) {
886
+ result = getABuilderWithExplicitContentType ( any ( Expr e | isXssSafeContentTypeExpr ( e ) ) )
887
+ }
888
+
889
+ private DataFlow:: Node getAVulnerableBuilder ( ) {
890
+ result = getABuilderWithExplicitContentType ( any ( Expr e | isXssVulnerableContentTypeExpr ( e ) ) )
891
+ }
892
+
893
+ /**
894
+ * A response builder sanitized by setting a safe content type.
895
+ *
896
+ * The content type could be set before the `entity(...)` call that needs sanitizing
897
+ * (e.g. `Response.ok().type("application/json").entity(sanitizeMe)`)
898
+ * or at the same time (e.g. `Response.ok(sanitizeMe, "application/json")`
899
+ * or the content-type could be set afterwards (e.g. `Response.ok().entity(userControlled).type("application/json")`)
900
+ *
901
+ * This differs from `getASanitizedBuilder` in that we also include functions that must set the entity
902
+ * at the same time, or the entity must already have been set, so propagating forwards to sanitize future
903
+ * build steps is not necessary.
904
+ */
905
+ private class SanitizedResponseBuilder extends XssSanitizer {
906
+ SanitizedResponseBuilder ( ) {
907
+ // e.g. sanitizeMe.type("application/json")
908
+ this = getASanitizedBuilder ( )
909
+ or
910
+ this .asExpr ( ) =
911
+ any ( MethodAccess ma |
912
+ ma .getMethod ( ) .hasQualifiedName ( getAJaxRsPackage ( "core" ) , "Response" , "ok" ) and
913
+ (
914
+ // e.g. Response.ok(sanitizeMe, new Variant("application/json", ...))
915
+ ma .getArgument ( 1 ) = getASanitizedBuilder ( ) .asExpr ( )
916
+ or
917
+ // e.g. Response.ok(sanitizeMe, "application/json")
918
+ isXssSafeContentTypeExpr ( ma .getArgument ( 1 ) )
919
+ )
920
+ )
921
+ }
922
+ }
923
+
924
+ /**
925
+ * An entity call that serves as a sink and barrier because it has a vulnerable content-type set.
926
+ *
927
+ * We flag these as direct sinks because otherwise it may be sanitized when it reaches a resource
928
+ * method with a safe-looking `@Produces` annotation. They are barriers because otherwise if the
929
+ * resource method does *not* have a safe-looking `@Produces` annotation then it would be doubly
930
+ * reported, once at the `entity(...)` call and once on return from the resource method.
931
+ */
932
+ private class VulnerableEntity extends XssSinkBarrier {
933
+ VulnerableEntity ( ) {
934
+ this .asExpr ( ) =
935
+ any ( MethodAccess ma |
936
+ (
937
+ // Vulnerable content-type already set:
938
+ ma .getQualifier ( ) = getAVulnerableBuilder ( ) .asExpr ( )
939
+ or
940
+ // Vulnerable content-type set in the future:
941
+ getAVulnerableBuilder ( ) .asExpr ( ) .( MethodAccess ) .getQualifier * ( ) = ma
942
+ ) and
943
+ ma .getMethod ( ) .hasName ( "entity" )
944
+ ) .getArgument ( 0 )
945
+ or
946
+ this .asExpr ( ) =
947
+ any ( MethodAccess ma |
948
+ (
949
+ isXssVulnerableContentTypeExpr ( ma .getArgument ( 1 ) )
950
+ or
951
+ ma .getArgument ( 1 ) = getAVulnerableBuilder ( ) .asExpr ( )
952
+ ) and
953
+ ma .getMethod ( ) .hasName ( "ok" )
954
+ ) .getArgument ( 0 )
955
+ }
956
+ }
0 commit comments