new file: changelog_unreleased/4557-f-153-kea-dhcp4-post-scrub-hostname-with-empty-dns-label-bypasses-cve-2025-11232-fix-causes
modified: src/bin/dhcp4/dhcp4_messages.cc
modified: src/bin/dhcp4/dhcp4_messages.h
modified: src/bin/dhcp4/dhcp4_messages.mes
modified: src/bin/dhcp4/dhcp4_srv.cc
modified: src/bin/dhcp4/tests/fqdn_unittest.cc
modified: src/lib/dhcpsrv/d2_client_mgr.h
modified: src/lib/dhcpsrv/tests/d2_client_unittest.cc
--- /dev/null
+[bug] tmark
+ Kea now logs and drops host names and FQDNs
+ when host name sanitization results in
+ embedded empty labels (e.g. "aaa..bbb.com").
+ Applies to both kea-dhcp4 and kea-dhcp6.
+ Thank you to Qifan Zhang from Palo Alto Networks
+ for reporting the issue.
+ (Gitlab #4557)
extern const isc::log::MessageID DHCP4_RESPONSE_HOSTNAME_DATA = "DHCP4_RESPONSE_HOSTNAME_DATA";
extern const isc::log::MessageID DHCP4_RESPONSE_HOSTNAME_GENERATE = "DHCP4_RESPONSE_HOSTNAME_GENERATE";
extern const isc::log::MessageID DHCP4_ROOT_USER_SECURITY_WARNING = "DHCP4_ROOT_USER_SECURITY_WARNING";
+extern const isc::log::MessageID DHCP4_SANITIZED_HOSTNAME_MALFORMED = "DHCP4_SANITIZED_HOSTNAME_MALFORMED";
extern const isc::log::MessageID DHCP4_SECURITY_CHECKS_DISABLED = "DHCP4_SECURITY_CHECKS_DISABLED";
extern const isc::log::MessageID DHCP4_SERVER_FAILED = "DHCP4_SERVER_FAILED";
extern const isc::log::MessageID DHCP4_SERVER_INITIATED_DECLINE = "DHCP4_SERVER_INITIATED_DECLINE";
"DHCP4_RESPONSE_HOSTNAME_DATA", "%1: including Hostname option in the server's response: %2",
"DHCP4_RESPONSE_HOSTNAME_GENERATE", "%1: server has generated hostname %2 for the client",
"DHCP4_ROOT_USER_SECURITY_WARNING", "kea-dhcp4 running as root user!",
+ "DHCP4_SANITIZED_HOSTNAME_MALFORMED", "%1: hostname after sanitizing is malformed: %2",
"DHCP4_SECURITY_CHECKS_DISABLED", "Invoked with command line option -X, Security checks are disabled!!",
"DHCP4_SERVER_FAILED", "server failed: %1",
"DHCP4_SERVER_INITIATED_DECLINE", "%1: Lease for addr %2 has been found to be already in use. The lease will be unavailable for %3 seconds.",
extern const isc::log::MessageID DHCP4_RESPONSE_HOSTNAME_DATA;
extern const isc::log::MessageID DHCP4_RESPONSE_HOSTNAME_GENERATE;
extern const isc::log::MessageID DHCP4_ROOT_USER_SECURITY_WARNING;
+extern const isc::log::MessageID DHCP4_SANITIZED_HOSTNAME_MALFORMED;
extern const isc::log::MessageID DHCP4_SECURITY_CHECKS_DISABLED;
extern const isc::log::MessageID DHCP4_SERVER_FAILED;
extern const isc::log::MessageID DHCP4_SERVER_INITIATED_DECLINE;
An DHCPOFFER for the 0.0.0.0 address was generated for a client requesting
the v6-only-preferred (108) option but the option is not in the response as
expected: the erroneous response is dropped, the discover query is displayed.
+
+% DHCP4_SANITIZED_HOSTNAME_MALFORMED %1: hostname after sanitizing is malformed: %2
+Logged at debug log level 50.
+This debug message is issued when the value generated by the server after applying
+host name sanitization does not constitute a valid domain name. The first argument
+includes the client and transaction identification information. The second argument
+contains a description of the data error.
return;
}
+ try {
+ label_count = OptionDataTypeUtil::getLabelCount(tmp);
+ } catch (const std::exception& exc) {
+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_SANITIZED_HOSTNAME_MALFORMED)
+ .arg(ex.getQuery()->getLabel())
+ .arg(exc.what());
+ return;
+ }
+
hostname = tmp;
}
// Hostname should not be in the response.
ASSERT_FALSE(resp->getOption(DHO_HOST_NAME));
+
+ // Set the hostname option.
+ ASSERT_NO_THROW(client.includeHostname("aaa.___.bbb"));
+
+ // Send the DHCPDISCOVER and make sure that the server responded.
+ ASSERT_NO_THROW(client.doDiscover());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Should have logged that it was scrubbed empty.
+ log = "DHCP4_SANITIZED_HOSTNAME_MALFORMED";
+ EXPECT_EQ(1U, countFile(log));
+
+ // Hostname should not be in the response.
+ ASSERT_FALSE(resp->getOption(DHO_HOST_NAME));
}
// Verifies that when the FQDN option is scrubbed empty it is logged
ASSERT_FALSE(resp->getOption(DHO_FQDN));
}
-
} // end of anonymous namespace
}
std::string clean_name = ss.str();
- if (clean_name.empty() || clean_name == ".") {
+ if (clean_name.empty() || clean_name == "." ||
+ (clean_name.find("..") != std::string::npos)) {
isc_throw(FQDNScrubbedEmpty, client_name);
}
scenario.client_name_, scenario.name_type_);
Option4ClientFqdn response(request);
- mgr.adjustDomainName<Option4ClientFqdn>(request, response, *ddns_params_);
+ ASSERT_NO_THROW_LOG(mgr.adjustDomainName<Option4ClientFqdn>
+ (request, response, *ddns_params_));
EXPECT_EQ(scenario.expected_name_, response.getDomainName());
EXPECT_EQ(Option4ClientFqdn::FULL, response.getDomainNameType());
}
Option6ClientFqdn request(0, scenario.client_name_, scenario.name_type_);
Option6ClientFqdn response(request);
- mgr.adjustDomainName<Option6ClientFqdn>(request, response, *ddns_params_);
+ ASSERT_NO_THROW_LOG(mgr.adjustDomainName<Option6ClientFqdn>
+ (request, response, *ddns_params_));
EXPECT_EQ(scenario.expected_name_, response.getDomainName());
EXPECT_EQ(Option6ClientFqdn::FULL, response.getDomainNameType());
}
Option4ClientFqdn response(request);
ASSERT_THROW_MSG(mgr.adjustDomainName<Option4ClientFqdn>(request, response, *ddns_params_),
FQDNScrubbedEmpty, "___.");
+
+ Option4ClientFqdn request2(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "aaa.___.bbb", Option4ClientFqdn::FULL);
+
+ Option4ClientFqdn response2(request2);
+ ASSERT_THROW_MSG(mgr.adjustDomainName<Option4ClientFqdn>(request2, response2, *ddns_params_),
+ FQDNScrubbedEmpty, "aaa.___.bbb.");
}
/// @brief Tests adjustDomainName template method with Option4ClientFqdn
Option6ClientFqdn response(request);
ASSERT_THROW_MSG(mgr.adjustDomainName<Option6ClientFqdn>(request, response, *ddns_params_),
FQDNScrubbedEmpty, "___.");
+
+ Option6ClientFqdn request2(0, "aaa.___.bbb", Option6ClientFqdn::FULL);
+
+ Option6ClientFqdn response2(request2);
+ ASSERT_THROW_MSG(mgr.adjustDomainName<Option6ClientFqdn>(request2, response2, *ddns_params_),
+ FQDNScrubbedEmpty, "aaa.___.bbb.");
}
} // end of anonymous namespace