Play Framework is popular among Java and Scala developers for building fast, scalable web applications. However, from version 2.8.3 to 2.8.15, a subtle yet dangerous vulnerability was discovered in its forms library. Known as CVE-2022-31018, this issue can crash your whole app and cause a severe denial of service.

Let's break down what happened, how you can exploit it, and—most importantly—how you can fix or avoid it.

What’s the Vulnerability?

The problem lies in how Play’s forms library handles JSON data binding through the following common API methods:

- Scala/Java: Form#bindFromRequest
- Scala/Java: Form#bind

If the incoming JSON is deeply nested—think JSON inside JSON inside JSON, and so on—the library tries to process the entire object. This can eat up all available system memory, leading to a java.lang.OutOfMemoryError. In typical Play configurations, this results in the app crashing outright.

What Triggers It?: Deep, complex JSON data sent to your forms.

- What Happens?: Play tries to ‘unpack’ the whole thing, which eats up all server RAM and crashes your application.

Vulnerable Controller Example (Scala)

import play.api.data._
import play.api.data.Forms._
import play.api.mvc._

case class UserData(name: String)

object UserForm {
  val form = Form(
    mapping(
      "name" -> text
    )(UserData.apply)(UserData.unapply)
  )
}

class UserController(cc: ControllerComponents) extends AbstractController(cc) {
  def submitUser = Action { implicit request =>
    // This is vulnerable!
    val user = UserForm.form.bindFromRequest()
    Ok("Processed")
  }
}

An attacker sends a POST request like this

{
  "name": {"a": {"b": {"c": {"d": ... { "z": "boom" } } } } }
}

*Just repeat the nesting dozens or hundreds of times. This is easily scripted.*

What Exactly Happens in Play?

The Form binders in Play recursively walk through every nested level in the JSON. Since there's no default limit in the affected versions, the app keeps digging deeper, allocating new objects for every level. Given enough layers (which is easy to automate), the JVM runs out of heap memory, and the process dies with an OutOfMemoryError.

If your app is running on the default dispatcher, and the default config of akka.jvm-exit-on-fatal-error is true (which is the case unless you change it), THE WHOLE SERVER PROCESS CRASHES.

Risk: Simple request can permanently crash your app, causing downtime.

- Who can exploit? Anyone who can POST data to your server. No authentication needed, in most cases.

Official Patch & References

This was fixed in Play Framework 2.8.16. After the fix, there is now a global limit on the JSON parsing depth (default to 50), which you can change if necessary.

References

- Play Framework Security Advisory
- Official Release Notes
- NVD - CVE-2022-31018 Entry

If you’re not expecting JSON data, switch to a different body parser. For example

def submitUser = Action(parse.formUrlEncoded) { implicit request =>
  val user = UserForm.form.bindFromRequest()
  Ok("Processed safely")
}

2. Filter Incoming Request Depth

You could implement custom middleware to reject requests with excessive JSON depth (though the patch is the best).

TL;DR (Summary)

- CVE-2022-31018 allows attackers to crash any Play Framework app using forms and JSON on versions 2.8.3 – 2.8.15.

Update to Play 2.8.16 or newer ASAP, or use body parsers that reject JSON.

- See the Security Advisory for full details.


Stay safe: always keep your dependencies up to date, and don’t trust incoming data—even if it looks harmless!

Timeline

Published on: 06/02/2022 17:15:00 UTC
Last modified on: 06/13/2022 12:26:00 UTC