@@ -284,7 +284,8 @@ class MessageBodyReaderRead extends Method {
284284}
285285
286286private string getContentTypeString ( Expr e ) {
287- result = e .( CompileTimeConstantExpr ) .getStringValue ( )
287+ result = e .( CompileTimeConstantExpr ) .getStringValue ( ) and
288+ result != ""
288289 or
289290 exists ( Field jaxMediaType |
290291 // Accesses to static fields on `MediaType` class do not have constant strings in the database
@@ -327,8 +328,9 @@ private class JaxRSXssSink extends XssSink {
327328 |
328329 not exists ( resourceMethod .getProducesAnnotation ( ) )
329330 or
330- getContentTypeString ( resourceMethod .getProducesAnnotation ( ) .getADeclaredContentTypeExpr ( ) ) =
331- "text/plain"
331+ isXssVulnerableContentType ( getContentTypeString ( resourceMethod
332+ .getProducesAnnotation ( )
333+ .getADeclaredContentTypeExpr ( ) ) )
332334 )
333335 }
334336}
@@ -805,3 +807,150 @@ private class JaxRsUrlOpenSink extends SinkModelCsv {
805807 ]
806808 }
807809}
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