diff --git a/changelog.d/19405.misc b/changelog.d/19405.misc new file mode 100644 index 0000000000..f3be5b2027 --- /dev/null +++ b/changelog.d/19405.misc @@ -0,0 +1 @@ +Disallow requests to the health endpoint from containing trailing path characters. \ No newline at end of file diff --git a/synapse/rest/health.py b/synapse/rest/health.py index ae7cab7a2d..9c7a846076 100644 --- a/synapse/rest/health.py +++ b/synapse/rest/health.py @@ -22,6 +22,8 @@ from twisted.web.resource import Resource from twisted.web.server import Request +from synapse.api.errors import Codes + class HealthResource(Resource): """A resource that does nothing except return a 200 with a body of `OK`, @@ -34,5 +36,15 @@ class HealthResource(Resource): isLeaf = 1 def render_GET(self, request: Request) -> bytes: + # Prevent path traversal by ensuring the request path is exactly /health. + if request.path != b"/health": + request.setResponseCode(404) + body = ( + '{"errcode":"' + + Codes.UNRECOGNIZED + + '","error":"Unrecognized request"}' + ) + return body.encode("utf-8") + request.setHeader(b"Content-Type", b"text/plain") return b"OK" diff --git a/tests/rest/test_health.py b/tests/rest/test_health.py index bdbfce796a..17249b4eae 100644 --- a/tests/rest/test_health.py +++ b/tests/rest/test_health.py @@ -33,3 +33,16 @@ class HealthCheckTests(unittest.HomeserverTestCase): self.assertEqual(channel.code, 200) self.assertEqual(channel.result["body"], b"OK") + + def test_health_path_traversal(self) -> None: + """ + Test that the health endpoint does not allow extra path segments, + which could be used to access other resources. + + Regression test for: https://github.com/element-hq/synapse/issues/19395 + """ + channel = self.make_request("GET", "/health/extra/path", shorthand=False) + + self.assertEqual(channel.code, 404) + self.assertEqual(channel.json_body["errcode"], "M_UNRECOGNIZED") + self.assertIn("error", channel.json_body)