Introduction

During a recent web security challenge designed by Shahrokh, I encountered an interesting scenario involving JavaScript URL execution, which was guarded by a hostname origin check. The challenge's core was a simple but strict validation of the domain name before allowing the execution of potentially dangerous JavaScript URLs.

The challenge HTML code was as follows:

None
<script>
    (function() {
      const messageElement = document.getElementById('message');
      const params = new URLSearchParams(window.location.search);
      const url = params.get('url');
      if (!url) {
        messageElement.textContent = 'No "url" parameter provided.';
        return;
      }
      if (window.location.hostname === 'attacker-newrelic.com') {
        messageElement.textContent = 'You should pass check in line 61 :)';
        return;
      }
      messageElement.textContent = `Redirecting to: ${url}`;
      window.location.href = url;
    })();
  </script>

As you can see, the JavaScript inside the page checks the current hostname exactly against "attacker-newrelic.com" using strict equality (===). If the hostname matches, it allows a special message indicating a successful check, otherwise, it redirects the user to the url parameter's value.

My objective was to bypass this strict hostname check without owning the domain, without manipulating DNS, or modifying the hosts file on my machine. Also, I wanted to execute a malicious payload like:

javascript:alert(origin)

Even though the hostname check was designed to block any domain other than attacker-newrelic.com.

Initial Approach: Unicode Homograph Attack

At first, I tried a classic phishing and spoofing technique: register a domain with visually similar Unicode characters — a homograph domain.

For example, replacing the first Latin "a" with Cyrillic "а" (U+0430):

аttacker-newrelic.com

Which translates to Punycode as:

xn--ttacker-newrelic-sdn.com

The idea was that the browser would treat this visually as "attacker-newrelic.com" but the strict string check would fail or pass incorrectly.

Result

Testing the URL:

http://xn--ttacker-newrelic-sdn.com/challenge.html?url=javascript:alert(origin)

At first glance, it seems the origin check prevents the attack, as location.hostname resolves to xn--ttacker-newrelic-sdn.com, which doesn't exactly match attacker-newrelic.com. However, the JavaScript payload still executes in the browser.

What actually happens is that the payload runs and pops an alert, but the origin differs due to the encoded domain or trailing dot tricks — meaning the browser treats them as different origins. As a result, security mechanisms like cookie sharing, CORS policies, or other origin-bound checks fail silently. This highlights a key detail: the origin must match exactly — including FQDN encoding and trailing dots — for things like cookies to apply. It's not enough for the payload to execute visually.

You can use the following image to understand how the origin differs:

None

Secondary Attempts: DNS and Hosts File Modification

I also tried to locally override DNS resolution using the hosts file:

127.0.0.1 attacker-newrelic.com

However, this was not viable because:

  • The challenge environment did not permit local DNS overrides.
  • The origin check was done in the browser via JavaScript, not at the DNS level.
  • The real server IP was hidden behind Cloudflare, making direct IP access impossible.

The Final and Elegant Solution: Trailing Dot Trick

After several dead ends, a simple yet powerful technique worked: using a trailing dot at the end of the hostname.

What is the trailing dot?

In DNS and URL standards, a trailing dot denotes a Fully Qualified Domain Name (FQDN). For example:

attacker-newrelic.com.

is equivalent to

attacker-newrelic.com

But the string comparison in JavaScript treats them differently.

Why does this matter?

In the browser, accessing:

None
https://attacker-newrelic.com./challenge.html?url=javascript:alert(origin)

results in

location.hostname === "attacker-newrelic.com."  // true
location.hostname === "attacker-newrelic.com"   // false

If the origin check is:

<script>
    (function() {
      const messageElement = document.getElementById('message');
      const params = new URLSearchParams(window.location.search);
      const url = params.get('url');
      if (!url) {
        messageElement.textContent = 'No "url" parameter provided.';
        return;
      }
      if (window.location.hostname === 'attacker-newrelic.com') {
        messageElement.textContent = 'You should pass check in line 61 :)';
        return;
      }
      messageElement.textContent = `Redirecting to: ${url}`;
      window.location.href = url;
    })();
  </script>

Then this will fail with the trailing dot. But if the code does not normalize the trailing dot, it might allow other cases to slip through, or if the logic is reversed (e.g., checking for inequality), this can lead to bypass.

In my case, the application was allowing the trailing-dot variant to bypass restrictions, enabling full XSS execution with the crafted URL.

and on dig request:

None

1. What is an FQDN?

A Fully Qualified Domain Name (FQDN) is the complete domain name that uniquely specifies the exact location of a host within the DNS hierarchy, including all higher-level domains up to the DNS root.

  • It unambiguously identifies a host in the Domain Name System (DNS).
  • An FQDN always ends with a trailing dot (.) to denote the root of the DNS namespace.

Example of an FQDN:

www.example.com.
  • www — Hostname (or subdomain)
  • example — Second-level domain
  • com — Top-level domain (TLD)
  • . — Root zone (the DNS namespace root)

