Insecure or unset HTTP headers - Accept
Need
Declaration and verification of the expected media type in HTTP exchanges
Context
• Usage of Kotlin 1.9+ on the JVM for building application services
• Usage of java.net.HttpURLConnection for outbound HTTP requests
Description
1. Non compliant code
import java.net.HttpURLConnection
import java.net.URI
fun fetchInvoice(id: String): String {
val connection = URI("https://billing.example.com/invoices/$id").toURL().openConnection() as HttpURLConnection
// Any media type is accepted and read as JSON
connection.setRequestProperty("Accept", "*/*")
return connection.inputStream.bufferedReader().use { it.readText() }...The `fetchInvoice` function below calls a partner API with `HttpURLConnection` and sends `Accept` with the value `*/*`, then reads the body as if it were JSON. Accepting any media type means the client has no contract with the server about what it receives. If the partner endpoint is misconfigured, compromised or replaced through a redirect, it can answer with HTML, XML or an executable payload, and the application passes that content to its JSON parser, caches it or forwards it to its own users. Content that is later served back with the wrong type is a common path to cross-site scripting and to parser attacks such as XML external entities. Omitting the `Accept` header entirely has the same effect, since most servers treat it as `*/*`.
2. Steps
• Send an `Accept` header with the specific media type the client can process, such as `application/json`, instead of `*/*`.
• Verify the `Content-Type` and status of every response before parsing the body.
• Declare `consumes` and `produces` on server endpoints and reject unexpected types with `415 Unsupported Media Type`.
• Send `X-Content-Type-Options` with the value `nosniff` from servers so browsers do not guess content types.
3. Secure code example
import java.net.HttpURLConnection
import java.net.URI
fun fetchInvoice(id: String): String {
val connection = URI("https://billing.example.com/invoices/$id").toURL().openConnection() as HttpURLConnection
connection.setRequestProperty("Accept", "application/json")
val contentType = connection.contentType.orEmpty()
check(connection.responseCode == HttpURLConnection.HTTP_OK) { "Unexpected status ${connection.responseCode}" }...The corrected function sends `Accept: application/json`, declaring the only media type it can process, and verifies the `Content-Type` of the response before reading the body. Anything that is not JSON raises an error instead of reaching the parser or the rest of the application. It also rejects responses whose status is not `200 OK`, so error pages returned by proxies or gateways are never treated as data. On the server side, the same rule applies in reverse: endpoints should declare the types they consume and produce, and reject requests with unexpected `Content-Type` values with `415 Unsupported Media Type`.
References
• 153. Insecure or unset HTTP headers - Accept