0125: Particle Generator
Writeup for the Intigriti January 2025 challenge π₯
Last updated
Writeup for the Intigriti January 2025 challenge π₯
Last updated
URL Parsing, Path Traversal, XSS
Pop an alert and win Intigriti swag! π
Here's the challenge source code. I've excluded the irrelevant functionality, e.g. generateFallingParticles
, which is just for animation.
Let's trace through in order of execution:
When the page loads, checkQueryParm()
is called
A text
parameter is extracted from the URL using a custom function; getParameterByName
Square brackets ([]
) are escaped in the parameter name.. I'm not sure why?
Regex ensures the text
parameter is supplied after a ?
or &
character (ensuring it is part of the query string), e.g. ?text=CryptoCat
or &text=CryptoCat
It also captures everything after the =
in the query string with (=([^&#]*)|&|#|$)
, which matches any sequence of characters up to:
&
(next parameter)
#
(start of a fragment)
$
(end of the string)
The extracted query value undergoes two final operations, before being returned:
+
symbols are replaced with a [space]
The string is URL-decoded
Next, an XSS()
function checks if the URL-decoded query string (window.location.search
) or fragment identifier (window.location.hash
) contain any angular brackets (<>
)
Providing they do not, modal.innerHTML
will be set to the text
parameter (this is our dangerous sink)
One final thing to mention; when we submit the form it executes a redirectToText()
function which URL-encodes the textbox input and redirects the browser to the new path, e.g. /challenge?text=CryptoCat
TLDR; we can't place our XSS payload in the window.location.search
or window.location.hash
due to the XSS filter.
However, getParameterName
performs regex on the entire URL (window.location.href
), so maybe we can inject elsewhere without tripping the XSS()
detector.
Note that since the window.location.search
is validated, we can't use ?
in the query string, e.g.
However, the custom getParameterName
function extracts queries that become with ?
or &
, so what if we try like:
Nice! So it won't trip the filter but the page is 404! If we change the URL to include a ?
first, it will load e.g.
But now we have the same problem that our payload will be filtered!
This is where path traversal comes in! We can provide our payload in the path of the URL, but apply some traversal such that the URL resolves to the same endpoint! Let's verify the behaviour:
We are returned to the /
directory, so the traversal indeed works! Let's check the console.
Perfect! We have smuggled our parameter in the path, all we need now is to replace it with an XSS payload (careful to URL encode characters due to normalisation).
Note: this was made possible due to the fact the backend server decodes the URL path before routing it. Originally, the creator wanted me to host the challenge on GitHub Pages due to this default behaviour. Instead, we implemented the same functionality via nginx
.