Critical severity9.8NVD Advisory· Published Dec 12, 2024· Updated Apr 15, 2026
CVE-2024-55875
CVE-2024-55875
Description
http4k is a functional toolkit for Kotlin HTTP applications. Prior to version 5.41.0.0, there is a potential XXE (XML External Entity Injection) vulnerability when http4k handling malicious XML contents within requests, which might allow attackers to read local sensitive information on server, trigger Server-side Request Forgery and even execute code under some circumstances. Version 5.41.0.0 contains a patch for the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.http4k:http4k-format-xmlMaven | >= 5.0.0.0, < 5.41.0.0 | 5.41.0.0 |
org.http4k:http4k-format-xmlMaven | < 4.50.0.0 | 4.50.0.0 |
Patches
242baae81c5ab2 files changed · +139 −6
core/format/xml/src/main/kotlin/org/http4k/format/Xml.kt+17 −6 modified@@ -19,6 +19,7 @@ import org.w3c.dom.Document import java.io.InputStream import java.io.StringWriter import java.nio.ByteBuffer +import javax.xml.XMLConstants import javax.xml.parsers.DocumentBuilderFactory import javax.xml.transform.TransformerFactory import javax.xml.transform.dom.DOMSource @@ -39,9 +40,10 @@ object Xml : AutoMarshallingXml() { @JvmName("stringAsXmlToJsonElement") fun asXmlToJsonElement(input: String): JsonElement = input.asXmlToJsonElement() - fun String.asXmlDocument(): Document = + fun String.asXmlDocument(config: XmlParsingConfig = defaultXmlParsingConfig): Document = DocumentBuilderFactory .newInstance() + .apply(config) .newDocumentBuilder() .parse(byteInputStream()) @@ -63,14 +65,15 @@ object Xml : AutoMarshallingXml() { /** * Convenience function to write the object as XML to the message body and set the content type. */ - fun <IN : Any> BiDiLensSpec<IN, String>.xml() = map({ it.asXmlDocument() }, { it.asXmlString() }) + fun <IN : Any> BiDiLensSpec<IN, String>.xml(config: XmlParsingConfig = defaultXmlParsingConfig) = map({ it.asXmlDocument(config) }, { it.asXmlString() }) - fun asBiDiMapping() = - BiDiMapping<String, Document>({ it.asXmlDocument() }, { it.asXmlString() }) + fun asBiDiMapping(config: XmlParsingConfig = defaultXmlParsingConfig) = + BiDiMapping<String, Document>({ it.asXmlDocument(config) }, { it.asXmlString() }) fun Body.Companion.xml( description: String? = null, - contentNegotiation: ContentNegotiation = ContentNegotiation.None + contentNegotiation: ContentNegotiation = ContentNegotiation.None, + config: XmlParsingConfig = defaultXmlParsingConfig ): BiDiBodyLensSpec<Document> = httpBodyRoot( listOf(Meta(true, "body", ObjectParam, "body", description, emptyMap())), @@ -79,5 +82,13 @@ object Xml : AutoMarshallingXml() { ) .map(Body::payload) { Body(it) } .map(ByteBuffer::asString, String::asByteBuffer) - .map({ it.asXmlDocument() }, { it.asXmlString() }) + .map({ it.asXmlDocument(config) }, { it.asXmlString() }) +} + +typealias XmlParsingConfig = DocumentBuilderFactory.() -> Unit + +val defaultXmlParsingConfig: XmlParsingConfig = { + isExpandEntityReferences = false + setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "") + setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "") }
core/format/xml/src/test/kotlin/org/http4k/format/XmlSecurityTest.kt+122 −0 added@@ -0,0 +1,122 @@ +package org.http4k.format + +import com.natpryce.hamkrest.assertion.assertThat +import com.natpryce.hamkrest.equalTo +import org.http4k.core.Body +import org.http4k.core.ContentType.Companion.APPLICATION_XML +import org.http4k.core.HttpHandler +import org.http4k.core.Method +import org.http4k.core.Request +import org.http4k.core.Response +import org.http4k.core.Status.Companion.BAD_REQUEST +import org.http4k.core.Status.Companion.OK +import org.http4k.format.Xml.asXmlString +import org.http4k.format.Xml.xml +import org.http4k.lens.contentType +import org.http4k.server.ApacheServer +import org.http4k.server.asServer +import org.http4k.util.PortBasedTest +import org.junit.jupiter.api.Test +import org.w3c.dom.Document +import java.util.concurrent.atomic.AtomicBoolean + +class XmlSecurityTest : PortBasedTest { + + @Test + fun `does not expand external entity`() { + val websiteAccessed = AtomicBoolean(false) + + val maliciousWebsite = { _: Request -> + websiteAccessed.set(true); + Response(OK) + }.asServer(ApacheServer(0)).start() + + val requestBody = + """<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE root [<!ENTITY xxe SYSTEM "http://localhost:${maliciousWebsite.port()}">]> + <root>&xxe;</root> + """.trimIndent() + + val xmlLens = Body.xml().toLens() + + val app: HttpHandler = { request -> + try { + val xmlDocument: Document = xmlLens(request) + Response(OK).body(xmlDocument.asXmlString()) + } catch (e: Exception) { + Response(BAD_REQUEST).body("Invalid XML: ${e.message}") + } + } + + app(Request(Method.POST, "/").contentType(APPLICATION_XML).body(requestBody)) + assertThat(websiteAccessed.get(), equalTo(false)) + } + + @Test + fun `external schema is not loaded`() { + val websiteAccessed = AtomicBoolean(false) + + val maliciousWebsite = { _: Request -> + websiteAccessed.set(true); + Response(OK) + }.asServer(ApacheServer(0)).start() + + val requestBody = """ + <?xml version="1.0" encoding="UTF-8"?> + <user xmlns:xsi="http://localhost:${maliciousWebsite.port()}" + xsi:noNamespaceSchemaLocation="http://localhost:${maliciousWebsite.port()}"> + <name>John Doe</name> + <email>john@example.com</email> + <age>30</age> + </user> + """.trimIndent() + + val xmlLens = Body.xml().toLens() + + val app: HttpHandler = { request -> + try { + val xmlDocument: Document = xmlLens(request) + Response(OK).body(xmlDocument.asXmlString()) + } catch (e: Exception) { + Response(BAD_REQUEST).body("Invalid XML: ${e.message}") + } + } + + app(Request(Method.POST, "/").contentType(APPLICATION_XML).body(requestBody)) + assertThat(websiteAccessed.get(), equalTo(false)) + } + + @Test + fun `external dtd is not loaded`() { + val websiteAccessed = AtomicBoolean(false) + + val maliciousWebsite = { _: Request -> + websiteAccessed.set(true); + Response(OK) + }.asServer(ApacheServer(0)).start() + + val requestBody = """ + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE note SYSTEM "http://localhost:${maliciousWebsite.port()}"> + <note> + <to>Alice</to> + <from>Bob</from> + <message>Hello</message> + </note> + """.trimIndent() + + val xmlLens = Body.xml().toLens() + + val app: HttpHandler = { request -> + try { + val xmlDocument: Document = xmlLens(request) + Response(OK).body(xmlDocument.asXmlString()) + } catch (e: Exception) { + Response(BAD_REQUEST).body("Invalid XML: ${e.message}") + } + } + + app(Request(Method.POST, "/").contentType(APPLICATION_XML).body(requestBody)) + assertThat(websiteAccessed.get(), equalTo(false)) + } +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-7mj5-hjjj-8rgwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-55875ghsaADVISORY
- github.com/http4k/http4k/blob/25696dff2d90206cc1da42f42a1a8dbcdbcdf18c/core/format/xml/src/main/kotlin/org/http4k/format/Xml.ktnvdWEB
- github.com/http4k/http4k/commit/35297adc6d6aca4951d50d8cdf17ff87a8b19fbcnvdWEB
- github.com/http4k/http4k/security/advisories/GHSA-7mj5-hjjj-8rgwnvdWEB
News mentions
0No linked articles in our index yet.