I was hunting for XSS vulnerabilities on an HR application that had proved fruitful in the past when I discovered some user-controlled input that was rendered unsafely in the DOM. This is how I typically find XSS vulnerabilities – send a unique string (like ‘mypayload’) in the field and the use my browsers Dev Tools to search for that string in the DOM to see if and how it’s being reflected back.
This particular page was actually where I had found the source of stored XSS in the past. I decided to revisit it to see if there were any other places on the site that handled this information unsafely. The page in question allowed the currently logged in user to modify their contact details:

I began with a simple payload:

There was a page on the site that served as a corporate directory. It displayed all employees of the organization. I navigated to the page, opened Dev Tools, and searched for my test payload.

I could see the first name and last name were appearing inside of an array called ‘dataSource’ that was inside of a Javascript tag. This seemed to be how the site was populating the data for each employee as this JS code existed for each employee in the directory.
My next thought was to update the name to something that would break this Javascript:

And sure enough, it broke the directory page!

You can see my closing script tag ended the Javascript block. The page was interpreting my input as valid Javascript. Next was to use a POC payload, like ‘alert()’ to prove the point.
Unfortunately, this revealed (2) problems. The first was that these name fields have character limits that enforced on the server. Each field (first, last) could have a maximum of 30 characters. So I could insert 30 characters in the first name field and 30 in the last name which would be separated by a space. The second issue was that the first word of each letter in input was being capitalized and every other letter in the word was being lowercased. So ‘alert()’ became ‘Alert()’ which will not work.
URL encoding the ‘a’ kept it lowercase but ate too many characters so I couldn’t finish the payload.
<a href="javaScRipT: name">a
The above payload would work BUT it doesn’t leave enough characters to break out of the current script tag at the beginning.
After spending more time that I’d like to admin on this one, I took the issue to a discord server I’m part of with some fellow BB hunters. One of them mentioned this site for finding tiny XSS payloads: https://tinyxss.terjanq.me/
After trying many payloads, we finally found one that worked!

"}]});F=Function</script><Svg Onload=F(`'`+URL)()>
The result looked like this:

Let’s walk through the payload!
"}]});
This cleanly closes the dataSource element and the getOrgChart function it was nested inside of.
F=Function</script>
We take advantage of already being inside of a script tag and define a new function called ‘F’. This is basically a dynamic function constructor — a direct cousin of ‘eval’. We then close the script block.
<Svg Onload=F(`'`+URL)()>
This part is using HTML entity encoding to get capital letters. Let’s look at it decoded:
<Svg Onload=F(`'`+URL)()>
We’re using the svg tag with the onload element to call our function ‘F’ once the webpage has loaded. In modern browsers, ‘URL’ is actually a built-in global — it’s the ‘window.URL’ constructor function for working with URL objects. Essentially, it will pull in the full URL – including the #hash, and URL encode everything up to the hash:
<Svg Onload=F(`'`+URL)()>
So the following URL:
https://victim.com/page#'-alert(document.domain)//
Would become:
<Svg Onload=F('https://victim.com/page#'-alert(document.domain)//)()>
‘https://victim.com/page#‘ is a string literal (delimited by the single quotes)
-alert(document.domain)// is interpreted as normal JavaScript which will immediately execute the alert dialogue!

So there we have it! Our DOM-based reflected XSS is a success. This was an incredible learning experience
Leave a Reply