Skip to content

Commit a27cc21

Browse files
committed
fix: prevent denial of service submitting form-url-encoded payload
cherry pick #12410 for Micronaut 3
1 parent 94eacf7 commit a27cc21

3 files changed

Lines changed: 122 additions & 1 deletion

File tree

json-core/src/main/java/io/micronaut/json/bind/JsonBeanPropertyBinder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ private ObjectBuilder getOrCreateNodeAtIndex(ArrayBuilder arrayNode, int arrayIn
265265

266266
private void expandArrayToThreshold(int arrayIndex, ArrayBuilder arrayNode) {
267267
if (arrayIndex < arraySizeThreshhold) {
268-
while (arrayNode.values.size() != arrayIndex + 1) {
268+
while (arrayNode.values.size() < arrayIndex + 1) {
269269
arrayNode.values.add(FixedValue.NULL);
270270
}
271271
}

test-suite/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ dependencies {
8787

8888
testFixturesApi libs.managed.spock
8989
testFixturesApi libs.managed.groovy
90+
testImplementation(libs.junit.jupiter.params)
9091
}
9192

9293
//tasks.withType(Test).configureEach {
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package io.micronaut.json.bind;
2+
3+
import io.micronaut.context.annotation.Property;
4+
import io.micronaut.context.annotation.Requires;
5+
import io.micronaut.core.annotation.Introspected;
6+
import io.micronaut.http.HttpRequest;
7+
import io.micronaut.http.MediaType;
8+
import io.micronaut.http.annotation.Body;
9+
import io.micronaut.http.annotation.Controller;
10+
import io.micronaut.http.annotation.Post;
11+
import io.micronaut.http.client.BlockingHttpClient;
12+
import io.micronaut.http.client.HttpClient;
13+
import io.micronaut.http.client.annotation.Client;
14+
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
15+
import jakarta.inject.Inject;
16+
import org.junit.jupiter.params.ParameterizedTest;
17+
import org.junit.jupiter.params.provider.ValueSource;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.Objects;
22+
23+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
24+
import static org.junit.jupiter.api.Assertions.assertEquals;
25+
26+
@Property(name = "spec.name", value = "JsonBeanPropertyBinderDenialOfServiceTest")
27+
@MicronautTest
28+
class JsonBeanPropertyBinderDenialOfServiceTest {
29+
30+
@Inject
31+
@Client("/") HttpClient httpClient;
32+
33+
@ParameterizedTest
34+
@ValueSource(strings = {
35+
"authors%5B0%5D.name=low&authors%5B1%5D.name=high",
36+
"authors%5B1%5D.name=high&authors%5B0%5D.name=low"
37+
})
38+
void submittingAFormUrlEncodedPayloadShouldNotCreateAnOutOfMemoryError(String body) {
39+
BlockingHttpClient client = httpClient.toBlocking();
40+
HttpRequest<?> request = HttpRequest.POST("/poc/book", body)
41+
.contentType(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
42+
Book b = assertDoesNotThrow(() -> client.retrieve(request, Book.class));
43+
Book expected = expected();
44+
assertEquals(expected, b);
45+
}
46+
47+
private static Book expected() {
48+
Book expected = new Book();
49+
Author firstAuthor = new Author();
50+
firstAuthor.setName("low");
51+
Author secondAuthor = new Author();
52+
secondAuthor.setName("high");
53+
List<Author> authors = new ArrayList<>();
54+
authors.add(firstAuthor);
55+
authors.add(secondAuthor);
56+
expected.setAuthors(authors);
57+
return expected;
58+
}
59+
60+
@Requires(property = "spec.name", value = "JsonBeanPropertyBinderDenialOfServiceTest")
61+
@Controller("/poc")
62+
static class PocController {
63+
@Post(uri = "/book", consumes = MediaType.APPLICATION_FORM_URLENCODED)
64+
Book bind(@Body Book book) { return book; }
65+
}
66+
67+
@Introspected
68+
static class Author {
69+
private String name;
70+
public String getName() { return name; }
71+
public void setName(String name) { this.name = name; }
72+
73+
@Override
74+
public final boolean equals(Object o) {
75+
if (!(o instanceof Author)) return false;
76+
77+
Author author = (Author) o;
78+
return Objects.equals(name, author.name);
79+
}
80+
81+
@Override
82+
public int hashCode() {
83+
return Objects.hashCode(name);
84+
}
85+
86+
@Override
87+
public String toString() {
88+
return "Author{" +
89+
"name='" + name + '\'' +
90+
'}';
91+
}
92+
}
93+
94+
@Introspected
95+
static class Book {
96+
private List<Author> authors;
97+
public List<Author> getAuthors() { return authors; }
98+
public void setAuthors(List<Author> authors) { this.authors = authors; }
99+
100+
@Override
101+
public final boolean equals(Object o) {
102+
if (!(o instanceof Book)) return false;
103+
104+
Book book = (Book) o;
105+
return Objects.equals(authors, book.authors);
106+
}
107+
108+
@Override
109+
public int hashCode() {
110+
return Objects.hashCode(authors);
111+
}
112+
113+
@Override
114+
public String toString() {
115+
return "Book{" +
116+
"authors=" + authors +
117+
'}';
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)