From 26b37ad6bcfd094d9bc8edbc2cd9c1349c3d88d7 Mon Sep 17 00:00:00 2001 From: Nicolas Will Date: Wed, 29 Jul 2026 02:57:23 +0200 Subject: [PATCH] Correct Micronaut cookie and map coverage --- .../micronaut/MicronautControllerTest.java | 5 +++ .../micronaut/MicronautHttpRequestTest.java | 6 ++++ .../tests/MicronautResponseSplitting.java | 20 +++++++++++ .../semmle/tests/ResponseSplitting.expected | 33 ++++++++++++------- .../io/micronaut/http/cookie/Cookies.java | 4 +-- 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/java/ql/test/library-tests/frameworks/micronaut/MicronautControllerTest.java b/java/ql/test/library-tests/frameworks/micronaut/MicronautControllerTest.java index 4bc38d949b1..ee4d30a0f57 100644 --- a/java/ql/test/library-tests/frameworks/micronaut/MicronautControllerTest.java +++ b/java/ql/test/library-tests/frameworks/micronaut/MicronautControllerTest.java @@ -1,6 +1,7 @@ import io.micronaut.http.annotation.*; import io.micronaut.http.*; import io.micronaut.http.cookie.Cookies; +import io.micronaut.http.cookie.Cookie; import java.io.InputStream; import java.io.Reader; @@ -72,6 +73,10 @@ class MicronautControllerTest { @Get("/cookies") void testCookies(Cookies cookies) { sink(cookies); // $hasTaintFlow + sink(cookies.getValue("session")); // $hasTaintFlow + for (java.util.Map.Entry entry : cookies) { + sink(entry.getValue()); // $hasTaintFlow + } } @Post("/stream") diff --git a/java/ql/test/library-tests/frameworks/micronaut/MicronautHttpRequestTest.java b/java/ql/test/library-tests/frameworks/micronaut/MicronautHttpRequestTest.java index b3340b6c6cb..42d94165f6e 100644 --- a/java/ql/test/library-tests/frameworks/micronaut/MicronautHttpRequestTest.java +++ b/java/ql/test/library-tests/frameworks/micronaut/MicronautHttpRequestTest.java @@ -47,6 +47,12 @@ class MicronautHttpRequestTest { sink(cookie.getPath()); // $hasTaintFlow sink(request.getCookies().getAll()); // $hasTaintFlow sink(request.getCookies().findCookie("session")); // $hasTaintFlow + sink(request.getCookies().getValue("session")); // $hasTaintFlow + sink(request.getCookies().values().iterator().next()); // $hasTaintFlow + sink(request.getCookies().asMap().get("session")); // $hasTaintFlow + for (Map.Entry entry : request.getCookies()) { + sink(entry.getValue()); // $hasTaintFlow + } } @Get("/uri") diff --git a/java/ql/test/query-tests/security/CWE-113/semmle/tests/MicronautResponseSplitting.java b/java/ql/test/query-tests/security/CWE-113/semmle/tests/MicronautResponseSplitting.java index 4ba35f86959..f95566722f8 100644 --- a/java/ql/test/query-tests/security/CWE-113/semmle/tests/MicronautResponseSplitting.java +++ b/java/ql/test/query-tests/security/CWE-113/semmle/tests/MicronautResponseSplitting.java @@ -4,6 +4,8 @@ import io.micronaut.http.annotation.QueryValue; import io.micronaut.http.HttpResponse; import io.micronaut.http.MutableHttpResponse; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; @Controller("/headers") public class MicronautResponseSplitting { @@ -26,4 +28,22 @@ public class MicronautResponseSplitting { // BAD: user-controlled header value return HttpResponse.ok().headers(Collections.singletonMap("X-Custom", headerValue)); } + + @Get("/bad-map-mutation") + public MutableHttpResponse badMapMutation(@QueryValue String headerValue) { + Map headers = new HashMap<>(); + headers.put("X-Custom", headerValue); + return HttpResponse.ok().headers(headers); + } + + @Get("/good-map") + public MutableHttpResponse goodMap() { + return HttpResponse.ok().headers(Collections.singletonMap("X-Custom", "safe")); + } + + @Get("/good-map-sanitized") + public MutableHttpResponse goodMapSanitized(@QueryValue String headerValue) { + String sanitized = headerValue.replace('\n', ' ').replace('\r', ' '); + return HttpResponse.ok().headers(Collections.singletonMap("X-Custom", sanitized)); + } } diff --git a/java/ql/test/query-tests/security/CWE-113/semmle/tests/ResponseSplitting.expected b/java/ql/test/query-tests/security/CWE-113/semmle/tests/ResponseSplitting.expected index 5be15214151..3aaffd3ece3 100644 --- a/java/ql/test/query-tests/security/CWE-113/semmle/tests/ResponseSplitting.expected +++ b/java/ql/test/query-tests/security/CWE-113/semmle/tests/ResponseSplitting.expected @@ -1,16 +1,20 @@ #select -| MicronautResponseSplitting.java:14:53:14:63 | headerValue | MicronautResponseSplitting.java:12:39:12:68 | headerValue : String | MicronautResponseSplitting.java:14:53:14:63 | headerValue | This header depends on a $@, which may cause a response-splitting vulnerability. | MicronautResponseSplitting.java:12:39:12:68 | headerValue | user-provided value | -| MicronautResponseSplitting.java:27:42:27:90 | singletonMap(...) | MicronautResponseSplitting.java:25:42:25:71 | headerValue : String | MicronautResponseSplitting.java:27:42:27:90 | singletonMap(...) | This header depends on a $@, which may cause a response-splitting vulnerability. | MicronautResponseSplitting.java:25:42:25:71 | headerValue | user-provided value | +| MicronautResponseSplitting.java:16:53:16:63 | headerValue | MicronautResponseSplitting.java:14:39:14:68 | headerValue : String | MicronautResponseSplitting.java:16:53:16:63 | headerValue | This header depends on a $@, which may cause a response-splitting vulnerability. | MicronautResponseSplitting.java:14:39:14:68 | headerValue | user-provided value | +| MicronautResponseSplitting.java:29:42:29:90 | singletonMap(...) | MicronautResponseSplitting.java:27:42:27:71 | headerValue : String | MicronautResponseSplitting.java:29:42:29:90 | singletonMap(...) | This header depends on a $@, which may cause a response-splitting vulnerability. | MicronautResponseSplitting.java:27:42:27:71 | headerValue | user-provided value | +| MicronautResponseSplitting.java:36:42:36:48 | headers | MicronautResponseSplitting.java:33:50:33:79 | headerValue : String | MicronautResponseSplitting.java:36:42:36:48 | headers | This header depends on a $@, which may cause a response-splitting vulnerability. | MicronautResponseSplitting.java:33:50:33:79 | headerValue | user-provided value | | ResponseSplitting.java:23:23:23:28 | cookie | ResponseSplitting.java:22:39:22:66 | getParameter(...) : String | ResponseSplitting.java:23:23:23:28 | cookie | This header depends on a $@, which may cause a response-splitting vulnerability. | ResponseSplitting.java:22:39:22:66 | getParameter(...) | user-provided value | | ResponseSplitting.java:28:38:28:72 | getParameter(...) | ResponseSplitting.java:28:38:28:72 | getParameter(...) | ResponseSplitting.java:28:38:28:72 | getParameter(...) | This header depends on a $@, which may cause a response-splitting vulnerability. | ResponseSplitting.java:28:38:28:72 | getParameter(...) | user-provided value | | ResponseSplitting.java:29:38:29:72 | getParameter(...) | ResponseSplitting.java:29:38:29:72 | getParameter(...) | ResponseSplitting.java:29:38:29:72 | getParameter(...) | This header depends on a $@, which may cause a response-splitting vulnerability. | ResponseSplitting.java:29:38:29:72 | getParameter(...) | user-provided value | | ResponseSplitting.java:59:27:59:57 | replaceFirst(...) | ResponseSplitting.java:53:14:53:48 | getParameter(...) : String | ResponseSplitting.java:59:27:59:57 | replaceFirst(...) | This header depends on a $@, which may cause a response-splitting vulnerability. | ResponseSplitting.java:53:14:53:48 | getParameter(...) | user-provided value | edges -| MicronautResponseSplitting.java:12:39:12:68 | headerValue : String | MicronautResponseSplitting.java:14:53:14:63 | headerValue | provenance | Sink:MaD:1 | -| MicronautResponseSplitting.java:25:42:25:71 | headerValue : String | MicronautResponseSplitting.java:27:79:27:89 | headerValue : String | provenance | | -| MicronautResponseSplitting.java:27:79:27:89 | headerValue : String | MicronautResponseSplitting.java:27:42:27:90 | singletonMap(...) | provenance | MaD:7 Sink:MaD:2 | +| MicronautResponseSplitting.java:14:39:14:68 | headerValue : String | MicronautResponseSplitting.java:16:53:16:63 | headerValue | provenance | Sink:MaD:1 | +| MicronautResponseSplitting.java:27:42:27:71 | headerValue : String | MicronautResponseSplitting.java:29:79:29:89 | headerValue : String | provenance | | +| MicronautResponseSplitting.java:29:79:29:89 | headerValue : String | MicronautResponseSplitting.java:29:42:29:90 | singletonMap(...) | provenance | MaD:7 Sink:MaD:2 | +| MicronautResponseSplitting.java:33:50:33:79 | headerValue : String | MicronautResponseSplitting.java:35:33:35:43 | headerValue : String | provenance | | +| MicronautResponseSplitting.java:35:9:35:15 | headers [post update] : HashMap [] : String | MicronautResponseSplitting.java:36:42:36:48 | headers | provenance | Sink:MaD:2 | +| MicronautResponseSplitting.java:35:33:35:43 | headerValue : String | MicronautResponseSplitting.java:35:9:35:15 | headers [post update] : HashMap [] : String | provenance | MaD:8 | | ResponseSplitting.java:22:20:22:67 | new Cookie(...) : Cookie | ResponseSplitting.java:23:23:23:28 | cookie | provenance | Sink:MaD:3 | -| ResponseSplitting.java:22:39:22:66 | getParameter(...) : String | ResponseSplitting.java:22:20:22:67 | new Cookie(...) : Cookie | provenance | Src:MaD:5 MaD:8 | +| ResponseSplitting.java:22:39:22:66 | getParameter(...) : String | ResponseSplitting.java:22:20:22:67 | new Cookie(...) : Cookie | provenance | Src:MaD:5 MaD:9 | | ResponseSplitting.java:53:14:53:48 | getParameter(...) : String | ResponseSplitting.java:59:27:59:27 | t : String | provenance | Src:MaD:5 | | ResponseSplitting.java:59:27:59:27 | t : String | ResponseSplitting.java:59:27:59:57 | replaceFirst(...) | provenance | MaD:6 Sink:MaD:4 | models @@ -21,13 +25,18 @@ models | 5 | Source: javax.servlet; ServletRequest; false; getParameter; (String); ; ReturnValue; remote; manual | | 6 | Summary: java.lang; String; false; replaceFirst; ; ; Argument[this]; ReturnValue; taint; manual | | 7 | Summary: java.util; Collections; false; singletonMap; (Object,Object); ; Argument[1]; ReturnValue.MapValue; value; manual | -| 8 | Summary: javax.servlet.http; Cookie; false; Cookie; ; ; Argument[1]; Argument[this]; taint; manual | +| 8 | Summary: java.util; Map; true; put; (Object,Object); ; Argument[1]; Argument[this].MapValue; value; manual | +| 9 | Summary: javax.servlet.http; Cookie; false; Cookie; ; ; Argument[1]; Argument[this]; taint; manual | nodes -| MicronautResponseSplitting.java:12:39:12:68 | headerValue : String | semmle.label | headerValue : String | -| MicronautResponseSplitting.java:14:53:14:63 | headerValue | semmle.label | headerValue | -| MicronautResponseSplitting.java:25:42:25:71 | headerValue : String | semmle.label | headerValue : String | -| MicronautResponseSplitting.java:27:42:27:90 | singletonMap(...) | semmle.label | singletonMap(...) | -| MicronautResponseSplitting.java:27:79:27:89 | headerValue : String | semmle.label | headerValue : String | +| MicronautResponseSplitting.java:14:39:14:68 | headerValue : String | semmle.label | headerValue : String | +| MicronautResponseSplitting.java:16:53:16:63 | headerValue | semmle.label | headerValue | +| MicronautResponseSplitting.java:27:42:27:71 | headerValue : String | semmle.label | headerValue : String | +| MicronautResponseSplitting.java:29:42:29:90 | singletonMap(...) | semmle.label | singletonMap(...) | +| MicronautResponseSplitting.java:29:79:29:89 | headerValue : String | semmle.label | headerValue : String | +| MicronautResponseSplitting.java:33:50:33:79 | headerValue : String | semmle.label | headerValue : String | +| MicronautResponseSplitting.java:35:9:35:15 | headers [post update] : HashMap [] : String | semmle.label | headers [post update] : HashMap [] : String | +| MicronautResponseSplitting.java:35:33:35:43 | headerValue : String | semmle.label | headerValue : String | +| MicronautResponseSplitting.java:36:42:36:48 | headers | semmle.label | headers | | ResponseSplitting.java:22:20:22:67 | new Cookie(...) : Cookie | semmle.label | new Cookie(...) : Cookie | | ResponseSplitting.java:22:39:22:66 | getParameter(...) : String | semmle.label | getParameter(...) : String | | ResponseSplitting.java:23:23:23:28 | cookie | semmle.label | cookie | diff --git a/java/ql/test/stubs/micronaut-4.x/io/micronaut/http/cookie/Cookies.java b/java/ql/test/stubs/micronaut-4.x/io/micronaut/http/cookie/Cookies.java index b35fcb9a6e0..6a24e572918 100644 --- a/java/ql/test/stubs/micronaut-4.x/io/micronaut/http/cookie/Cookies.java +++ b/java/ql/test/stubs/micronaut-4.x/io/micronaut/http/cookie/Cookies.java @@ -1,10 +1,10 @@ package io.micronaut.http.cookie; -import java.util.Collection; +import io.micronaut.core.convert.value.ConvertibleValues; import java.util.Optional; import java.util.Set; -public interface Cookies { +public interface Cookies extends ConvertibleValues { Set getAll(); Optional findCookie(CharSequence name); Cookie get(CharSequence name);