From 7be9b77a19b57a5390173097395b5eaced7e98b6 Mon Sep 17 00:00:00 2001 From: "Airis." <114083619+Airis101@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:05:22 +0800 Subject: [PATCH] fix: Allow configured non-loopback host addresses in DNS rebinding protection Previously, the DNS rebinding protection only allowed localhost and 127.0.0.1, which prevented the plugin from working when a non-loopback address was configured in the host setting. Changes: - Updated isValidOrigin(), isValidHost(), and isValidReferer() to accept the configured host address as an allowed host - Added configured host to CORS allowHost list when it's not localhost/127.0.0.1 - All validation functions now check against the configured host in addition to the default loopback addresses This fix ensures that when users configure a non-loopback address (e.g., 0.0.0.0 or a specific IP), requests from that address will pass validation while maintaining protection against DNS rebinding attacks. --- .../net/portswigger/mcp/KtorServerManager.kt | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/net/portswigger/mcp/KtorServerManager.kt b/src/main/kotlin/net/portswigger/mcp/KtorServerManager.kt index e91f0c7..d44400a 100644 --- a/src/main/kotlin/net/portswigger/mcp/KtorServerManager.kt +++ b/src/main/kotlin/net/portswigger/mcp/KtorServerManager.kt @@ -45,6 +45,9 @@ class KtorServerManager(private val api: MontoyaApi) : ServerManager { install(CORS) { allowHost("localhost:${config.port}") allowHost("127.0.0.1:${config.port}") + if (config.host.lowercase() !in setOf("localhost", "127.0.0.1")) { + allowHost("${config.host}:${config.port}") + } allowMethod(HttpMethod.Get) allowMethod(HttpMethod.Post) @@ -64,7 +67,7 @@ class KtorServerManager(private val api: MontoyaApi) : ServerManager { val referer = call.request.header("Referer") val userAgent = call.request.header("User-Agent") - if (origin != null && !isValidOrigin(origin)) { + if (origin != null && !isValidOrigin(origin, config.host)) { api.logging().logToOutput("Blocked DNS rebinding attack from origin: $origin") call.respond(HttpStatusCode.Forbidden) return@intercept @@ -74,13 +77,13 @@ class KtorServerManager(private val api: MontoyaApi) : ServerManager { return@intercept } - if (host != null && !isValidHost(host, config.port)) { + if (host != null && !isValidHost(host, config.port, config.host)) { api.logging().logToOutput("Blocked DNS rebinding attack from host: $host") call.respond(HttpStatusCode.Forbidden) return@intercept } - if (referer != null && !isValidReferer(referer)) { + if (referer != null && !isValidReferer(referer, config.host)) { api.logging().logToOutput("Blocked suspicious request from referer: $referer") call.respond(HttpStatusCode.Forbidden) return@intercept @@ -135,12 +138,13 @@ class KtorServerManager(private val api: MontoyaApi) : ServerManager { executor.awaitTermination(10, TimeUnit.SECONDS) } - private fun isValidOrigin(origin: String): Boolean { + private fun isValidOrigin(origin: String, configuredHost: String): Boolean { try { val url = URI(origin).toURL() val hostname = url.host.lowercase() - val allowedHosts = setOf("localhost", "127.0.0.1") + val allowedHosts = mutableSetOf("localhost", "127.0.0.1") + allowedHosts.add(configuredHost.lowercase()) return hostname in allowedHosts } catch (_: Exception) { @@ -159,13 +163,15 @@ class KtorServerManager(private val api: MontoyaApi) : ServerManager { return browserIndicators.any { userAgentLower.contains(it) } } - private fun isValidHost(host: String, expectedPort: Int): Boolean { + private fun isValidHost(host: String, expectedPort: Int, configuredHost: String): Boolean { try { val parts = host.split(":") val hostname = parts[0].lowercase() val port = if (parts.size > 1) parts[1].toIntOrNull() else null - val allowedHosts = setOf("localhost", "127.0.0.1") + val allowedHosts = mutableSetOf("localhost", "127.0.0.1") + allowedHosts.add(configuredHost.lowercase()) + if (hostname !in allowedHosts) { return false } @@ -180,12 +186,14 @@ class KtorServerManager(private val api: MontoyaApi) : ServerManager { } } - private fun isValidReferer(referer: String): Boolean { + private fun isValidReferer(referer: String, configuredHost: String): Boolean { try { val url = URI(referer).toURL() val hostname = url.host.lowercase() - val allowedHosts = setOf("localhost", "127.0.0.1") + val allowedHosts = mutableSetOf("localhost", "127.0.0.1") + allowedHosts.add(configuredHost.lowercase()) + return hostname in allowedHosts } catch (_: Exception) {