November 1, 2026
One Null Byte, One Leaked Version String: Fixing an Azure Application Gateway Fingerprinting…
A short pentest finding, a default error page, and a fix that takes about 30 minutes.

By Tobi Oderinde
3 min read
A pentest report landed with a finding that looked almost too small to matter: add %00 to a URL and the server tells you what it is. No data stolen, no login bypassed, just one line of HTML. But findings like this are exactly what attackers collect first, so I fixed it properly.
In this article I'll cover what caused it, how to fix it with custom error pages on Azure Application Gateway, the gotchas I hit, and what the fix does not cover.
What the tester found
Sending a URL-encoded null byte in the path makes the gateway reject the request before it reaches the backend:
curl -i 'https://api.example.com/v1/Transaction/invalid%00'curl -i 'https://api.example.com/v1/Transaction/invalid%00'The response is the gateway's default 400 page, which ends with:
<center>Microsoft-Azure-Application-Gateway/v2</center><center>Microsoft-Azure-Application-Gateway/v2</center>That string tells an attacker exactly which platform sits in front of your APIs, so they can focus on techniques and known issues for that product. Severity is low, but the fix is cheap.
Why it happens
The page isn't generated by your app. The gateway produces it whenever it rejects or fails a request itself. Any response your backend generates passes through untouched, which is why you'll never see this string from a normal API error.
Status
Typical cause
Generated by
400
Malformed path, bad encoding, oversized headers
Gateway
403
WAF block
Gateway
405 / 408
Disallowed method, request timeout
Gateway
502 / 503 / 504
Backend unhealthy, down or slow
Gateway
500, 404 with app body
Application error
Your backend
So %00 is just one trigger. Fixing only the 400 would leave the same leak on every other row.
The fix: custom error pages
Application Gateway lets you replace its default pages with your own HTML, hosted at a public HTTPS URL. The gateway fetches the file itself.
1. Host the page
A storage account with static website hosting is the simplest option:
az storage account create --name <sa> --resource-group <rg> \
--location <region> --sku Standard_LRS --https-only true --min-tls-version TLS1_2
az storage blob service-properties update --account-name <sa> \
--static-website --index-document error.html --404-document error.html
az storage blob upload --account-name <sa> --container-name '$web' \
--name error.html --file ./error.html --content-type text/html --overwriteaz storage account create --name <sa> --resource-group <rg> \
--location <region> --sku Standard_LRS --https-only true --min-tls-version TLS1_2
az storage blob service-properties update --account-name <sa> \
--static-website --index-document error.html --404-document error.html
az storage blob upload --account-name <sa> --container-name '$web' \
--name error.html --file ./error.html --content-type text/html --overwriteEnabling static website creates the $web container and a separate public endpoint. Your other containers are not affected.
Gotcha: use the *.web.core.windows.net endpoint, not *.blob.core.windows.net/$web/.... The blob URL returns a ResourceNotFound XML error, and the gateway needs the static website URL.
2. Make the page self-contained
The gateway serves one file. Inline your CSS, and either base64-embed images or host them on the same endpoint. Keep any product, server or version names out of the page. If your page loads images from a public blob URL, you've traded one fingerprint for another.
3. Attach it to the listener
In the portal: Application gateway > Listeners > your listener > Custom error pages, then paste the page URL into the status code fields. Do this on both the HTTPS and HTTP listeners for the same hostname.
Or in Terraform:
custom_error_configuration {
status_code = "HttpStatus400"
custom_error_page_url = "https://<sa>.z13.web.core.windows.net/error.html"
}custom_error_configuration {
status_code = "HttpStatus400"
custom_error_page_url = "https://<sa>.z13.web.core.windows.net/error.html"
}Repeat the block for 403, 405, 408, 500, 502, 503 and 504. One generic page can serve all of them.
4. Retest
curl -i 'https://api.example.com/v1/Transaction/invalid%00'curl -i 'https://api.example.com/v1/Transaction/invalid%00'On Windows PowerShell use curl.exe, because curl is an alias for Invoke-WebRequest. The body should now be your page, with no gateway string.
What this doesn't fix
Be honest with yourself and the tester about the limits:
- Other listeners. Production, sandbox and any other hostnames on the same gateway have the same leak until you configure them too. A gateway-level setting covers requests that match no listener.
- Errors from deeper layers. If your ingress controller or a proxy inside Kubernetes returns its own 404 or 503, the gateway passes it through. Those pages leak a different fingerprint and need their own custom error config.
- Headers. Check
curl -ioutput forServeror similar headers, not just the body. - 404 on listeners. The portal doesn't offer 404 in the listener-level list, so gateway-generated 404s need separate attention.
- Reachability. The error page must be publicly reachable over HTTPS. A storage firewall will break the save.
Checklist
- Generic, self-contained error page with no product or version names
- Hosted on the static website endpoint, tested in a browser
- Set for 400, 403, 405, 408, 500, 502, 503, 504
- Applied to HTTP and HTTPS listeners, and to every environment
- Retested with
%00, a TRACE request and an unknown Host header - Response headers checked for leftovers
- Ingress layer reviewed for its own default pages
Conclusion
A fingerprinting finding is small, but fixing it teaches you where your errors actually come from: the gateway, the ingress, or the app. Cover every gateway-generated status code, every listener and every environment, then retest from the outside. If you fix only the exact URL in the report, the next tester will find the same leak through another door.
Further reading
- Microsoft Learn: Create Application Gateway custom error pages (https://learn.microsoft.com/azure/application-gateway/custom-error)
- Microsoft Learn: Static website hosting in Azure Storage (https://learn.microsoft.com/azure/storage/blobs/storage-blob-static-website)
- OWASP Web Security Testing Guide: Fingerprint Web Server (https://owasp.org/www-project-web-security-testing-guide/)