GitHub Security Advisory: GHSA-43w5-mmxv-cpvh
Package: io.micronaut:micronaut-json-core (MAVEN)
Severity: HIGH (CVSS 0.0)
CWE: CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')
Vulnerable versions: < 3.8.13
Patched in: 3.8.13

Summary: Micronaut vulnerable to DoS via crafted form-urlencoded body binding with descending array indices

Details:
In `JsonBeanPropertyBinder::expandArrayToThreshold` in `io.micronaut:micronaut-json-core` before Micronaut 4 4.10.16 and in Micronaut 3 before 3.10.5 does not correctly handle descending array index order during form-urlencoded body binding, which allows remote attackers to cause a denial of service (non-terminating loop, CPU exhaustion, and OutOfMemoryError) via crafted indexed form parameters (e.g., `authors[1].name` followed by `authors[0].name`).

### Example

With such an application

```java
package dosform;

import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Consumes;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.annotation.Produces;

import java.net.URI;

@Controller
class HomeController {

    @Produces(MediaType.TEXT_HTML)
    @Get
    String index() {
        return """
                <!DOCTYPE html>
                <html>
                <head>
                <title></title>
                </head>
                <body>
    <form action="/submit" method="post">
      <label for="firstAuthor">Fist Author</label>
      <input id="firstAuthor" name="authors[0].name" type="text"/>

      <label for="secondAuthor">Second Author</label>
      <input id="secondAuthor" name="authors[1].name" type="text"/>
      
      <label for="thirdAuthor">Third Author</label>
      <input id="thirdAuthor" name="authors[2].name" type="text"/>

      <button type="submit">Submit</button>
    </form>
               
                </body>
                </html>
                """;
    }

    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Post("/submit")
    HttpResponse<?> submit(@Body Book book) {
        return HttpResponse.seeOther(URI.create("/"));
    }
}
package dosform;

import io.micronaut.core.annotation.Introspected;

import java.util.Objects;

@Introspected
public class Author {
    private String name;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @Override
    public final boolean equals(Object o) {
        if (!(o instanceof Author)) return false;

        Author author = (Author) o;
        return Objects.equals(name, author.name);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(name);
    }

    @Override
    public String toString() {
        return "Author{" +
                "name='" + name + '\'' +
                '}';
    }
}
package dosform;

import io.micronaut.core.annotation.Introspected;

import java.util.List;
import java.util.Objects;

@Introspected
public class Book {
    private List<Author> authors;
    public List<Author> getAuthors() { return authors; }
    public void setAuthors(List<Author> authors) { this.authors = authors; }

    @Override
    public final boolean equals(Object o) {
        if (!(o instance