Skip to content

[27.4] Anti-SSRF Protection Blocking Internal HTTP Calls #8206

@ChrisChristophers

Description

@ChrisChristophers

1. Describe the bug
We're experiencing issues with failing internal HTTP requests between two OnPrem BC-Containers ( using SSL with Self-Signed Certificates )after upgrading from 27.3 to 27.4. Downgrading back to 27.3 provides a temporary fix.

2. To Reproduce
Create two BC 27.4 Containers and call one's API from the other.

    procedure GetCompanies(InstanceUrl: Text; Username: Text; Password: Text; var Companies: List of [Text])
    var
        l_HttpClient: HttpClient;
        l_HttpRequestMessage: HttpRequestMessage;
        l_HttpResponseMessage: HttpResponseMessage;
        l_Headers: HttpHeaders;
        l_AuthText: Text;
        l_Base64Convert: Codeunit "Base64 Convert";
        l_Url: Text;
        l_ResponseText: Text;
        l_JsonToken: JsonToken;
        l_JsonArray: JsonArray;
        l_JsonValue: JsonToken;
        l_CompanyName: Text;
        i: Integer;
    begin
        Clear(Companies);

        // Use the correct API v2.0 endpoint
        l_Url := InstanceUrl.TrimEnd('/') + '/api/v2.0/companies';

        // Build auth header
        l_AuthText := l_Base64Convert.ToBase64(Username + ':' + Password);

        l_HttpClient.UseServerCertificateValidation(false);

        l_HttpRequestMessage.Method := 'GET';
        l_HttpRequestMessage.SetRequestUri(l_Url);
        l_HttpRequestMessage.GetHeaders(l_Headers);
        l_Headers.Add('Authorization', 'Basic ' + l_AuthText);

        if not l_HttpClient.Send(l_HttpRequestMessage, l_HttpResponseMessage) then
            Error('Failed to connect to %1', l_Url);

        if not l_HttpResponseMessage.IsSuccessStatusCode then
            Error('Failed to get companies. Status: %1 %2', l_HttpResponseMessage.HttpStatusCode, l_HttpResponseMessage.ReasonPhrase);

        l_HttpResponseMessage.Content.ReadAs(l_ResponseText);

        if not l_JsonToken.ReadFrom(l_ResponseText) then
            Error('Invalid JSON response');

        if not l_JsonToken.AsObject().Get('value', l_JsonToken) then
            Error('No value array in response');

        l_JsonArray := l_JsonToken.AsArray();

        for i := 0 to l_JsonArray.Count - 1 do begin
            l_JsonArray.Get(i, l_JsonValue);
            if l_JsonValue.AsObject().Get('name', l_JsonToken) then begin
                l_CompanyName := l_JsonToken.AsValue().AsText();
                Companies.Add(l_CompanyName);
            end;
        end;
    end;

3. Expected behavior
Retrieve the companies from our target environment.

4. Actual behavior

Image

5. Versions:

  • AL Language: latest
  • Visual Studio Code: latest
  • Business Central: 27.4
  • List of Visual Studio Code extensions that you have installed:
  • Operating System:
    • [X ] Windows
    • Linux
    • MacOS

Here is the Eventlog and a Claude analysation:

EventLogHttpCallFail.csv

Claude's Response:

Analysis: Anti-SSRF Protection Blocking Internal HTTP Calls

The log shows a clear pattern — all 3 calls fail identically. Here's the chain of events for each call:

Error Sequence (per call)

  1. AntiSSRFWrapper.cs  Non routable address detected: 10.11.42.206 / 10.11.42.205
  2. AntiSSRFHandler.cs  SSRF violation detected: non routable network address.
  3. HTTP response  Status code = 0 (0) (request never sent)
  4. Exception  NavNCLDialogException bubbles up from Codeunit51148.GetCompanies  Page51178

Root Cause

BC 27.4 introduced (or enforced) Anti-SSRF (Server-Side Request Forgery) protection in the HTTP client pipeline. This new AntiSSRFHandler intercepts outgoing HTTP requests, resolves the target hostname to an IP, and blocks requests to private/non-routable IP ranges.

Your internal hostnames resolve to RFC 1918 private addresses:

Hostname | IP | Range -- | -- | -- bc-prelive.sw.buhl-data.com | 10.11.42.206 | 10.0.0.0/8 (private) bc-live.sw.buhl-data.com | 10.11.42.205 | 10.0.0.0/8 (private)

The AntiSSRFHandler.SendAsync (line 114) checks the resolved IP before the request leaves the server and kills it with status code 0 if it targets a private network.

Why 27.3 Works

In 27.3, the AntiSSRFHandler either didn't exist or wasn't enforced for on-premises server-to-server calls. Microsoft tightened this in 27.4, likely as part of their ongoing security hardening (this is a standard cloud-security measure to prevent extensions from reaching internal infrastructure).

Fix Options

  1. Server configuration — In the BC Server CustomSettings.config, look for a setting like:

    <textarea class="ime-text-area" readonly="true" tabindex="-1" aria-hidden="true" style="min-width: 0px; min-height: 0px; margin: 0px; padding: 0px; position: absolute; resize: none; border: none; overflow: hidden; color: transparent; background-color: transparent; z-index: -10; outline: none !important;"></textarea>

    or use the Set-NAVServerConfiguration PowerShell cmdlet. The exact key name may vary — check the 27.4 release notes or CustomSettings.config for any new Anti-SSRF / "NonRoutable" / "PrivateNetwork" settings.

  2. Reverse proxy with a public IP — Put a reverse proxy (e.g., nginx, IIS ARR) in front of the target BC instances that has a public/routable IP, and point your DNS there.

  3. Use localhost / loopback exception — If the caller and target run on the same machine, localhost may be exempted (though this likely won't apply to your setup).

Recommended first step: Check the BC 27.4 server configuration file on the calling instance for new Anti-SSRF settings and enable the exception for private network addresses.


Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions