Skip to content

Commit a650c4d

Browse files
authored
fix: prevent denial of service submitting form-url-encoded payload (#12411)
* fix: prevent denial of service submitting form-url-encoded payload cherry pick #12410 for Micronaut 3 * ci: update to github actions v4 * actions/setup-java@v4 * ignore SslRefreshSpec * use commonhaus micronaut website is down * use gradle/action/setup-gradle As of v3 this action has been superceded by gradle/actions/setup-gradle. Any workflow that uses gradle/gradle-build-action@v3 will transparently delegate to gradle/actions/setup-gradle@v3. Users are encouraged to update their workflows, replacing: uses: gradle/gradle-build-action@v3 with uses: gradle/actions/setup-gradle@v3 See the setup-gradle documentation for up-to-date documentation for gradle/actions/setup-gradle. * use gradle/actions/wrapper-validation As of v3 this action has been superceded by gradle/actions/wrapper-validation. Any workflow that uses gradle/wrapper-validation-action@v3 will transparently delegate to gradle/actions/wrapper-validation@v3. Users are encouraged to update their workflows, replacing: uses: gradle/wrapper-validation-action@v3 with uses: gradle/actions/wrapper-validation@v3 See the wrapper-validation documentation for up-to-date documentation for gradle/actions/wrapper-validation. * upload binary compatibility results only if java 17
1 parent 94eacf7 commit a650c4d

10 files changed

Lines changed: 148 additions & 26 deletions

File tree

.github/workflows/central-sync.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ jobs:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- name: Checkout repository
18-
uses: actions/checkout@v3
18+
uses: actions/checkout@v4
1919
with:
2020
ref: v${{ github.event.inputs.release_version }}
21-
- uses: gradle/wrapper-validation-action@v1
21+
- uses: gradle/actions/wrapper-validation@v5
2222
- name: Set up JDK
23-
uses: actions/setup-java@v3
23+
uses: actions/setup-java@v4
2424
with:
2525
distribution: 'adopt'
2626
java-version: '11'

.github/workflows/graalvm.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ jobs:
2727
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
2828
sudo apt-get clean
2929
df -h
30-
- uses: actions/checkout@v3
31-
- uses: actions/cache@v3
30+
- uses: actions/checkout@v4
31+
- uses: actions/cache@v4
3232
with:
3333
path: ~/.gradle/caches
3434
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
@@ -42,7 +42,7 @@ jobs:
4242
components: 'native-image'
4343
github-token: ${{ secrets.GITHUB_TOKEN }}
4444
- name: Setup Gradle
45-
uses: gradle/gradle-build-action@v2.3.3
45+
uses: gradle/actions/setup-gradle@v5
4646
- name: Build with Gradle
4747
id: gradle
4848
run: |

.github/workflows/gradle.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ jobs:
3838
df -h
3939
4040
- name: "📥 Checkout repository"
41-
uses: actions/checkout@v3
41+
uses: actions/checkout@v4
4242
with:
4343
fetch-depth: 0
4444
distribution: 'temurin'
4545
java-version: ${{ matrix.java }}
4646

4747
- name: "🔧 Setup Gradle"
48-
uses: gradle/gradle-build-action@v2
48+
uses: gradle/actions/setup-gradle@v5
4949

5050
- name: "❓ Optional setup step"
5151
run: |
@@ -65,8 +65,8 @@ jobs:
6565
check_retries: 'true'
6666

6767
- name: "📜 Upload binary compatibility check results"
68-
if: always()
69-
uses: actions/upload-artifact@v3
68+
if: matrix.java == '17'
69+
uses: actions/upload-artifact@v4
7070
with:
7171
name: binary-compatibility-reports
7272
path: "**/build/reports/binary-compatibility-*.html"

.github/workflows/publish-snapshot.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ jobs:
1010
if: github.repository != 'micronaut-projects/micronaut-project-template'
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v3
14-
- uses: actions/cache@v3
13+
- uses: actions/checkout@v4
14+
- uses: actions/cache@v4
1515
with:
1616
path: ~/.gradle/caches
1717
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
1818
restore-keys: |
1919
${{ runner.os }}-gradle-
2020
- name: Set up JDK
21-
uses: actions/setup-java@v3
21+
uses: actions/setup-java@v4
2222
with:
2323
java-version: '11'
2424
distribution: 'temurin'

.github/workflows/release.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ jobs:
1414
runs-on: ubuntu-latest
1515
steps:
1616
- name: Checkout repository
17-
uses: actions/checkout@v3
17+
uses: actions/checkout@v4
1818
with:
1919
token: ${{ secrets.GH_TOKEN }}
20-
- uses: gradle/wrapper-validation-action@v1
20+
- uses: gradle/actions/wrapper-validation@v5
2121
- name: Set up JDK
22-
uses: actions/setup-java@v3
22+
uses: actions/setup-java@v4
2323
with:
2424
java-version: '11'
2525
distribution: 'temurin'
@@ -66,13 +66,13 @@ jobs:
6666
# Store the hash in a file, which is uploaded as a workflow artifact.
6767
echo $(sha256sum $ARTIFACTS | base64 -w0) > artifacts-sha256
6868
- name: Upload build artifacts
69-
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
69+
uses: actions/upload-artifact@4
7070
with:
7171
name: gradle-build-outputs
7272
path: build/repo/${{ steps.publish.outputs.group }}/*/${{ steps.publish.outputs.version }}/*
7373
retention-days: 5
7474
- name: Upload artifacts-sha256
75-
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
75+
uses: actions/upload-artifact@4
7676
with:
7777
name: artifacts-sha256
7878
path: artifacts-sha256
@@ -101,7 +101,7 @@ jobs:
101101
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }}
102102
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
103103
- name: Checkout micronaut-core
104-
uses: actions/checkout@v3
104+
uses: actions/checkout@v4
105105
with:
106106
token: ${{ secrets.GH_TOKEN }}
107107
repository: micronaut-projects/micronaut-core
@@ -130,7 +130,7 @@ jobs:
130130
artifacts-sha256: ${{ steps.set-hash.outputs.artifacts-sha256 }}
131131
steps:
132132
- name: Download artifacts-sha256
133-
uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1
133+
uses: actions/download-artifact@v4
134134
with:
135135
name: artifacts-sha256
136136
# The SLSA provenance generator expects the hash digest of artifacts to be passed as a job
@@ -161,9 +161,9 @@ jobs:
161161
if: startsWith(github.ref, 'refs/tags/')
162162
steps:
163163
- name: Checkout repository
164-
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
164+
uses: actions/checkout@v4
165165
- name: Download artifacts
166-
uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1
166+
uses: actions/download-artifact@v4
167167
with:
168168
name: gradle-build-outputs
169169
path: build/repo

http-client/src/test/groovy/io/micronaut/http/client/SslRefreshSpec.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import io.micronaut.runtime.context.scope.refresh.RefreshEvent
1515
import io.micronaut.runtime.server.EmbeddedServer
1616
import io.netty.handler.ssl.SslHandler
1717
import spock.lang.AutoCleanup
18+
import spock.lang.Ignore
1819
import spock.lang.Shared
1920
import spock.lang.Specification
2021

2122
import java.security.cert.X509Certificate
2223

24+
@Ignore
2325
class SslRefreshSpec extends Specification {
2426

2527
@Shared List<String> ciphers = ['TLS_RSA_WITH_AES_128_CBC_SHA',
@@ -51,7 +53,6 @@ class SslRefreshSpec extends Specification {
5153
.run(EmbeddedServer)
5254
@Shared @AutoCleanup HttpClient client = embeddedServer.applicationContext
5355
.createBean(HttpClient, embeddedServer.getURI())
54-
5556
void "test ssl refresh"() {
5657
when:
5758
def response = client.toBlocking().exchange(HttpRequest.GET('/ssl/refresh'), Map)

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 {

test-suite/src/test/groovy/io/micronaut/docs/http/client/proxy/ClientProxySpec.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import spock.lang.Specification
2121
class ClientProxySpec extends Specification {
2222

2323
static final int PROXY_PORT = 3128
24-
static final String URL = "https://micronaut.io/"
25-
static final String HTML_FRAGMENT = 'Home - Micronaut Framework'
24+
static final String URL = "https://www.commonhaus.org"
25+
static final String HTML_FRAGMENT = 'Commonhaus Foundation'
2626

2727
@AutoCleanup
2828
GenericContainer proxyContainer =
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)