The trailing dot is critical — it indicates the absolute, fully qualified name. Without it, the name can be considered relative in some DNS contexts.

2. Structure of an FQDN

An FQDN consists of a series of labels separated by dots (.), forming a hierarchical sequence from the most specific (left) to the most general (right):

[hostname].[subdomain].[domain].[top-level domain].[root]

Example:

mail.sales.company.com.
  • mail — host or service
  • sales — subdomain
  • company — second-level domain
  • com — TLD
  • . — DNS root (explicit trailing dot)

Each label can be up to 63 characters long; the full FQDN cannot exceed 255 characters.

3. Why is the trailing dot important?

  • The trailing dot explicitly signals the DNS root.
  • When the trailing dot is omitted, DNS resolvers often treat the name as relative, appending the local domain or search suffix.

For example, if you query www.example.com (no trailing dot) on a client machine with the local domain local.netThe DNS resolver might attempt:

www.example.com.local.net

before or in addition to querying www.example.com.

When you query with the trailing dot:

www.example.com.

The resolver understands this is an absolute name and looks it up exactly without appending anything.

4. DNS Internals and FQDN

  • The DNS namespace is a hierarchical tree, starting at the root zone .
  • Each label is a node in the tree
  • The root zone (.) is the apex of the DNS hierarchy and contains top-level domains (TLDs) like .com, .org, .net

When a DNS query is made with an FQDN (with ta railing dot), the resolver starts at the root servers and walks down the hierarchy label by label until it finds the requested record.

5. Practical Usage of FQDN

  • In DNS configurations: zone files, reverse DNS, SSL certificates, email systems
  • In system configs: e.g., /etc/hosts can contain FQDNs for hostname resolution
  • In URLs and HTTP requests: usually, browsers strip the trailing dot for display, but some tools or scripts use the trailing dot explicitly

6. How do browsers treat FQDN with a trailing dot?

  • Browsers typically accept URLs with trailing dots (e.g., http://example.com./) and treat them as equivalent to the domain without a trailing dot for network requests.
  • However, JavaScript APIs expose window.location.hostname including the trailing dot if present.
  • This can lead to logical mismatches in hostname checks if the code doesn't normalize the trailing dot.
  • Attackers can abuse this behavior to bypass hostname-based security checks (e.g., bypass window.location.hostname === "example.com" by using example.com.).

7. Security implications of FQDN trailing dot

  • Hostname verification bypass: A common client-side security mistake is to compare window.location.hostname or similar fields directly without normalizing trailing dots.
  • Cookie scope confusion: Cookies set for example.com and example.com. might be treated differently by browsers.
  • Phishing & domain spoofing: Malicious actors can exploit subtle differences in DNS resolution and presentation of FQDNs to trick users or bypass filters.
  • CORS bypass: Cross-Origin Resource Sharing policies might treat example.com and example.com. as different origins depending on the implementation.

8. Technical Standards & RFCs

  • RFC 1034 (Domain Names — Concepts and Facilities)
  • RFC 1035 (Domain Names — Implementation and Specification)
  • RFC 2181 (Clarifications to the DNS Specification)

These RFCs define the syntax, structure, and behavior of domain names, including the concept of fully qualified domain names and the significance of the trailing dot.

9. Key Takeaways

Concept Explanation FQDN Complete domain name including root zone (with trailing dot) Trailing Dot Marks the DNS root; makes the domain name absolute DNS Resolution FQDNs ensure exact and unambiguous DNS queries Browser Behavior Accepts trailing dot but exposes it in JavaScript APIs Security Risks Direct string comparison without normalization can be bypassed Practical Use Essential for DNS configs, SSL certs, emails, and network tools

10. Example of Normalization in JavaScript

// Remove trailing dot before hostname comparison
function normalizeHostname(hostname) {
  return hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;
}
if (normalizeHostname(window.location.hostname) === 'attacker-newrelic.com') {
  // Safe check
}

11. Bonus: FQDN vs Relative Domain Names

  • FQDN: Always ends with a dot; absolute path in DNS tree
  • Relative Domain Name: No trailing dot; resolved relative to a search domain or suffix

Example:

Name Meaning server1 Relative to local domain search server1.company.com. Absolute, fully qualified name

Acknowledgments

I want to sincerely thank my good friend Sharo (@Sharo_k_h) for his invaluable help and insights during this challenge. His guidance was crucial in understanding the nuances behind the trailing dot technique and FQDN behavior in browsers.

Conclusion

This challenge was an excellent reminder that sometimes the simplest tricks, like adding a trailing dot, can bypass complex security checks. Unicode homograph attacks and DNS manipulation are often overestimated, while subtle string nuances in domain names can be the real weak points.

🔹 Follow me for more security research and write-ups: 📌 Channel: GO-TO CVE 🐦 Twitter: soltanali0

Source: elmahdi4.wordpress.com

Further Reading: GitHub Commit Demonstrating Similar Behavior