HIGH dns cache poisoningaspnethmac signatures

Dns Cache Poisoning in Aspnet with Hmac Signatures

Dns Cache Poisoning in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability

In ASP.NET applications, DNS cache poisoning can intersect with HMAC signature validation when an attacker manipulates DNS records to redirect a client or server to a malicious IP. If the application resolves a hostname (for example, a payment gateway or an identity provider) at startup or request time and caches the result, poisoned DNS can cause all requests to be sent to an endpoint controlled by the attacker. Even when HMAC signatures are used to ensure message integrity and authenticity, the security of the scheme depends on communicating with the correct, trusted endpoint. If DNS is poisoned, the client or server may establish an HTTPS connection to an attacker-controlled server that presents a valid TLS certificate for the malicious host, and the application may incorrectly trust the HMAC signatures it receives because they are verified only against a shared secret, not against the identity of the endpoint.

ASP.NET applications that use HMAC signatures for API authentication or message verification often rely on static configuration (shared secrets) and may not cryptographically bind the peer’s identity to the DNS name used during the TLS handshake. If DNS resolution is performed via Dns.GetHostEntry or similar APIs and the result is cached, an attacker who can poison the local DNS cache (via compromised router, malicious resolver, or local attacker) can redirect traffic. Because HMAC does not inherently prevent the use of a malicious host, the application may accept attacker-generated messages that appear valid, leading to request tampering, session hijacking, or privilege escalation. This becomes especially relevant when using HMAC for webhook validation or message queues where the consuming endpoint trusts the signature without independently verifying the transport identity.

The risk is compounded when applications use framework features like HttpClient with a base address derived from a DNS name, and the DNS lookup occurs once and reused across requests. If the DNS entry changes to point to an attacker, the application may continue to send requests to the poisoned host, validating HMAC signatures on attacker-controlled data. Common attack patterns include intercepting webhook notifications or API calls and forging valid HMAC-signed requests that the backend processes as legitimate. For these reasons, defense in depth is required: ensuring endpoint identity is verified via certificate pinning or secure default resolver behavior, avoiding long-lived caches of DNS results for critical endpoints, and coupling HMAC validation with transport-layer identity checks.

Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes

To reduce the risk when using HMAC signatures in ASP.NET, apply multiple layers of mitigation. First, avoid relying solely on DNS-based resolution for critical endpoints; prefer explicit IPs or use configuration that is verified at runtime, and ensure that HttpClient is configured with appropriate ServerCertificateCustomValidationCallback to enforce certificate pinning for known hosts. Second, do not cache DNS results for critical service endpoints, or use short timeouts and re-validate the endpoint identity on each request. Third, bind the expected hostname into your HMAC verification logic by including the service identifier or a nonce tied to the expected endpoint, so that even if a message is intercepted, it cannot be reused against a different host.

Below are concrete code examples for HMAC validation in ASP.NET Core that emphasize identity binding and safe handling of endpoints. These samples show how to compute and verify HMAC signatures and how to configure HttpClient to reduce DNS poisoning risks.

Computing and sending an HMAC-signed request (client)

using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

public class HmacClient
{
    private readonly HttpClient _httpClient;
    private readonly byte[] _secret;

    public HmacClient(string baseUrl, byte[] secret)
    {
        // Prefer a known host via configuration; avoid dynamic DNS lookups for critical calls
        _httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) };
        _secret = secret ?? throw new ArgumentNullException(nameof(secret));
    }

    public async Task SendSignedRequestAsync(string path, HttpMethod method, string payload = "")
    {
        var request = new HttpRequestMessage(method, path);
        if (!string.IsNullOrEmpty(payload))
        {
            request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
        }

        var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
        var stringToSign = $"{method.Method}\n{path}\n{timestamp}\n{payload}";

        using var hmac = new HMACSHA256(_secret);
        var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));

        request.Headers.Add("X-API-Timestamp", timestamp);
        request.Headers.Add("X-API-Signature", signature);
        // Include an identity hint if your protocol supports it
        request.Headers.Add("X-API-Client-Id", "trusted-client-01");

        return await _httpClient.SendAsync(request);
    }
}

