Skip to content

Commit cf31af7

Browse files
committed
Add Micronaut framework support for Java QL
Add CodeQL support for Micronaut: add MaD models for HTTP, HTTP client and multipart (sources, sinks and summary propagation), new framework QLL modules (Controller, WebSocket, Config, Data, Security). Add library tests and query tests exercising request inputs, file uploads, HttpClient sinks (SSRF), header sinks (response-splitting) and redirect sinks (open-redirect), plus expected results and extractor options. Include Micronaut 4.x stubs used by the tests.
1 parent 600f585 commit cf31af7

File tree

73 files changed

+2467
-844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+2467
-844
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/java-all
4+
extensible: sinkModel
5+
data:
6+
# HttpClient.toBlocking() returns BlockingHttpClient; retrieve/exchange with String URL are SSRF sinks
7+
- ["io.micronaut.http.client", "BlockingHttpClient", True, "retrieve", "(String)", "", "Argument[0]", "request-forgery", "manual"]
8+
- ["io.micronaut.http.client", "BlockingHttpClient", True, "retrieve", "(String,Class)", "", "Argument[0]", "request-forgery", "manual"]
9+
- ["io.micronaut.http.client", "BlockingHttpClient", True, "exchange", "(String)", "", "Argument[0]", "request-forgery", "manual"]
10+
- ["io.micronaut.http.client", "BlockingHttpClient", True, "exchange", "(String,Class)", "", "Argument[0]", "request-forgery", "manual"]
11+
- addsTo:
12+
pack: codeql/java-all
13+
extensible: summaryModel
14+
data:
15+
# HttpClient.toBlocking() taint propagation
16+
- ["io.micronaut.http.client", "HttpClient", True, "toBlocking", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
17+
# HttpRequest.GET/POST/PUT/DELETE/PATCH factory methods propagate URI taint
18+
- ["io.micronaut.http", "HttpRequest", True, "GET", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
19+
- ["io.micronaut.http", "HttpRequest", True, "POST", "(String,Object)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
20+
- ["io.micronaut.http", "HttpRequest", True, "PUT", "(String,Object)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
21+
- ["io.micronaut.http", "HttpRequest", True, "DELETE", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
22+
- ["io.micronaut.http", "HttpRequest", True, "PATCH", "(String,Object)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
23+
- ["io.micronaut.http", "HttpRequest", True, "HEAD", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
24+
- ["io.micronaut.http", "HttpRequest", True, "OPTIONS", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
25+
# UriBuilder taint propagation
26+
- ["io.micronaut.http.uri", "UriBuilder", True, "of", "(CharSequence)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
27+
- ["io.micronaut.http.uri", "UriBuilder", True, "of", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
28+
- ["io.micronaut.http.uri", "UriBuilder", True, "host", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
29+
- ["io.micronaut.http.uri", "UriBuilder", True, "path", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
30+
- ["io.micronaut.http.uri", "UriBuilder", True, "queryParam", "(String,Object[])", "", "Argument[0]", "ReturnValue", "taint", "manual"]
31+
- ["io.micronaut.http.uri", "UriBuilder", True, "queryParam", "(String,Object[])", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
32+
- ["io.micronaut.http.uri", "UriBuilder", True, "fragment", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
33+
- ["io.micronaut.http.uri", "UriBuilder", True, "build", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/java-all
4+
extensible: sourceModel
5+
data:
6+
- ["io.micronaut.http", "HttpRequest", True, "getBody", "", "", "ReturnValue", "remote", "manual"]
7+
- ["io.micronaut.http", "HttpRequest", True, "getHeaders", "", "", "ReturnValue", "remote", "manual"]
8+
- ["io.micronaut.http", "HttpRequest", True, "getParameters", "", "", "ReturnValue", "remote", "manual"]
9+
- ["io.micronaut.http", "HttpRequest", True, "getCookies", "", "", "ReturnValue", "remote", "manual"]
10+
- ["io.micronaut.http", "HttpRequest", True, "getUri", "", "", "ReturnValue", "remote", "manual"]
11+
- ["io.micronaut.http", "HttpRequest", True, "getPath", "", "", "ReturnValue", "remote", "manual"]
12+
- ["io.micronaut.http", "HttpRequest", True, "getContentType", "", "", "ReturnValue", "remote", "manual"]
13+
- ["io.micronaut.http", "HttpRequest", True, "getContentLength", "", "", "ReturnValue", "remote", "manual"]
14+
- ["io.micronaut.http", "HttpRequest", True, "getMethodName", "", "", "ReturnValue", "remote", "manual"]
15+
- addsTo:
16+
pack: codeql/java-all
17+
extensible: summaryModel
18+
data:
19+
- ["io.micronaut.http", "HttpHeaders", True, "get", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
20+
- ["io.micronaut.http", "HttpHeaders", True, "getAll", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
21+
- ["io.micronaut.http", "HttpHeaders", True, "getFirst", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
22+
- ["io.micronaut.http", "HttpHeaders", True, "values", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
23+
- ["io.micronaut.http", "HttpParameters", True, "get", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
24+
- ["io.micronaut.http", "HttpParameters", True, "getAll", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
25+
- ["io.micronaut.http", "HttpParameters", True, "getFirst", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
26+
- ["io.micronaut.http.cookie", "Cookies", True, "get", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
27+
- ["io.micronaut.http.cookie", "Cookies", True, "getAll", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
28+
- ["io.micronaut.http.cookie", "Cookies", True, "findCookie", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
29+
- ["io.micronaut.http.cookie", "Cookie", True, "getValue", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
30+
- ["io.micronaut.http.cookie", "Cookie", True, "getName", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
31+
- ["io.micronaut.http.cookie", "Cookie", True, "getDomain", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
32+
- ["io.micronaut.http.cookie", "Cookie", True, "getPath", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
33+
- addsTo:
34+
pack: codeql/java-all
35+
extensible: sinkModel
36+
data:
37+
- ["io.micronaut.http", "MutableHttpResponse", True, "header", "(CharSequence,CharSequence)", "", "Argument[1]", "response-splitting", "manual"]
38+
- ["io.micronaut.http", "HttpResponse", True, "redirect", "(URI)", "", "Argument[0]", "url-redirection", "manual"]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/java-all
4+
extensible: sourceModel
5+
data:
6+
- ["io.micronaut.http.multipart", "CompletedFileUpload", True, "getBytes", "", "", "ReturnValue", "remote", "manual"]
7+
- ["io.micronaut.http.multipart", "CompletedFileUpload", True, "getInputStream", "", "", "ReturnValue", "remote", "manual"]
8+
- ["io.micronaut.http.multipart", "CompletedFileUpload", True, "getFilename", "", "", "ReturnValue", "remote", "manual"]
9+
- ["io.micronaut.http.multipart", "CompletedFileUpload", True, "getContentType", "", "", "ReturnValue", "remote", "manual"]
10+
- ["io.micronaut.http.multipart", "CompletedFileUpload", True, "getSize", "", "", "ReturnValue", "remote", "manual"]

java/ql/lib/semmle/code/java/dataflow/FlowSources.qll

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ import semmle.code.java.frameworks.Guice
3030
import semmle.code.java.frameworks.struts.StrutsActions
3131
import semmle.code.java.frameworks.Thrift
3232
import semmle.code.java.frameworks.javaee.jsf.JSFRenderer
33+
import semmle.code.java.frameworks.micronaut.MicronautController
34+
import semmle.code.java.frameworks.micronaut.MicronautWebSocket
35+
import semmle.code.java.frameworks.micronaut.MicronautConfig
3336
private import semmle.code.java.dataflow.ExternalFlow
3437
private import codeql.threatmodels.ThreatModels
3538

@@ -187,6 +190,38 @@ private class AndroidExternalStorageSource extends RemoteFlowSource {
187190
override string getSourceType() { result = "Android external storage" }
188191
}
189192

193+
private class MicronautHttpInputParameterSource extends RemoteFlowSource {
194+
MicronautHttpInputParameterSource() {
195+
this.asParameter() = any(MicronautRequestMappingParameter mrmp | mrmp.isTaintedInput())
196+
}
197+
198+
override string getSourceType() { result = "Micronaut HTTP input parameter" }
199+
}
200+
201+
private class MicronautWebSocketParameterSource extends RemoteFlowSource {
202+
MicronautWebSocketParameterSource() { this.asParameter() instanceof MicronautWebSocketParameter }
203+
204+
override string getSourceType() { result = "Micronaut WebSocket parameter" }
205+
}
206+
207+
private class MicronautConfigSource extends LocalUserInput {
208+
MicronautConfigSource() {
209+
this.asExpr() = any(MicronautConfigField f).getAnAccess()
210+
or
211+
this.asParameter() instanceof MicronautConfigParameter
212+
}
213+
214+
override string getThreatModel() { result = "environment" }
215+
}
216+
217+
private class MicronautErrorHandlerSource extends RemoteFlowSource {
218+
MicronautErrorHandlerSource() {
219+
this.asParameter() = any(MicronautErrorHandler h).getARemoteParameter()
220+
}
221+
222+
override string getSourceType() { result = "Micronaut error handler parameter" }
223+
}
224+
190225
/** Class for `tainted` user input. */
191226
abstract class UserInput extends SourceNode { }
192227

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/** Provides classes for identifying Micronaut configuration injection sources. */
2+
overlay[local?]
3+
module;
4+
5+
import java
6+
7+
/** The annotation type `@Value` from `io.micronaut.context.annotation`. */
8+
class MicronautValueAnnotation extends AnnotationType {
9+
MicronautValueAnnotation() { this.hasQualifiedName("io.micronaut.context.annotation", "Value") }
10+
}
11+
12+
/** The annotation type `@Property` from `io.micronaut.context.annotation`. */
13+
class MicronautPropertyAnnotation extends AnnotationType {
14+
MicronautPropertyAnnotation() {
15+
this.hasQualifiedName("io.micronaut.context.annotation", "Property")
16+
}
17+
}
18+
19+
/**
20+
* A field annotated with Micronaut's `@Value` or `@Property` annotation,
21+
* representing an injected configuration value.
22+
*/
23+
class MicronautConfigField extends Field {
24+
MicronautConfigField() {
25+
this.getAnAnnotation().getType() instanceof MicronautValueAnnotation
26+
or
27+
this.getAnAnnotation().getType() instanceof MicronautPropertyAnnotation
28+
}
29+
}
30+
31+
/**
32+
* A parameter annotated with Micronaut's `@Value` or `@Property` annotation,
33+
* representing an injected configuration value.
34+
*/
35+
class MicronautConfigParameter extends Parameter {
36+
MicronautConfigParameter() {
37+
this.getAnAnnotation().getType() instanceof MicronautValueAnnotation
38+
or
39+
this.getAnAnnotation().getType() instanceof MicronautPropertyAnnotation
40+
}
41+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* Provides classes for identifying Micronaut HTTP controllers and their request handling methods.
3+
*/
4+
overlay[local?]
5+
module;
6+
7+
import java
8+
9+
/** An annotation type that identifies Micronaut controllers. */
10+
class MicronautControllerAnnotation extends AnnotationType {
11+
MicronautControllerAnnotation() {
12+
this.hasQualifiedName("io.micronaut.http.annotation", "Controller")
13+
}
14+
}
15+
16+
/**
17+
* A class annotated as a Micronaut `@Controller`.
18+
*/
19+
class MicronautController extends Class {
20+
MicronautController() {
21+
this.getAnAnnotation().getType() instanceof MicronautControllerAnnotation
22+
}
23+
}
24+
25+
/** An annotation type that identifies Micronaut HTTP method mappings. */
26+
class MicronautHttpMethodAnnotation extends AnnotationType {
27+
MicronautHttpMethodAnnotation() {
28+
this.getPackage().hasName("io.micronaut.http.annotation") and
29+
this.hasName([
30+
"Get", "Post", "Put", "Delete", "Patch", "Head", "Options", "Trace", "CustomHttpMethod"
31+
])
32+
}
33+
}
34+
35+
/**
36+
* A method on a Micronaut controller that is executed in response to an HTTP request.
37+
*/
38+
class MicronautRequestMappingMethod extends Method {
39+
MicronautRequestMappingMethod() {
40+
this.getDeclaringType() instanceof MicronautController and
41+
this.getAnAnnotation().getType() instanceof MicronautHttpMethodAnnotation
42+
}
43+
44+
/** Gets a request mapping parameter. */
45+
MicronautRequestMappingParameter getARequestParameter() { result = this.getAParameter() }
46+
}
47+
48+
/** A Micronaut annotation indicating remote user input from HTTP requests. */
49+
class MicronautHttpInputAnnotation extends Annotation {
50+
MicronautHttpInputAnnotation() {
51+
exists(AnnotationType a |
52+
a = this.getType() and
53+
a.getPackage().hasName("io.micronaut.http.annotation")
54+
|
55+
a.hasName([
56+
"PathVariable", "QueryValue", "Body", "Header", "CookieValue", "Part", "RequestAttribute"
57+
])
58+
)
59+
}
60+
}
61+
62+
/** A parameter of a `MicronautRequestMappingMethod`. */
63+
class MicronautRequestMappingParameter extends Parameter {
64+
MicronautRequestMappingParameter() { this.getCallable() instanceof MicronautRequestMappingMethod }
65+
66+
/** Holds if the parameter should not be considered a direct source of taint. */
67+
predicate isNotDirectlyTaintedInput() {
68+
this.getType().(RefType).getAnAncestor().hasQualifiedName("io.micronaut.http", "HttpResponse")
69+
or
70+
this.getType()
71+
.(RefType)
72+
.getAnAncestor()
73+
.hasQualifiedName("io.micronaut.http", "MutableHttpResponse")
74+
or
75+
this.getType().(RefType).getAnAncestor().hasQualifiedName("java.security", "Principal")
76+
or
77+
this.getType().(RefType).getAnAncestor().hasQualifiedName("java.util", "Locale")
78+
or
79+
this.getType().(RefType).getAnAncestor().hasQualifiedName("java.util", "TimeZone")
80+
or
81+
this.getType().(RefType).getAnAncestor().hasQualifiedName("java.time", "ZoneId")
82+
or
83+
// @Value/@Property parameters are configuration injection, not HTTP input
84+
this.getAnAnnotation()
85+
.getType()
86+
.hasQualifiedName("io.micronaut.context.annotation", ["Value", "Property"])
87+
}
88+
89+
private predicate isExplicitlyTaintedInput() {
90+
// The MicronautHttpInputAnnotations allow access to the URI path,
91+
// request parameters, cookie values, headers, and the body of the request.
92+
this.getAnAnnotation() instanceof MicronautHttpInputAnnotation
93+
or
94+
// A @RequestBean parameter binds multiple request attributes into a POJO
95+
this.getAnAnnotation().getType() instanceof MicronautRequestBeanAnnotation
96+
or
97+
// An HttpRequest parameter provides access to request data
98+
this.getType()
99+
.(RefType)
100+
.getASourceSupertype*()
101+
.hasQualifiedName("io.micronaut.http", "HttpRequest")
102+
or
103+
// InputStream or Reader parameters allow access to the body of a request
104+
this.getType().(RefType).getAnAncestor() instanceof TypeInputStream
105+
or
106+
this.getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "Reader")
107+
}
108+
109+
/** Holds if the input is tainted (i.e. comes from user-controlled input). */
110+
predicate isTaintedInput() {
111+
this.isExplicitlyTaintedInput()
112+
or
113+
not this.isNotDirectlyTaintedInput()
114+
}
115+
}
116+
117+
/** An annotation type that identifies Micronaut error handler methods. */
118+
class MicronautErrorAnnotation extends AnnotationType {
119+
MicronautErrorAnnotation() { this.hasQualifiedName("io.micronaut.http.annotation", "Error") }
120+
}
121+
122+
/** A method annotated with Micronaut's `@Error` that handles exceptions. */
123+
class MicronautErrorHandler extends Method {
124+
MicronautErrorHandler() { this.getAnAnnotation().getType() instanceof MicronautErrorAnnotation }
125+
126+
/** Gets a parameter that carries user-controlled request data. */
127+
Parameter getARemoteParameter() {
128+
result = this.getAParameter() and
129+
result
130+
.getType()
131+
.(RefType)
132+
.getASourceSupertype*()
133+
.hasQualifiedName("io.micronaut.http", "HttpRequest")
134+
}
135+
}
136+
137+
/** An annotation type that identifies Micronaut request bean parameters. */
138+
class MicronautRequestBeanAnnotation extends AnnotationType {
139+
MicronautRequestBeanAnnotation() {
140+
this.hasQualifiedName("io.micronaut.http.annotation", "RequestBean")
141+
}
142+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/** Provides classes for identifying Micronaut Data repositories and query annotations. */
2+
overlay[local?]
3+
module;
4+
5+
import java
6+
7+
/**
8+
* The annotation type `@Query` from `io.micronaut.data.annotation`.
9+
*/
10+
class MicronautQueryAnnotation extends AnnotationType {
11+
MicronautQueryAnnotation() { this.hasQualifiedName("io.micronaut.data.annotation", "Query") }
12+
}
13+
14+
/**
15+
* The annotation type `@Repository` from `io.micronaut.data.annotation`.
16+
*/
17+
class MicronautRepositoryAnnotation extends AnnotationType {
18+
MicronautRepositoryAnnotation() {
19+
this.hasQualifiedName("io.micronaut.data.annotation", "Repository")
20+
}
21+
}
22+
23+
/**
24+
* A class annotated with Micronaut's `@Repository` annotation.
25+
*/
26+
class MicronautRepositoryClass extends RefType {
27+
MicronautRepositoryClass() {
28+
this.getAnAnnotation().getType() instanceof MicronautRepositoryAnnotation
29+
or
30+
this.getAnAncestor().hasQualifiedName("io.micronaut.data.repository", "GenericRepository")
31+
}
32+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Provides classes for identifying Micronaut Security annotations.
3+
*
4+
* Micronaut Security provides the `@Secured` annotation and integrates
5+
* with standard `@RolesAllowed` for method-level access control.
6+
*/
7+
overlay[local?]
8+
module;
9+
10+
import java
11+
12+
/**
13+
* The annotation type `@Secured` from `io.micronaut.security.annotation`.
14+
*/
15+
class MicronautSecuredAnnotation extends AnnotationType {
16+
MicronautSecuredAnnotation() {
17+
this.hasQualifiedName("io.micronaut.security.annotation", "Secured")
18+
}
19+
}
20+
21+
/**
22+
* A callable (method or constructor) that is annotated with Micronaut's `@Secured`
23+
* annotation, either directly or via its declaring type.
24+
*/
25+
class MicronautSecuredCallable extends Callable {
26+
MicronautSecuredCallable() {
27+
this.getAnAnnotation().getType() instanceof MicronautSecuredAnnotation
28+
or
29+
this.getDeclaringType().getAnAnnotation().getType() instanceof MicronautSecuredAnnotation
30+
}
31+
}
32+
33+
/**
34+
* A class annotated with Micronaut's `@Secured` annotation.
35+
*/
36+
class MicronautSecuredClass extends Class {
37+
MicronautSecuredClass() { this.getAnAnnotation().getType() instanceof MicronautSecuredAnnotation }
38+
}

0 commit comments

Comments
 (0)