diff --git a/Apps/DnsRebindBlockingApp/App.cs b/Apps/DnsRebindBlockingApp/App.cs new file mode 100644 index 00000000..6602ef87 --- /dev/null +++ b/Apps/DnsRebindBlockingApp/App.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using DnsServerCore.ApplicationCommon; +using TechnitiumLibrary.Net; +using TechnitiumLibrary.Net.Dns; +using TechnitiumLibrary.Net.Dns.ResourceRecords; + +namespace DnsRebindBlocking +{ + public class App: IDnsApplication, IDnsPostProcessor + { + private AppConfig Config = null!; + private HashSet PrivateNetworks = new(); + private IDnsServer DnsServer = null!; + + public void Dispose() + { + // Nothing to dispose of. + } + + public Task InitializeAsync(IDnsServer dnsServer, string config) + { + DnsServer = dnsServer; + Config = JsonSerializer.Deserialize(config, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + })!; + DnsServer.WriteLog($"Initializing. Enabled: {Config.Enabled}"); + PrivateNetworks.Clear(); + foreach (var privateNetwork in Config.PrivateNetworks) + { + var success = NetworkAddress.TryParse(privateNetwork, out NetworkAddress networkAddress); + PrivateNetworks.Add(networkAddress); + } + + // Add the ServerDomain to the PrivateDomains list so it doesn't block it's own. + Config.PrivateDomains.Add(DnsServer.ServerDomain); + + return Task.CompletedTask; + } + + public string Description => "Block DNS responses with protected IP ranges to prevent DNS rebinding attacks."; + + public Task PostProcessAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, DnsDatagram response) + { + if (!Config.Enabled) + return Task.FromResult(response); + + var answers = response.Answer.Where(res => !IsFilteredRebind(res)).ToList(); + var additional = response.Additional.Where(res => !IsFilteredRebind(res)).ToList(); + + return Task.FromResult(response.Clone(answers, response.Authority, additional)); + } + + private bool IsFilteredRebind(DnsResourceRecord record) + { + if (record.Type != DnsResourceRecordType.A && record.Type != DnsResourceRecordType.AAAA) + return false; + IPAddress address; + switch (record.Type) + { + case DnsResourceRecordType.A: + address = ((DnsARecordData)record.RDATA).Address; + break; + case DnsResourceRecordType.AAAA: + address = ((DnsAAAARecordData)record.RDATA).Address; + break; + default: + return false; + } + + var isPrivateNetwork = PrivateNetworks.Any(net => net.Contains(address)); + var isPrivateDomain = IsZoneFound(Config.PrivateDomains, record.Name, out _); + return isPrivateNetwork && !isPrivateDomain; + } + + private static string? GetParentZone(string domain) + { + var i = domain.IndexOf('.'); + //dont return root zone + return i > -1 ? domain[(i + 1)..] : null; + } + + private static bool IsZoneFound(IReadOnlySet domains, string domain, out string? foundZone) + { + var currentDomain = domain.ToLower(); + do + { + if (domains.Contains(currentDomain)) + { + foundZone = currentDomain; + return true; + } + + currentDomain = GetParentZone(currentDomain); + } + while (currentDomain is not null); + + foundZone = null; + return false; + } + } +} \ No newline at end of file diff --git a/Apps/DnsRebindBlockingApp/AppConfig.cs b/Apps/DnsRebindBlockingApp/AppConfig.cs new file mode 100644 index 00000000..fdc0e4b1 --- /dev/null +++ b/Apps/DnsRebindBlockingApp/AppConfig.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace DnsRebindBlocking; + +public class AppConfig +{ + public required bool Enabled { get; set; } + public required List PrivateNetworks { get; init; } = new(); + public required HashSet PrivateDomains { get; init; } = new(); +} \ No newline at end of file diff --git a/Apps/DnsRebindBlockingApp/DnsRebindBlockingApp.csproj b/Apps/DnsRebindBlockingApp/DnsRebindBlockingApp.csproj new file mode 100644 index 00000000..5f3edd9c --- /dev/null +++ b/Apps/DnsRebindBlockingApp/DnsRebindBlockingApp.csproj @@ -0,0 +1,43 @@ + + + + net7.0 + false + true + 1.0.0 + Technitium + Technitium DNS Server + Shreyas Zare, Rui Fung Yip + DnsRebindBlocking + https://technitium.com/dns/ + https://github.com/TechnitiumSoftware/DnsServer + Blocks DNS rebinding attacks using configured private domains and networks. + false + Library + enable + + + + + false + + + + + + ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll + false + + + ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll + false + + + + + + PreserveNewest + + + + diff --git a/Apps/DnsRebindBlockingApp/dnsApp.config b/Apps/DnsRebindBlockingApp/dnsApp.config new file mode 100644 index 00000000..6e50535e --- /dev/null +++ b/Apps/DnsRebindBlockingApp/dnsApp.config @@ -0,0 +1,14 @@ +{ + "enabled": false, + "privateNetworks": [ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "169.254.0.0/16", + "fc00::/7", + "fe80::/10" + ], + "privateDomains": [ + "home.arpa" + ] +} \ No newline at end of file diff --git a/DnsServer.sln b/DnsServer.sln index bee88d1d..b0aba57b 100644 --- a/DnsServer.sln +++ b/DnsServer.sln @@ -55,6 +55,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZoneAliasApp", "Apps\ZoneAl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DefaultRecordsApp", "Apps\DefaultRecordsApp\DefaultRecordsApp.csproj", "{BCF0373F-540D-4E9E-A8AD-70CEE9B385A6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DnsRebindBlockingApp", "Apps\DnsRebindBlockingApp\DnsRebindBlockingApp.csproj", "{6F3E5B6B-7EA7-483D-8736-41ED92404B88}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -161,6 +163,10 @@ Global {BCF0373F-540D-4E9E-A8AD-70CEE9B385A6}.Debug|Any CPU.Build.0 = Debug|Any CPU {BCF0373F-540D-4E9E-A8AD-70CEE9B385A6}.Release|Any CPU.ActiveCfg = Release|Any CPU {BCF0373F-540D-4E9E-A8AD-70CEE9B385A6}.Release|Any CPU.Build.0 = Release|Any CPU + {6F3E5B6B-7EA7-483D-8736-41ED92404B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F3E5B6B-7EA7-483D-8736-41ED92404B88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F3E5B6B-7EA7-483D-8736-41ED92404B88}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F3E5B6B-7EA7-483D-8736-41ED92404B88}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -186,6 +192,7 @@ Global {29688452-F88A-49F5-9C98-BE7B2812C522} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2} {6FF8C5F7-C98E-41C1-8FCD-25AEA0057673} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2} {BCF0373F-540D-4E9E-A8AD-70CEE9B385A6} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2} + {6F3E5B6B-7EA7-483D-8736-41ED92404B88} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6747BB6D-2826-4356-A213-805FBCCF9201}