mirror of
https://github.com/fergalmoran/DnsServer.git
synced 2026-02-05 15:33:57 +00:00
DnsRebindingProtection: updated implementation code for optimization.
This commit is contained in:
@@ -1,110 +0,0 @@
|
||||
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<NetworkAddress> 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<AppConfig>(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 var networkAddress);
|
||||
if (!success)
|
||||
DnsServer.WriteLog($"Invalid network address specified: {privateNetwork}");
|
||||
else
|
||||
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<DnsDatagram> PostProcessAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, DnsDatagram response)
|
||||
{
|
||||
// Do not filter authoritative responses. Because in this case any rebinding is intentional.
|
||||
if (!Config.Enabled || response.AuthoritativeAnswer)
|
||||
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.RDATA)
|
||||
{
|
||||
case DnsARecordData data:
|
||||
address = data.Address;
|
||||
break;
|
||||
case DnsAAAARecordData data:
|
||||
address = data.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<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DnsRebindBlocking;
|
||||
|
||||
public class AppConfig
|
||||
{
|
||||
public required bool Enabled { get; set; }
|
||||
public required List<string> PrivateNetworks { get; init; } = new();
|
||||
public required HashSet<string> PrivateDomains { get; init; } = new();
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Version>1.0.0</Version>
|
||||
<Company>Technitium</Company>
|
||||
<Product>Technitium DNS Server</Product>
|
||||
<Authors>Shreyas Zare, Rui Fung Yip</Authors>
|
||||
<RootNamespace>DnsRebindBlocking</RootNamespace>
|
||||
<PackageProjectUrl>https://technitium.com/dns/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/TechnitiumSoftware/DnsServer</RepositoryUrl>
|
||||
<Description>Blocks DNS rebinding attacks using configured private domains and networks.</Description>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DnsServerCore.ApplicationCommon\DnsServerCore.ApplicationCommon.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="TechnitiumLibrary">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="TechnitiumLibrary.Net">
|
||||
<HintPath>..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="dnsApp.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
180
Apps/DnsRebindingProtectionApp/App.cs
Normal file
180
Apps/DnsRebindingProtectionApp/App.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
Technitium DNS Server
|
||||
Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
using DnsServerCore.ApplicationCommon;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using TechnitiumLibrary;
|
||||
using TechnitiumLibrary.Net;
|
||||
using TechnitiumLibrary.Net.Dns;
|
||||
using TechnitiumLibrary.Net.Dns.ResourceRecords;
|
||||
|
||||
namespace DnsRebindingProtection
|
||||
{
|
||||
public sealed class App : IDnsApplication, IDnsPostProcessor
|
||||
{
|
||||
#region variables
|
||||
|
||||
bool _enableProtection;
|
||||
HashSet<NetworkAddress> _privateNetworks;
|
||||
HashSet<string> _privateDomains;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Nothing to dispose of.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region private
|
||||
|
||||
private static string GetParentZone(string domain)
|
||||
{
|
||||
int i = domain.IndexOf('.');
|
||||
if (i > -1)
|
||||
return domain[(i + 1)..];
|
||||
|
||||
//dont return root zone
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsPrivateDomain(string domain)
|
||||
{
|
||||
domain = domain.ToLowerInvariant();
|
||||
|
||||
do
|
||||
{
|
||||
if (_privateDomains.Contains(domain))
|
||||
return true;
|
||||
|
||||
domain = GetParentZone(domain);
|
||||
}
|
||||
while (domain is not null);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsRebindingAttempt(DnsResourceRecord record)
|
||||
{
|
||||
IPAddress address;
|
||||
|
||||
switch (record.Type)
|
||||
{
|
||||
case DnsResourceRecordType.A:
|
||||
if (IsPrivateDomain(record.Name))
|
||||
return false;
|
||||
|
||||
address = (record.RDATA as DnsARecordData).Address;
|
||||
break;
|
||||
|
||||
case DnsResourceRecordType.AAAA:
|
||||
if (IsPrivateDomain(record.Name))
|
||||
return false;
|
||||
|
||||
address = (record.RDATA as DnsAAAARecordData).Address;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (NetworkAddress networkAddress in _privateNetworks)
|
||||
{
|
||||
if (networkAddress.Contains(address))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryDetectRebinding(IReadOnlyList<DnsResourceRecord> answer, out List<DnsResourceRecord> protectedAnswer)
|
||||
{
|
||||
for (int i = 0; i < answer.Count; i++)
|
||||
{
|
||||
DnsResourceRecord record = answer[i];
|
||||
if (IsRebindingAttempt(record))
|
||||
{
|
||||
//rebinding attempt detected!
|
||||
//prepare protected answer
|
||||
protectedAnswer = new List<DnsResourceRecord>(answer.Count);
|
||||
|
||||
//copy passed records
|
||||
for (int j = 0; j < i; j++)
|
||||
protectedAnswer.Add(answer[j]);
|
||||
|
||||
//copy remaining records with check
|
||||
for (int j = i + 1; j < answer.Count; j++)
|
||||
{
|
||||
record = answer[j];
|
||||
if (!IsRebindingAttempt(record))
|
||||
protectedAnswer.Add(record);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protectedAnswer = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region public
|
||||
|
||||
public Task InitializeAsync(IDnsServer dnsServer, string config)
|
||||
{
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(config);
|
||||
JsonElement jsonConfig = jsonDocument.RootElement;
|
||||
|
||||
_enableProtection = jsonConfig.GetPropertyValue("enableProtection", true);
|
||||
_privateNetworks = new HashSet<NetworkAddress>(jsonConfig.ReadArray("privateNetworks", NetworkAddress.Parse));
|
||||
_privateDomains = new HashSet<string>(jsonConfig.ReadArray("privateDomains"));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<DnsDatagram> PostProcessAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, DnsDatagram response)
|
||||
{
|
||||
// Do not filter authoritative responses. Because in this case any rebinding is intentional.
|
||||
if (!_enableProtection || response.AuthoritativeAnswer)
|
||||
return Task.FromResult(response);
|
||||
|
||||
if (TryDetectRebinding(response.Answer, out List<DnsResourceRecord> protectedAnswer))
|
||||
return Task.FromResult(response.Clone(protectedAnswer));
|
||||
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
|
||||
public string Description
|
||||
{ get { return "Protects from DNS rebinding attacks using configured private domains and networks."; } }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"enabled": false,
|
||||
"enableProtection": true,
|
||||
"privateNetworks": [
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"169.254.0.0/16",
|
||||
Reference in New Issue
Block a user