I am trying to test my Scala Play application through a Unit Test, but the behaviour I am experiencing in my test is different to the behaviour I experience when sending a POST request to my route through Postman.
I am trying to test that the Scala Play application will handle any XML which contains DTD, as I am trying to prove that I am protected against XXE attacks. According to James Roper, Play was patched to reject XML containing DTD by default due to potential vulnerabilities.
My problem is that when I hit my route, I get a 400: Bad Request
error as expected due to the Play application catching the DTD. However, when I try to hit the route through my Unit Tests, I get a 200: Ok
, so the tests are behaving differently to the actual Play Application.
Here is my Controller code:
def handlePost(): Action[NodeSeq] = Action.async(parse.xml) {
implicit request =>
Future.successful(Ok(request.body))
}
At some point behind the scenes, POSTing to this Controller seems to fail at Action.async(parse.xml)
. I have also swapped out request.body
with some other random XML to verify that it fails before even touching the request body in my code.
My test case:
class RoutesSpec extends PlaySpec with GuiceOneAppPerSuite {
"The POST route" must {
"not handle XXE XML" in {
val xml: Elem = scala.xml.XML.loadString(
"""<?xml version="1.0" encoding="utf-8"?>
|<!DOCTYPE foo [
|<!ELEMENT foo (bar)>
| <!ELEMENT bar (#PCDATA)>
|]>
|<foo>
| <bar>string</bar>
|</foo>
""".stripMargin)
val Some(result) = route(app, FakeRequest(POST_REQUEST, "/my-route")
.withXmlBody(xml))
status(result) mustEqual 400 // currently returns 200
}
}
}
I have two questions:
OR
XML.loadString
is also vulnerable and am aware of how to fix it. Is my test misbehaving at this line, and if so, is there an alternative way to blindly pass XML to my test method without evaluating it and interpreting the DTD?scala.xml.Unparsed
seems to do the job. I think it was the vulnerability in scala.xml.XML.loadString
which was tripping me up.
class RoutesSpec extends PlaySpec with GuiceOneAppPerSuite {
"The POST route" must {
"not handle XXE XML" in {
val xml = scala.xml.Unparsed(
"""<?xml version="1.0" encoding="utf-8"?>
|<!DOCTYPE foo [
|<!ELEMENT foo (bar)>
| <!ELEMENT bar (#PCDATA)>
|]>
|<foo>
| <bar>string</bar>
|</foo>
""".stripMargin)
val Some(result) = route(app, FakeRequest(POST_REQUEST, "/my-route")
.withXmlBody(xml))
status(result) mustEqual 400
contentAsString(result) mustBe """{"statusCode":400,"message":"bad request"}"""
}
}
}