Reputation: 325
In Ktor, is there a way to limit size of data that can be attempted to be deserialized from JSON? Context is defending against denial-of-service attacks where a malicious client might try and send a huge payload to cause out-of-memory issues
I've used a similar capability in Play before (https://www.playframework.com/documentation/2.8.x/ScalaBodyParsers#Max-content-length). You can set the maximum globally, and also specifically override on individual routes.
Upvotes: 0
Views: 714
Reputation: 7109
Unfortunately, there is no built-in functionality in Ktor to limit the size of a body for deserialization. Still, you can write an interceptor for the ApplicationReceivePipeline
pipeline to replace the ByteReadChannel
(body bytes are read from this object) with another channel. The latter's implementation will count a total number of bytes read and throw an exception if that count exceeds some limit. Here is an example:
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.request.*
import io.ktor.routing.*
import io.ktor.serialization.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.util.pipeline.*
import io.ktor.utils.io.*
import kotlinx.coroutines.GlobalScope
fun main() {
embeddedServer(Netty, port = 2222) {
val beforeSerialization = PipelinePhase("")
receivePipeline.insertPhaseBefore(ApplicationReceivePipeline.Transform, beforeSerialization)
receivePipeline.intercept(beforeSerialization) { receive ->
if (subject.value is ByteReadChannel) {
val readChannel = subject.value as ByteReadChannel
val limit = 1024
var totalBytes = 0
val channel = GlobalScope.writer {
val byteArray = ByteArray(4088)
val bytesRead = readChannel.readAvailable(byteArray)
totalBytes += bytesRead
if (totalBytes > limit) {
throw IllegalStateException("Limit ($limit) for receiving exceeded. Read $totalBytes.")
}
channel.writeFully(byteArray, 0, bytesRead)
}.channel
proceedWith(ApplicationReceiveRequest(receive.typeInfo, channel, reusableValue = true))
}
}
install(ContentNegotiation) {
json()
}
routing {
post("/") {
val r = call.receive<MyData>()
println(r)
}
}
}.start(wait = true)
}
@kotlinx.serialization.Serializable
data class MyData(val x: String)
Upvotes: 1