Verifying HMAC and binding to expected identity (server)

using System;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

public class HmacValidationMiddleware
{
    private readonly RequestDelegate _next;
    private readonly byte[] _secret;
    private const string ExpectedClientId = "trusted-client-01";

    public HmacValidationMiddleware(RequestDelegate next, byte[] secret)
    {
        _next = next;
        _secret = secret;
    }

    public async Task Invoke(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue("X-API-Timestamp", out var timestampValues) ||
            !context.Request.Headers.TryGetValue("X-API-Signature", out var signatureValues) ||
            !context.Request.Headers.TryGetValue("X-API-Client-Id", out var clientIdValues))
        {
            context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            return;
        }

        var timestamp = timestampValues.ToString();
        var receivedSignature = signatureValues.ToString();
        var clientId = clientIdValues.ToString();

        // Reject if client identity does not match expected value
        if (clientId != ExpectedClientId)
        {
            context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            return;
        }

        // Replay protection: ensure the timestamp is recent (e.g., within 5 minutes)
        if (!long.TryParse(timestamp, out var ts) ||
            DateTimeOffset.UtcNow.ToUnixTimeSeconds() - ts > 300)
        {
            context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            return;
        }

        // Reconstruct the signed string as the sender did
        using var reader = new System.IO.StreamReader(context.Request.Body);
        var body = await reader.ReadToEndAsync();
        var stringToSign = $"{context.Request.Method}\n{context.Request.Path}\n{timestamp}\n{body}";

        using var hmac = new HMACSHA256(_secret);
        var computed = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));

        if (!CryptographicOperations.FixedLengthEquals(Convert.FromBase64String(receivedSignature), Convert.FromBase64String(computed)))
        {
            context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            return;
        }

        await _next(context);
    }
}

Configuring HttpClient to avoid cached poisoned DNS

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

public class ResilientHttpClient
{
    private readonly HttpClientHandler _handler;
    private readonly HttpClient _httpClient;

    public ResilientHttpClient()
    {
        // Do not rely on OS DNS cache for critical endpoints; use a custom handler with controlled behavior
        _handler = new HttpClientHandler();
        // Consider enabling ServerCertificateCustomValidationCallback for pinning in production
        _httpClient = new HttpClient(_handler, disposeHandler: true) { Timeout = TimeSpan.FromSeconds(10) };
    }

    public async Task GetWithRefreshOnErrorAsync(string hostFromConfig, string path, CancellationToken ct)
    {
        // Example: rebuild the base address from a trusted configuration value on each critical request
        // or use a custom resolver that revalidates identity.
        var uri = new UriBuilder("https", hostFromConfig, 443, path).Uri;
        var response = await _httpClient.GetAsync(uri, ct);
        // If you suspect DNS issues, implement logic to re-resolve and rotate endpoints safely.
        return response;
    }
}

Note: middleBrick scans can help identify whether your exposed endpoints rely on DNS-based resolution without additional identity binding. Use the CLI (middlebrick scan <url>), the Web Dashboard, or the GitHub Action to detect related issues in your API surface.

Frequently Asked Questions

Can HMAC signatures alone prevent DNS cache poisoning attacks?
No. HMAC signatures ensure message integrity and authenticity between endpoints that share a secret, but they do not prevent an attacker from redirecting network traffic to a malicious host. You must bind the expected hostname or certificate to the validation logic and avoid relying solely on DNS-based resolution for critical endpoints.
What extra steps should I take in ASP.NET when using HMAC for webhooks?
Include the expected client identity (e.g., a client ID or fingerprint) in the signed string and verify it server-side; use short-lived nonces or timestamps to prevent replay; configure HttpClient with explicit base addresses and certificate pinning; and avoid long-lived DNS caching for security-critical endpoints.